You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
679 lines
24 KiB
679 lines
24 KiB
/*
|
|
* Copyright (c) 1999, 2008, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
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 <=============================================
|
|
}
|