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