/* * @(#)RepositorySupport.java 1.65 03/12/19 * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package com.sun.jmx.mbeanserver; // java import import java.util.Hashtable; import java.util.Enumeration; import java.util.Set; import java.util.ArrayList; import java.util.HashSet; import java.util.Vector; // RI import import javax.management.* ; import com.sun.jmx.defaults.ServiceName; import com.sun.jmx.trace.Trace; /** * The RepositorySupport implements the Repository interface. * This repository does not support persistency. * * @since 1.5 */ public class RepositorySupport implements Repository { // Private fields --------------------------------------------> /** * An object name for query describing the whole set of mbeans. * Optimization helper for queries. */ private final static ObjectName _WholeWordQueryObjectName; static { try { _WholeWordQueryObjectName = new ObjectName("*:*"); } catch (MalformedObjectNameException e) { throw new UnsupportedOperationException(e.getMessage()); } } /** * two int utilities to minimize wildmatch method stack frame overhead * during recursions. */ private static int _slen; private static int _plen; /** * 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 Hashtable domainTb; /** * Number of elements contained in the Repository */ private 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; /** The name of this class to be used for tracing */ private final static String dbgTag = "Repository"; // Private fields <============================================= // Private methods ---------------------------------------------> // TRACES & DEBUG //--------------- private final static boolean isTraceOn() { return Trace.isSelected(Trace.LEVEL_TRACE, Trace.INFO_MBEANSERVER); } private final static void trace(String clz, String func, String info) { Trace.send(Trace.LEVEL_TRACE, Trace.INFO_MBEANSERVER, clz, func, info); } private final static void trace(String func, String info) { trace(dbgTag, func, info); } private final static boolean isDebugOn() { return Trace.isSelected(Trace.LEVEL_DEBUG, Trace.INFO_MBEANSERVER); } private final static void debug(String clz, String func, String info) { Trace.send(Trace.LEVEL_DEBUG, Trace.INFO_MBEANSERVER, clz, func, info); } private final static void debug(String func, String info) { debug(dbgTag, func, info); } /* This class is used to match an ObjectName against a pattern. */ private final static class ObjectNamePattern { private final char[] domain; private final String[] keys; private final String[] values; private final String properties; private final boolean isPropertyPattern; /** * 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.isPattern(),pattern.getDomain(), pattern.isPropertyPattern(), pattern.getCanonicalKeyPropertyListString(), pattern.getKeyPropertyList(),pattern); } /** * Builds a new ObjectNamePattern object from an ObjectName pattern * constituents. * @param domainPattern pattern.isPattern(). * @param domain pattern.getDomain(). * @param propertyPattern pattern.isPropertyPattern(). * @param canonicalProps pattern.getCanonicalKeyPropertyListString() * @param keyPropertyList pattern.getKeyPropertyList() * @param pattern The ObjectName pattern under examination. **/ ObjectNamePattern(boolean domainPattern, String domain, boolean propertyPattern, String canonicalProps, Hashtable keyPropertyList, ObjectName pattern) { final int len = (keyPropertyList==null?0:keyPropertyList.size()); final Enumeration e = (keyPropertyList==null?null:keyPropertyList.keys()); this.domain = (domain == null?null:domain.toCharArray()); this.keys = new String[len]; this.values = new String[len]; for (int i = 0 ; i < len ; i++ ) { final String k = (String)e.nextElement(); keys[i] = k; values[i] = (String)keyPropertyList.get(k); } this.properties = canonicalProps; this.isPropertyPattern = propertyPattern; 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 name matches the pattern. **/ public boolean matchKeys(ObjectName name) { if (isPropertyPattern) { // Every property inside pattern should exist in name 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 (v.equals(values[i])) continue; return false; } return true; } else { if (keys.length != name.getKeyPropertyList().size()) return false; final String p1 = name.getCanonicalKeyPropertyListString(); final String p2 = properties; // if (p1 == null) return (p2 == null); // if (p2 == null) return p1.equals(""); 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 matchKeys()) **/ private final void addAllMatching(final Hashtable moiTb, final Set result, final ObjectNamePattern pattern) { synchronized (moiTb) { for (Enumeration e = moiTb.elements(); e.hasMoreElements();) { final NamedObject no = (NamedObject) e.nextElement(); final ObjectName on = no.getName(); // if all couples (property, value) are contained if (pattern.matchKeys(on)) result.add(no); } } } private final void addNewDomMoi(final Object object, final String dom, final ObjectName name) { final Hashtable moiTb= new Hashtable(); domainTb.put(dom, moiTb); moiTb.put(name.getCanonicalKeyPropertyListString(), new NamedObject(name, object)); nbElements++; } /* * Tests whether string s is matched by pattern p. * Supports "?", "*" each of which may be escaped with "\"; * Not yet supported: internationalization; "\" inside brackets.

* Wildcard matching routine by Karl Heuer. Public Domain.

*/ private static boolean wildmatch(char[] s, char[] p, int si, int pi) { char c; // Be careful: this is dangerous: it works because wildmatch // is protected by a synchronized block on domainTb _slen = s.length; _plen = p.length; // end of comment. while (pi < _plen) { // While still string c = p[pi++]; if (c == '?') { if (++si > _slen) return false; } else if (c == '*') { // Wildcard if (pi >= _plen) return true; do { if (wildmatch(s,p,si,pi)) return true; } while (++si < _slen); return false; } else { if (si >= _slen || c != s[si++]) return false; } } return (si == _slen); } /** * Retrieves the named object contained in repository * from the given objectname. */ private NamedObject retrieveNamedObject(ObjectName name) { // No patterns inside reposit if (name.isPattern() == true) return null; // Extract the domain name. String dom= name.getDomain().intern(); // Default domain case if (dom.length() == 0) { dom = domain; } Object tmp_object = domainTb.get(dom); if (tmp_object == null) { return null; // No domain containing registered object names } // If name exists in repository, we will get it now Hashtable moiTb= (Hashtable) tmp_object; Object o = moiTb.get(name.getCanonicalKeyPropertyListString()); if (o != null ) { return (NamedObject) o; } else return null; } // Private methods <============================================= // Protected methods ---------------------------------------------> // Protected methods <============================================= // Public methods ---------------------------------------------> /** * Construct a new repository with the given default domain. * */ public RepositorySupport(String domain) { domainTb= new Hashtable(5); if (domain != null && domain.length() != 0) this.domain = domain; else this.domain = ServiceName.DOMAIN; // Creates an new hastable for the default domain domainTb.put(this.domain.intern(), new Hashtable()); // ------------------------------ // ------------------------------ } /** * The purpose of this method is to provide a unified way to provide * whatever configuration information is needed by the specific * underlying implementation of the repository. * * @param configParameters An list containing the configuration * parameters needed by the specific Repository Service * implementation. */ public void setConfigParameters(ArrayList configParameters) { return; } /** * Returns the list of domains in which any MBean is currently * registered. * * @since.unbundled JMX RI 1.2 */ public String[] getDomains() { final ArrayList result; synchronized(domainTb) { // Temporary list result = new ArrayList(domainTb.size()); // Loop over all domains for (Enumeration e = domainTb.keys();e.hasMoreElements();) { // key = domain name final String key = (String)e.nextElement(); if (key == null) continue; // If no MBean in domain continue final Hashtable t = (Hashtable)domainTb.get(key); if (t == null || t.size()==0) continue; // Some MBean are registered => add to result. result.add(key); } } // Make an array from result. return (String[]) result.toArray(new String[result.size()]); } /** * Indicates whether or not the Repository Service supports filtering. If * the Repository Service does not support filtering, the MBean Server * will perform filtering. * * @return true if filtering is supported, false otherwise. */ public boolean isFiltering() { // Let the MBeanServer perform the filtering ! return false; } /** * 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. * */ public void addMBean(final Object object, ObjectName name) throws InstanceAlreadyExistsException { if (isTraceOn()) { trace("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) { try { name = new ObjectName(domain + name.toString()); } catch (MalformedObjectNameException e) { if (isDebugOn()) { debug("addMBean", "Unexpected MalformedObjectNameException"); } } } // Do we have default domain ? if (dom == domain) { to_default_domain = true; dom = domain; } else { to_default_domain = false; } // ------------------------------ // ------------------------------ // Validate name for an object if (name.isPattern() == true) { throw new RuntimeOperationsException( new IllegalArgumentException("Repository: cannot add mbean for pattern name " + name.toString())); } // Domain cannot be JMImplementation if entry does not exists if ( !to_default_domain && dom.equals("JMImplementation") && domainTb.containsKey("JMImplementation")) { throw new RuntimeOperationsException( new IllegalArgumentException( "Repository: domain name cannot be JMImplementation")); } // If domain not already exists, add it to the hash table final Hashtable moiTb= (Hashtable) domainTb.get(dom); if (moiTb == null) { addNewDomMoi(object, dom, name); return; } else { // Add instance if not already present String cstr = name.getCanonicalKeyPropertyListString(); Object elmt= moiTb.get(cstr); if (elmt != null) { throw new InstanceAlreadyExistsException(name.toString()); } else { nbElements++; moiTb.put(cstr, new NamedObject(name, object)); } } } /** * 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 (isTraceOn()) { trace("contains", "name=" + name); } return (retrieveNamedObject(name) != null); } /** * 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 Object retrieve(ObjectName name) { // ------------------------------ // ------------------------------ if (isTraceOn()) { trace("retrieve", "name=" + name); } // Calls internal retrieve method to get the named object NamedObject no = retrieveNamedObject(name); if (no == null) return null; else return no.getObject(); } /** * 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 query(ObjectName pattern, QueryExp query) { // ------------------------------ // ------------------------------ ObjectNamePattern on_pattern = null; // intermediate Object name pattern for performance final HashSet result = new HashSet(); // The following filter cases are considered : // null, "", "*:*"" : names in all domains // ":*" : names in defaultDomain // "domain:*" : names in the specified domain // "domain:[key=value], *" // Surely one of the most frequent case ... query on the whole world ObjectName name = null; if (pattern == null || pattern.getCanonicalName().length() == 0 || pattern.equals(_WholeWordQueryObjectName)) name = _WholeWordQueryObjectName; else name = pattern; // 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 == _WholeWordQueryObjectName) { synchronized(domainTb) { for(final Enumeration e = domainTb.elements(); e.hasMoreElements();) { final Hashtable moiTb = (Hashtable) e.nextElement(); result.addAll(moiTb.values()); } } return result; } String canonical_key_property_list_string = name.getCanonicalKeyPropertyListString(); // all names in default domain // // DF: fix 4618986 - take into account the case where the // property list is not empty. // if (name.getDomain().length() == 0) { final Hashtable moiTb = (Hashtable) domainTb.get(domain); if (canonical_key_property_list_string.length() == 0) { result.addAll(moiTb.values()); } else { if (on_pattern == null) on_pattern = new ObjectNamePattern(name); addAllMatching(moiTb,result,on_pattern); } return result; } // Pattern matching in the domain name (*, ?) synchronized (domainTb) { char[] dom2Match = name.getDomain().toCharArray(); String nextDomain; char [] theDom; for (final Enumeration enumi = domainTb.keys(); enumi.hasMoreElements();) { nextDomain = (String) enumi.nextElement(); theDom = nextDomain.toCharArray(); if (wildmatch(theDom, dom2Match, 0, 0)) { final Hashtable moiTb = (Hashtable) domainTb.get(nextDomain); if (canonical_key_property_list_string.length() == 0) result.addAll(moiTb.values()); else { if (on_pattern == null) on_pattern = new ObjectNamePattern(name); addAllMatching(moiTb,result,on_pattern); } } } } return result; } /** * Removes an MBean from the repository. * * @param name name of the MBean to remove. * * @exception InstanceNotFoundException The MBean does not exist in * the repository. */ public void remove(final ObjectName name) throws InstanceNotFoundException { // Debugging stuff if (isTraceOn()) { trace("remove", "name=" + name); } // Extract domain name. String dom= name.getDomain().intern(); // Default domain case if (dom.length() == 0) dom = domain; // Find the domain subtable Object tmp_object = domainTb.get(dom); if (tmp_object == null) { throw new InstanceNotFoundException(name.toString()); } // Remove the corresponding element Hashtable moiTb= (Hashtable) tmp_object; 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) domainTb.put(domain, new Hashtable()); } } /** * Gets the number of MBeans stored in the repository. * * @return Number of MBeans. */ public Integer getCount() { return new Integer(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; } }