| /* |
| * Copyright (c) 1999, 2008, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package com.sun.jmx.mbeanserver; |
| |
| import com.sun.jmx.defaults.ServiceName; |
| import static com.sun.jmx.defaults.JmxProperties.MBEANSERVER_LOGGER; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| import java.util.logging.Level; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.management.DynamicMBean; |
| import javax.management.InstanceAlreadyExistsException; |
| import javax.management.InstanceNotFoundException; |
| import javax.management.ObjectName; |
| import javax.management.QueryExp; |
| import javax.management.RuntimeOperationsException; |
| |
| /** |
| * This repository does not support persistency. |
| * |
| * @since 1.5 |
| */ |
| public class Repository { |
| |
| /** |
| * An interface that allows the caller to get some control |
| * over the registration. |
| * @see #addMBean |
| * @see #remove |
| */ |
| public interface RegistrationContext { |
| /** |
| * Called by {@link #addMBean}. |
| * Can throw a RuntimeOperationsException to cancel the |
| * registration. |
| */ |
| public void registering(); |
| |
| /** |
| * Called by {@link #remove}. |
| * Any exception thrown by this method will be ignored. |
| */ |
| public void unregistered(); |
| } |
| |
| // Private fields --------------------------------------------> |
| |
| /** |
| * The structure for storing the objects is very basic. |
| * A Hashtable is used for storing the different domains |
| * For each domain, a hashtable contains the instances with |
| * canonical key property list string as key and named object |
| * aggregated from given object name and mbean instance as value. |
| */ |
| private final Map<String,Map<String,NamedObject>> domainTb; |
| |
| /** |
| * Number of elements contained in the Repository |
| */ |
| private volatile int nbElements = 0; |
| |
| /** |
| * Domain name of the server the repository is attached to. |
| * It is quicker to store the information in the repository rather |
| * than querying the framework each time the info is required. |
| */ |
| private final String domain; |
| |
| /** |
| * We use a global reentrant read write lock to protect the repository. |
| * This seems safer and more efficient: we are using Maps of Maps, |
| * Guaranteing consistency while using Concurent objects at each level |
| * may be more difficult. |
| **/ |
| private final ReentrantReadWriteLock lock; |
| |
| // Private fields <============================================= |
| |
| // Private methods ---------------------------------------------> |
| |
| /* This class is used to match an ObjectName against a pattern. */ |
| private final static class ObjectNamePattern { |
| private final String[] keys; |
| private final String[] values; |
| private final String properties; |
| private final boolean isPropertyListPattern; |
| private final boolean isPropertyValuePattern; |
| |
| /** |
| * The ObjectName pattern against which ObjectNames are matched. |
| **/ |
| public final ObjectName pattern; |
| |
| /** |
| * Builds a new ObjectNamePattern object from an ObjectName pattern. |
| * @param pattern The ObjectName pattern under examination. |
| **/ |
| public ObjectNamePattern(ObjectName pattern) { |
| this(pattern.isPropertyListPattern(), |
| pattern.isPropertyValuePattern(), |
| pattern.getCanonicalKeyPropertyListString(), |
| pattern.getKeyPropertyList(), |
| pattern); |
| } |
| |
| /** |
| * Builds a new ObjectNamePattern object from an ObjectName pattern |
| * constituents. |
| * @param propertyListPattern pattern.isPropertyListPattern(). |
| * @param propertyValuePattern pattern.isPropertyValuePattern(). |
| * @param canonicalProps pattern.getCanonicalKeyPropertyListString(). |
| * @param keyPropertyList pattern.getKeyPropertyList(). |
| * @param pattern The ObjectName pattern under examination. |
| **/ |
| ObjectNamePattern(boolean propertyListPattern, |
| boolean propertyValuePattern, |
| String canonicalProps, |
| Map<String,String> keyPropertyList, |
| ObjectName pattern) { |
| this.isPropertyListPattern = propertyListPattern; |
| this.isPropertyValuePattern = propertyValuePattern; |
| this.properties = canonicalProps; |
| final int len = keyPropertyList.size(); |
| this.keys = new String[len]; |
| this.values = new String[len]; |
| int i = 0; |
| for (Map.Entry<String,String> entry : keyPropertyList.entrySet()) { |
| keys[i] = entry.getKey(); |
| values[i] = entry.getValue(); |
| i++; |
| } |
| this.pattern = pattern; |
| } |
| |
| /** |
| * Return true if the given ObjectName matches the ObjectName pattern |
| * for which this object has been built. |
| * WARNING: domain name is not considered here because it is supposed |
| * not to be wildcard when called. PropertyList is also |
| * supposed not to be zero-length. |
| * @param name The ObjectName we want to match against the pattern. |
| * @return true if <code>name</code> matches the pattern. |
| **/ |
| public boolean matchKeys(ObjectName name) { |
| // If key property value pattern but not key property list |
| // pattern, then the number of key properties must be equal |
| // |
| if (isPropertyValuePattern && |
| !isPropertyListPattern && |
| (name.getKeyPropertyList().size() != keys.length)) |
| return false; |
| |
| // If key property value pattern or key property list pattern, |
| // then every property inside pattern should exist in name |
| // |
| if (isPropertyValuePattern || isPropertyListPattern) { |
| for (int i = keys.length - 1; i >= 0 ; i--) { |
| // Find value in given object name for key at current |
| // index in receiver |
| // |
| String v = name.getKeyProperty(keys[i]); |
| // Did we find a value for this key ? |
| // |
| if (v == null) return false; |
| // If this property is ok (same key, same value), go to next |
| // |
| if (isPropertyValuePattern && |
| pattern.isPropertyValuePattern(keys[i])) { |
| // wildmatch key property values |
| // values[i] is the pattern; |
| // v is the string |
| if (Util.wildmatch(v,values[i])) |
| continue; |
| else |
| return false; |
| } |
| if (v.equals(values[i])) continue; |
| return false; |
| } |
| return true; |
| } |
| |
| // If no pattern, then canonical names must be equal |
| // |
| final String p1 = name.getCanonicalKeyPropertyListString(); |
| final String p2 = properties; |
| return (p1.equals(p2)); |
| } |
| } |
| |
| /** |
| * Add all the matching objects from the given hashtable in the |
| * result set for the given ObjectNamePattern |
| * Do not check whether the domains match (only check for matching |
| * key property lists - see <i>matchKeys()</i>) |
| **/ |
| private void addAllMatching(final Map<String,NamedObject> moiTb, |
| final Set<NamedObject> result, |
| final ObjectNamePattern pattern) { |
| synchronized (moiTb) { |
| for (NamedObject no : moiTb.values()) { |
| final ObjectName on = no.getName(); |
| // if all couples (property, value) are contained |
| if (pattern.matchKeys(on)) result.add(no); |
| } |
| } |
| } |
| |
| private void addNewDomMoi(final DynamicMBean object, |
| final String dom, |
| final ObjectName name, |
| final RegistrationContext context) { |
| final Map<String,NamedObject> moiTb = |
| new HashMap<String,NamedObject>(); |
| final String key = name.getCanonicalKeyPropertyListString(); |
| addMoiToTb(object,name,key,moiTb,context); |
| domainTb.put(dom, moiTb); |
| nbElements++; |
| } |
| |
| private void registering(RegistrationContext context) { |
| if (context == null) return; |
| try { |
| context.registering(); |
| } catch (RuntimeOperationsException x) { |
| throw x; |
| } catch (RuntimeException x) { |
| throw new RuntimeOperationsException(x); |
| } |
| } |
| |
| private void unregistering(RegistrationContext context, ObjectName name) { |
| if (context == null) return; |
| try { |
| context.unregistered(); |
| } catch (Exception x) { |
| // shouldn't come here... |
| MBEANSERVER_LOGGER.log(Level.FINE, |
| "Unexpected exception while unregistering "+name, |
| x); |
| } |
| } |
| |
| private void addMoiToTb(final DynamicMBean object, |
| final ObjectName name, |
| final String key, |
| final Map<String,NamedObject> moiTb, |
| final RegistrationContext context) { |
| registering(context); |
| moiTb.put(key,new NamedObject(name, object)); |
| } |
| |
| /** |
| * Retrieves the named object contained in repository |
| * from the given objectname. |
| */ |
| private NamedObject retrieveNamedObject(ObjectName name) { |
| |
| // No patterns inside reposit |
| if (name.isPattern()) return null; |
| |
| // Extract the domain name. |
| String dom = name.getDomain().intern(); |
| |
| // Default domain case |
| if (dom.length() == 0) { |
| dom = domain; |
| } |
| |
| Map<String,NamedObject> moiTb = domainTb.get(dom); |
| if (moiTb == null) { |
| return null; // No domain containing registered object names |
| } |
| |
| return moiTb.get(name.getCanonicalKeyPropertyListString()); |
| } |
| |
| // Private methods <============================================= |
| |
| // Protected methods ---------------------------------------------> |
| |
| // Protected methods <============================================= |
| |
| // Public methods ---------------------------------------------> |
| |
| /** |
| * Construct a new repository with the given default domain. |
| */ |
| public Repository(String domain) { |
| this(domain,true); |
| } |
| |
| /** |
| * Construct a new repository with the given default domain. |
| */ |
| public Repository(String domain, boolean fairLock) { |
| lock = new ReentrantReadWriteLock(fairLock); |
| |
| domainTb = new HashMap<String,Map<String,NamedObject>>(5); |
| |
| if (domain != null && domain.length() != 0) |
| this.domain = domain.intern(); // we use == domain later on... |
| else |
| this.domain = ServiceName.DOMAIN; |
| |
| // Creates a new hashtable for the default domain |
| domainTb.put(this.domain, new HashMap<String,NamedObject>()); |
| } |
| |
| /** |
| * Returns the list of domains in which any MBean is currently |
| * registered. |
| * |
| */ |
| public String[] getDomains() { |
| |
| lock.readLock().lock(); |
| final List<String> result; |
| try { |
| // Temporary list |
| result = new ArrayList<String>(domainTb.size()); |
| for (Map.Entry<String,Map<String,NamedObject>> entry : |
| domainTb.entrySet()) { |
| // Skip domains that are in the table but have no |
| // MBean registered in them |
| // in particular the default domain may be like this |
| Map<String,NamedObject> t = entry.getValue(); |
| if (t != null && t.size() != 0) |
| result.add(entry.getKey()); |
| } |
| } finally { |
| lock.readLock().unlock(); |
| } |
| |
| // Make an array from result. |
| return result.toArray(new String[result.size()]); |
| } |
| |
| /** |
| * Stores an MBean associated with its object name in the repository. |
| * |
| * @param object MBean to be stored in the repository. |
| * @param name MBean object name. |
| * @param context A registration context. If non null, the repository |
| * will call {@link RegistrationContext#registering() |
| * context.registering()} from within the repository |
| * lock, when it has determined that the {@code object} |
| * can be stored in the repository with that {@code name}. |
| * If {@link RegistrationContext#registering() |
| * context.registering()} throws an exception, the |
| * operation is abandonned, the MBean is not added to the |
| * repository, and a {@link RuntimeOperationsException} |
| * is thrown. |
| */ |
| public void addMBean(final DynamicMBean object, ObjectName name, |
| final RegistrationContext context) |
| throws InstanceAlreadyExistsException { |
| |
| if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) { |
| MBEANSERVER_LOGGER.logp(Level.FINER, Repository.class.getName(), |
| "addMBean", "name = " + name); |
| } |
| |
| // Extract the domain name. |
| String dom = name.getDomain().intern(); |
| boolean to_default_domain = false; |
| |
| // Set domain to default if domain is empty and not already set |
| if (dom.length() == 0) |
| name = Util.newObjectName(domain + name.toString()); |
| |
| // Do we have default domain ? |
| if (dom == domain) { // ES: OK (dom & domain are interned) |
| to_default_domain = true; |
| dom = domain; |
| } else { |
| to_default_domain = false; |
| } |
| |
| // Validate name for an object |
| if (name.isPattern()) { |
| throw new RuntimeOperationsException( |
| new IllegalArgumentException("Repository: cannot add mbean for " + |
| "pattern name " + name.toString())); |
| } |
| |
| lock.writeLock().lock(); |
| try { |
| // Domain cannot be JMImplementation if entry does not exist |
| if ( !to_default_domain && |
| dom.equals("JMImplementation") && |
| domainTb.containsKey("JMImplementation")) { |
| throw new RuntimeOperationsException( |
| new IllegalArgumentException( |
| "Repository: domain name cannot be JMImplementation")); |
| } |
| |
| // If domain does not already exist, add it to the hash table |
| final Map<String,NamedObject> moiTb = domainTb.get(dom); |
| if (moiTb == null) { |
| addNewDomMoi(object, dom, name, context); |
| return; |
| } else { |
| // Add instance if not already present |
| String cstr = name.getCanonicalKeyPropertyListString(); |
| NamedObject elmt= moiTb.get(cstr); |
| if (elmt != null) { |
| throw new InstanceAlreadyExistsException(name.toString()); |
| } else { |
| nbElements++; |
| addMoiToTb(object,name,cstr,moiTb,context); |
| } |
| } |
| |
| } finally { |
| lock.writeLock().unlock(); |
| } |
| } |
| |
| /** |
| * Checks whether an MBean of the name specified is already stored in |
| * the repository. |
| * |
| * @param name name of the MBean to find. |
| * |
| * @return true if the MBean is stored in the repository, |
| * false otherwise. |
| */ |
| public boolean contains(ObjectName name) { |
| if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) { |
| MBEANSERVER_LOGGER.logp(Level.FINER, Repository.class.getName(), |
| "contains", " name = " + name); |
| } |
| lock.readLock().lock(); |
| try { |
| return (retrieveNamedObject(name) != null); |
| } finally { |
| lock.readLock().unlock(); |
| } |
| } |
| |
| /** |
| * Retrieves the MBean of the name specified from the repository. The |
| * object name must match exactly. |
| * |
| * @param name name of the MBean to retrieve. |
| * |
| * @return The retrieved MBean if it is contained in the repository, |
| * null otherwise. |
| */ |
| public DynamicMBean retrieve(ObjectName name) { |
| if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) { |
| MBEANSERVER_LOGGER.logp(Level.FINER, Repository.class.getName(), |
| "retrieve", "name = " + name); |
| } |
| |
| // Calls internal retrieve method to get the named object |
| lock.readLock().lock(); |
| try { |
| NamedObject no = retrieveNamedObject(name); |
| if (no == null) return null; |
| else return no.getObject(); |
| } finally { |
| lock.readLock().unlock(); |
| } |
| } |
| |
| /** |
| * Selects and retrieves the list of MBeans whose names match the specified |
| * object name pattern and which match the specified query expression |
| * (optionally). |
| * |
| * @param pattern The name of the MBean(s) to retrieve - may be a specific |
| * object or a name pattern allowing multiple MBeans to be selected. |
| * @param query query expression to apply when selecting objects - this |
| * parameter will be ignored when the Repository Service does not |
| * support filtering. |
| * |
| * @return The list of MBeans selected. There may be zero, one or many |
| * MBeans returned in the set. |
| */ |
| public Set<NamedObject> query(ObjectName pattern, QueryExp query) { |
| |
| final Set<NamedObject> result = new HashSet<NamedObject>(); |
| |
| // The following filter cases are considered: |
| // null, "", "*:*" : names in all domains |
| // ":*", ":[key=value],*" : names in defaultDomain |
| // "domain:*", "domain:[key=value],*" : names in the specified domain |
| |
| // Surely one of the most frequent cases ... query on the whole world |
| ObjectName name; |
| if (pattern == null || |
| pattern.getCanonicalName().length() == 0 || |
| pattern.equals(ObjectName.WILDCARD)) |
| name = ObjectName.WILDCARD; |
| else name = pattern; |
| |
| lock.readLock().lock(); |
| try { |
| |
| // If pattern is not a pattern, retrieve this mbean ! |
| if (!name.isPattern()) { |
| final NamedObject no = retrieveNamedObject(name); |
| if (no != null) result.add(no); |
| return result; |
| } |
| |
| // All names in all domains |
| if (name == ObjectName.WILDCARD) { |
| for (Map<String,NamedObject> moiTb : domainTb.values()) { |
| result.addAll(moiTb.values()); |
| } |
| return result; |
| } |
| |
| final String canonical_key_property_list_string = |
| name.getCanonicalKeyPropertyListString(); |
| final boolean allNames = |
| (canonical_key_property_list_string.length()==0); |
| final ObjectNamePattern namePattern = |
| (allNames?null:new ObjectNamePattern(name)); |
| |
| // All names in default domain |
| if (name.getDomain().length() == 0) { |
| final Map<String,NamedObject> moiTb = domainTb.get(domain); |
| if (allNames) |
| result.addAll(moiTb.values()); |
| else |
| addAllMatching(moiTb, result, namePattern); |
| return result; |
| } |
| |
| if (!name.isDomainPattern()) { |
| final Map<String,NamedObject> moiTb = domainTb.get(name.getDomain()); |
| if (moiTb == null) return Collections.emptySet(); |
| if (allNames) |
| result.addAll(moiTb.values()); |
| else |
| addAllMatching(moiTb, result, namePattern); |
| return result; |
| } |
| |
| // Pattern matching in the domain name (*, ?) |
| final String dom2Match = name.getDomain(); |
| for (String dom : domainTb.keySet()) { |
| if (Util.wildmatch(dom, dom2Match)) { |
| final Map<String,NamedObject> moiTb = domainTb.get(dom); |
| if (allNames) |
| result.addAll(moiTb.values()); |
| else |
| addAllMatching(moiTb, result, namePattern); |
| } |
| } |
| return result; |
| } finally { |
| lock.readLock().unlock(); |
| } |
| } |
| |
| /** |
| * Removes an MBean from the repository. |
| * |
| * @param name name of the MBean to remove. |
| * @param context A registration context. If non null, the repository |
| * will call {@link RegistrationContext#unregistered() |
| * context.unregistered()} from within the repository |
| * lock, just after the mbean associated with |
| * {@code name} is removed from the repository. |
| * If {@link RegistrationContext#unregistered() |
| * context.unregistered()} is not expected to throw any |
| * exception. If it does, the exception is logged |
| * and swallowed. |
| * |
| * @exception InstanceNotFoundException The MBean does not exist in |
| * the repository. |
| */ |
| public void remove(final ObjectName name, |
| final RegistrationContext context) |
| throws InstanceNotFoundException { |
| |
| // Debugging stuff |
| if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) { |
| MBEANSERVER_LOGGER.logp(Level.FINER, Repository.class.getName(), |
| "remove", "name = " + name); |
| } |
| |
| // Extract domain name. |
| String dom= name.getDomain().intern(); |
| |
| // Default domain case |
| if (dom.length() == 0) dom = domain; |
| |
| lock.writeLock().lock(); |
| try { |
| // Find the domain subtable |
| final Map<String,NamedObject> moiTb = domainTb.get(dom); |
| if (moiTb == null) { |
| throw new InstanceNotFoundException(name.toString()); |
| } |
| |
| // Remove the corresponding element |
| if (moiTb.remove(name.getCanonicalKeyPropertyListString())==null) { |
| throw new InstanceNotFoundException(name.toString()); |
| } |
| |
| // We removed it ! |
| nbElements--; |
| |
| // No more object for this domain, we remove this domain hashtable |
| if (moiTb.isEmpty()) { |
| domainTb.remove(dom); |
| |
| // set a new default domain table (always present) |
| // need to reinstantiate a hashtable because of possible |
| // big buckets array size inside table, never cleared, |
| // thus the new ! |
| if (dom == domain) // ES: OK dom and domain are interned. |
| domainTb.put(domain, new HashMap<String,NamedObject>()); |
| } |
| |
| unregistering(context,name); |
| |
| } finally { |
| lock.writeLock().unlock(); |
| } |
| } |
| |
| /** |
| * Gets the number of MBeans stored in the repository. |
| * |
| * @return Number of MBeans. |
| */ |
| public Integer getCount() { |
| return nbElements; |
| } |
| |
| /** |
| * Gets the name of the domain currently used by default in the |
| * repository. |
| * |
| * @return A string giving the name of the default domain name. |
| */ |
| public String getDefaultDomain() { |
| return domain; |
| } |
| |
| // Public methods <============================================= |
| } |