/* * 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 <============================================= }