Return-Path: Delivered-To: apmail-incubator-felix-commits-archive@www.apache.org Received: (qmail 70950 invoked from network); 19 Jul 2006 08:44:40 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 19 Jul 2006 08:44:40 -0000 Received: (qmail 39642 invoked by uid 500); 19 Jul 2006 08:44:38 -0000 Delivered-To: apmail-incubator-felix-commits-archive@incubator.apache.org Received: (qmail 39615 invoked by uid 500); 19 Jul 2006 08:44:38 -0000 Mailing-List: contact felix-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: felix-dev@incubator.apache.org Delivered-To: mailing list felix-commits@incubator.apache.org Delivered-To: moderator for felix-commits@incubator.apache.org Received: (qmail 37565 invoked by uid 99); 17 Jul 2006 12:15:09 -0000 X-ASF-Spam-Status: No, hits=-8.6 required=10.0 tests=ALL_TRUSTED,INFO_TLD,NO_REAL_NAME X-Spam-Check-By: apache.org Received-SPF: pass (asf.osuosl.org: local policy) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r422696 [3/4] - in /incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/felix/ src/main/java/org/apache/felix/mosgi/ src/main/java/or... Date: Mon, 17 Jul 2006 12:14:33 -0000 To: felix-commits@incubator.apache.org From: sfrenot@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20060717121440.992A41A9824@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Added: incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/MX4JMBeanServer.java URL: http://svn.apache.org/viewvc/incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/MX4JMBeanServer.java?rev=422696&view=auto ============================================================================== --- incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/MX4JMBeanServer.java (added) +++ incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/MX4JMBeanServer.java Mon Jul 17 05:14:31 2006 @@ -0,0 +1,1489 @@ +/* + * Copyright (C) MX4J. + * All rights reserved. + * + * This software is distributed under the terms of the MX4J License version 1.0. + * See the terms of the MX4J License in the documentation provided with this software. + */ +/* + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.felix.mosgi.jmx.agent.mx4j.server; + + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import javax.management.Attribute; +import javax.management.AttributeList; +import javax.management.AttributeNotFoundException; +import javax.management.BadAttributeValueExpException; +import javax.management.BadBinaryOpValueExpException; +import javax.management.BadStringOperationException; +import javax.management.InstanceAlreadyExistsException; +import javax.management.InstanceNotFoundException; +import javax.management.IntrospectionException; +import javax.management.InvalidApplicationException; +import javax.management.InvalidAttributeValueException; +import javax.management.JMRuntimeException; +import javax.management.ListenerNotFoundException; +import javax.management.MBeanException; +import javax.management.MBeanInfo; +import javax.management.MBeanPermission; +import javax.management.MBeanRegistrationException; +import javax.management.MBeanServer; +import javax.management.MBeanServerDelegate; +import javax.management.MBeanServerNotification; +import javax.management.MBeanServerPermission; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.NotificationBroadcaster; +import javax.management.NotificationEmitter; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; +import javax.management.ObjectInstance; +import javax.management.ObjectName; +import javax.management.OperationsException; +import javax.management.QueryExp; +import javax.management.ReflectionException; +import javax.management.RuntimeErrorException; +import javax.management.RuntimeOperationsException; +import javax.management.StandardMBean; +import javax.management.loading.ClassLoaderRepository; +import javax.management.loading.PrivateClassLoader; + +import org.apache.felix.mosgi.jmx.agent.mx4j.ImplementationException; +import org.apache.felix.mosgi.jmx.agent.mx4j.MX4JSystemKeys; +import org.apache.felix.mosgi.jmx.agent.mx4j.loading.ClassLoaderObjectInputStream; +import org.apache.felix.mosgi.jmx.agent.mx4j.log.Log; +import org.apache.felix.mosgi.jmx.agent.mx4j.log.Logger; +import org.apache.felix.mosgi.jmx.agent.mx4j.server.interceptor.InvokerMBeanServerInterceptor; +import org.apache.felix.mosgi.jmx.agent.mx4j.server.interceptor.MBeanServerInterceptor; +import org.apache.felix.mosgi.jmx.agent.mx4j.server.interceptor.MBeanServerInterceptorConfigurator; +import org.apache.felix.mosgi.jmx.agent.mx4j.util.Utils; + +/** + * The MX4J MBeanServer implementation.
+ * The MBeanServer accomplishes these roles: + *
    + *
  • Returns information about the Agent + *
  • Acts as a repository for MBeans + *
  • Acts as an invoker, on behalf of the user, on MBeans + *
+ *
+ * The repository function is delegated to instances of {@link MBeanRepository} classes. + * This class acts as a factory for MBeanRepository instances, that can be controlled via the system property + * {@link mx4j.MX4JSystemKeys#MX4J_MBEANSERVER_REPOSITORY} to the qualified name of the implementation class.
+ * + * This class also acts as an invoker on MBeans. The architecture is interceptor-based, that is whenever you call + * from a client an MBeanServer method that will end up to call the MBean instance, the call is dispatched to + * the interceptor chain and eventually to the MBean.
+ * The interceptors are configurable via the MBean {@link MBeanServerInterceptorConfigurator}. + * When the call is about to arrive to the MBean instance, the last interceptor dispatches the call depending on + * the MBean type: if the MBean is a dynamic MBean, the call is dispatched directly; if the MBean is a standard + * MBean an {@link MBeanInvoker} is delegated to invoke on the MBean instance. + * + * @author Simone Bordet + * @version $Revision: 1.3 $ + */ +public class MX4JMBeanServer implements MBeanServer, java.io.Serializable +{ + private String defaultDomain; + private MBeanRepository mbeanRepository; + private MBeanServerDelegate delegate; + private ObjectName delegateName; + private MBeanIntrospector introspector; + private MBeanServerInterceptorConfigurator invoker; + private static long notifications; + private ModifiableClassLoaderRepository classLoaderRepository; + private Map domains = new HashMap(); + + private static final String[] EMPTY_PARAMS = new String[0]; + private static final Object[] EMPTY_ARGS = new Object[0]; + + /** + * Create a new MBeanServer implementation with the specified default domain. + * If the default domain is null, then the empty string is assumed. + * + * @param defaultDomain The default domain to be used + * @throws SecurityException if access is not granted to create an MBeanServer instance + */ + public MX4JMBeanServer(String defaultDomain, MBeanServer outer, MBeanServerDelegate delegate) + { + Logger logger = getLogger(); + if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Creating MBeanServer instance..."); + + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + { + if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Checking permission to create MBeanServer..."); + sm.checkPermission(new MBeanServerPermission("newMBeanServer")); + } + + if (defaultDomain == null) defaultDomain = ""; + this.defaultDomain = defaultDomain; + + if (delegate==null) throw new JMRuntimeException("Delegate can't be null"); + this.delegate = delegate; + + if (logger.isEnabledFor(Logger.TRACE)) logger.trace("MBeanServer default domain is: '" + this.defaultDomain + "'"); + + mbeanRepository = createMBeanRepository(); + + classLoaderRepository = createClassLoaderRepository(); + // JMX 1.2 requires the CLR to have as first entry the classloader of this class + classLoaderRepository.addClassLoader(getClass().getClassLoader()); + + introspector = new MBeanIntrospector(); + + // This is the official name of the delegate, it is used as a source for MBeanServerNotifications + try + { + delegateName = new ObjectName("JMImplementation", "type", "MBeanServerDelegate"); + } + catch (MalformedObjectNameException ignored) + { + } + + try + { + ObjectName invokerName = new ObjectName(MBeanServerInterceptorConfigurator.OBJECT_NAME); + invoker = new MBeanServerInterceptorConfigurator(this); + +// ContextClassLoaderMBeanServerInterceptor ccl = new ContextClassLoaderMBeanServerInterceptor(); +// NotificationListenerMBeanServerInterceptor notif = new NotificationListenerMBeanServerInterceptor(); +// SecurityMBeanServerInterceptor sec = new SecurityMBeanServerInterceptor(); + InvokerMBeanServerInterceptor inv = new InvokerMBeanServerInterceptor(outer==null ? this : outer); + +// invoker.addPreInterceptor(ccl); +// invoker.addPreInterceptor(notif); +// invoker.addPostInterceptor(sec); + invoker.addPostInterceptor(inv); + + invoker.start(); + + // The interceptor stack is in place, register the configurator and all interceptors + privilegedRegisterMBean(invoker, invokerName); + +// ObjectName cclName = new ObjectName("JMImplementation", "interceptor", "contextclassloader"); +// ObjectName notifName = new ObjectName("JMImplementation", "interceptor", "notificationwrapper"); +// ObjectName secName = new ObjectName("JMImplementation", "interceptor", "security"); + ObjectName invName = new ObjectName("JMImplementation", "interceptor", "invoker"); + +// privilegedRegisterMBean(ccl, cclName); +// privilegedRegisterMBean(notif, notifName); +// privilegedRegisterMBean(sec, secName); + privilegedRegisterMBean(inv, invName); + } + catch (Exception x) + { + logger.error("MBeanServerInterceptorConfigurator cannot be registered", x); + throw new ImplementationException(); + } + + // Now register the delegate + try + { + privilegedRegisterMBean(delegate, delegateName); + } + catch (Exception x) + { + logger.error("MBeanServerDelegate cannot be registered", x); + throw new ImplementationException(x.toString()); + } + + if (logger.isEnabledFor(Logger.TRACE)) logger.trace("MBeanServer instance created successfully"); + } + + /** + * Returns the ClassLoaderRepository for this MBeanServer. + * When first the ClassLoaderRepository is created in the constructor, the system property + * {@link mx4j.MX4JSystemKeys#MX4J_MBEANSERVER_CLASSLOADER_REPOSITORY} is tested; + * if it is non-null and defines a subclass of + * {@link ModifiableClassLoaderRepository}, then that class is used instead of the default one. + */ + public ClassLoaderRepository getClassLoaderRepository() + { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + { + sm.checkPermission(new MBeanPermission("-#-[-]", "getClassLoaderRepository")); + } + + return getModifiableClassLoaderRepository(); + } + + private ModifiableClassLoaderRepository getModifiableClassLoaderRepository() + { + return classLoaderRepository; + } + + public ClassLoader getClassLoader(ObjectName name) throws InstanceNotFoundException + { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + { + name = secureObjectName(name); + + if (name == null) + { + sm.checkPermission(new MBeanPermission("-#-[-]", "getClassLoader")); + } + else + { + MBeanMetaData metadata = findMBeanMetaData(name); + sm.checkPermission(new MBeanPermission(metadata.info.getClassName(), "-", name, "getClassLoader")); + } + } + + return getClassLoaderImpl(name); + } + + public ClassLoader getClassLoaderFor(ObjectName name) throws InstanceNotFoundException + { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + { + name = secureObjectName(name); + } + + // If name is null, I get InstanceNotFoundException + MBeanMetaData metadata = findMBeanMetaData(name); + + if (sm != null) + { + sm.checkPermission(new MBeanPermission(metadata.info.getClassName(), "-", name, "getClassLoaderFor")); + } + + return metadata.mbean.getClass().getClassLoader(); + } + + /** + * Returns the MBean classloader corrispondent to the given ObjectName. + * If name is null, the classloader of this class is returned. + */ + private ClassLoader getClassLoaderImpl(ObjectName name) throws InstanceNotFoundException + { + if (name == null) + { + return getClass().getClassLoader(); + } + else + { + MBeanMetaData metadata = findMBeanMetaData(name); + if (metadata.mbean instanceof ClassLoader) + { + return (ClassLoader)metadata.mbean; + } + else + { + throw new InstanceNotFoundException(name.getCanonicalName()); + } + } + } + + public ObjectInputStream deserialize(String className, ObjectName loaderName, byte[] bytes) + throws InstanceNotFoundException, OperationsException, ReflectionException + { + if (className == null || className.trim().length() == 0) + { + throw new RuntimeOperationsException(new IllegalArgumentException("Invalid class name '" + className + "'")); + } + + ClassLoader cl = getClassLoader(loaderName); + + try + { + Class cls = cl.loadClass(className); + return deserializeImpl(cls.getClassLoader(), bytes); + } + catch (ClassNotFoundException x) + { + throw new ReflectionException(x); + } + } + + public ObjectInputStream deserialize(String className, byte[] bytes) + throws OperationsException, ReflectionException + { + if (className == null || className.trim().length() == 0) + { + throw new RuntimeOperationsException(new IllegalArgumentException("Invalid class name '" + className + "'")); + } + + // Find the classloader that can load the given className using the ClassLoaderRepository + try + { + Class cls = getClassLoaderRepository().loadClass(className); + return deserializeImpl(cls.getClassLoader(), bytes); + } + catch (ClassNotFoundException x) + { + throw new ReflectionException(x); + } + } + + public ObjectInputStream deserialize(ObjectName objectName, byte[] bytes) + throws InstanceNotFoundException, OperationsException + { + ClassLoader cl = getClassLoaderFor(objectName); + return deserializeImpl(cl, bytes); + } + + /** + * Deserializes the given bytes using the specified classloader. + */ + private ObjectInputStream deserializeImpl(ClassLoader classloader, byte[] bytes) throws OperationsException + { + if (bytes == null || bytes.length == 0) + { + throw new RuntimeOperationsException(new IllegalArgumentException("Invalid byte array " + bytes)); + } + + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + try + { + return new ClassLoaderObjectInputStream(bais, classloader); + } + catch (IOException x) + { + throw new OperationsException(x.toString()); + } + } + + private MBeanServerInterceptor getHeadInterceptor() + { + MBeanServerInterceptor head = invoker.getHeadInterceptor(); + + if (head == null) throw new IllegalStateException("No MBeanServer interceptor, probably the configurator has been stopped"); + + return head; + } + + private Logger getLogger() + { + return Log.getLogger(getClass().getName()); + } + + /** + * Creates a new repository for MBeans. + * The system property {@link mx4j.MX4JSystemKeys#MX4J_MBEANSERVER_REPOSITORY} is tested + * for a full qualified name of a class implementing the {@link MBeanRepository} interface. + * In case the system property is not defined or the class is not loadable or instantiable, a default + * implementation is returned. + */ + private MBeanRepository createMBeanRepository() + { + Logger logger = getLogger(); + + if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Checking for system property " + MX4JSystemKeys.MX4J_MBEANSERVER_REPOSITORY); + + String value = (String)AccessController.doPrivileged(new PrivilegedAction() + { + public Object run() + { + return System.getProperty(MX4JSystemKeys.MX4J_MBEANSERVER_REPOSITORY); + } + }); + + if (value != null) + { + if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Property found for custom MBeanServer registry; class is: " + value); + + try + { + MBeanRepository registry = (MBeanRepository)Thread.currentThread().getContextClassLoader().loadClass(value).newInstance(); + if (logger.isEnabledFor(Logger.TRACE)) + { + logger.trace("Custom MBeanServer registry created successfully"); + } + return registry; + } + catch (Exception x) + { + if (logger.isEnabledFor(Logger.TRACE)) + { + logger.trace("Custom MBeanServer registry could not be created", x); + } + } + } + + return new DefaultMBeanRepository(); + } + + /** + * Creates a new ClassLoaderRepository for ClassLoader MBeans. + * The system property {@link mx4j.MX4JSystemKeys#MX4J_MBEANSERVER_CLASSLOADER_REPOSITORY} + * is tested for a full qualified name of a class + * extending the {@link ModifiableClassLoaderRepository} class. + * In case the system property is not defined or the class is not loadable or instantiable, a default + * implementation is returned. + */ + private ModifiableClassLoaderRepository createClassLoaderRepository() + { + Logger logger = getLogger(); + + if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Checking for system property " + MX4JSystemKeys.MX4J_MBEANSERVER_CLASSLOADER_REPOSITORY); + + String value = (String)AccessController.doPrivileged(new PrivilegedAction() + { + public Object run() + { + return System.getProperty(MX4JSystemKeys.MX4J_MBEANSERVER_CLASSLOADER_REPOSITORY); + } + }); + + if (value != null) + { + if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Property found for custom ClassLoaderRepository; class is: " + value); + + try + { + ModifiableClassLoaderRepository repository = (ModifiableClassLoaderRepository)Thread.currentThread().getContextClassLoader().loadClass(value).newInstance(); + if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Custom ClassLoaderRepository created successfully " + repository); + return repository; + } + catch (Exception x) + { + if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Custom ClassLoaderRepository could not be created", x); + } + } + return new DefaultClassLoaderRepository(); + } + + /** + * Returns the repository of MBeans for this MBeanServer + */ + private MBeanRepository getMBeanRepository() + { + return mbeanRepository; + } + + /** + * Looks up the metadata associated with the given ObjectName. + * @throws InstanceNotFoundException if the given ObjectName is not a registered MBean + */ + private MBeanMetaData findMBeanMetaData(ObjectName objectName) throws InstanceNotFoundException + { + MBeanMetaData metadata = null; + if (objectName != null) + { + objectName = normalizeObjectName(objectName); + + MBeanRepository repository = getMBeanRepository(); + synchronized (repository) + { + metadata = repository.get(objectName); + } + } + if (metadata == null) + { + throw new InstanceNotFoundException("MBeanServer cannot find MBean with ObjectName " + objectName); + } + return metadata; + } + + public void addNotificationListener(ObjectName observed, ObjectName listener, NotificationFilter filter, Object handback) + throws InstanceNotFoundException + { + listener = secureObjectName(listener); + + Object mbean = findMBeanMetaData(listener).mbean; + if (!(mbean instanceof NotificationListener)) + { + throw new RuntimeOperationsException(new IllegalArgumentException("MBean " + listener + " is not a NotificationListener")); + } + addNotificationListener(observed, (NotificationListener)mbean, filter, handback); + } + + public void addNotificationListener(ObjectName observed, NotificationListener listener, NotificationFilter filter, Object handback) + throws InstanceNotFoundException + { + if (listener == null) + { + throw new RuntimeOperationsException(new IllegalArgumentException("NotificationListener cannot be null")); + } + + observed = secureObjectName(observed); + + MBeanMetaData metadata = findMBeanMetaData(observed); + + Object mbean = metadata.mbean; + + if (!(mbean instanceof NotificationBroadcaster)) + { + throw new RuntimeOperationsException(new IllegalArgumentException("MBean " + observed + " is not a NotificationBroadcaster")); + } + + addNotificationListenerImpl(metadata, listener, filter, handback); + } + + private void addNotificationListenerImpl(MBeanMetaData metadata, NotificationListener listener, NotificationFilter filter, Object handback) + { + getHeadInterceptor().addNotificationListener(metadata, listener, filter, handback); + } + + public void removeNotificationListener(ObjectName observed, ObjectName listener) + throws InstanceNotFoundException, ListenerNotFoundException + { + listener = secureObjectName(listener); + + Object mbean = findMBeanMetaData(listener).mbean; + if (!(mbean instanceof NotificationListener)) + { + throw new RuntimeOperationsException(new IllegalArgumentException("MBean " + listener + " is not a NotificationListener")); + } + removeNotificationListener(observed, (NotificationListener)mbean); + } + + public void removeNotificationListener(ObjectName observed, NotificationListener listener) + throws InstanceNotFoundException, ListenerNotFoundException + { + if (listener == null) + { + throw new ListenerNotFoundException("NotificationListener cannot be null"); + } + + observed = secureObjectName(observed); + + MBeanMetaData metadata = findMBeanMetaData(observed); + Object mbean = metadata.mbean; + + if (!(mbean instanceof NotificationBroadcaster)) + { + throw new RuntimeOperationsException(new IllegalArgumentException("MBean " + observed + " is not a NotificationBroadcaster")); + } + + removeNotificationListenerImpl(metadata, listener); + } + + public void removeNotificationListener(ObjectName observed, ObjectName listener, NotificationFilter filter, Object handback) + throws InstanceNotFoundException, ListenerNotFoundException + { + listener = secureObjectName(listener); + + Object mbean = findMBeanMetaData(listener).mbean; + if (!(mbean instanceof NotificationListener)) + { + throw new RuntimeOperationsException(new IllegalArgumentException("MBean " + listener + " is not a NotificationListener")); + } + removeNotificationListener(observed, (NotificationListener)mbean, filter, handback); + } + + public void removeNotificationListener(ObjectName observed, NotificationListener listener, NotificationFilter filter, Object handback) + throws InstanceNotFoundException, ListenerNotFoundException + { + if (listener == null) + { + throw new ListenerNotFoundException("NotificationListener cannot be null"); + } + + observed = secureObjectName(observed); + + MBeanMetaData metadata = findMBeanMetaData(observed); + Object mbean = metadata.mbean; + + if (!(mbean instanceof NotificationEmitter)) + { + throw new RuntimeOperationsException(new IllegalArgumentException("MBean " + observed + " is not a NotificationEmitter")); + } + + removeNotificationListenerImpl(metadata, listener, filter, handback); + } + + private void removeNotificationListenerImpl(MBeanMetaData metadata, NotificationListener listener) + throws ListenerNotFoundException + { + getHeadInterceptor().removeNotificationListener(metadata, listener); + } + + private void removeNotificationListenerImpl(MBeanMetaData metadata, NotificationListener listener, NotificationFilter filter, Object handback) + throws ListenerNotFoundException + { + getHeadInterceptor().removeNotificationListener(metadata, listener, filter, handback); + } + + public Object instantiate(String className) + throws ReflectionException, MBeanException + { + return instantiate(className, null, null); + } + + public Object instantiate(String className, Object[] args, String[] parameters) + throws ReflectionException, MBeanException + { + if (className == null || className.trim().length() == 0) + { + throw new RuntimeOperationsException(new IllegalArgumentException("Class name cannot be null or empty")); + } + + try + { + Class cls = getModifiableClassLoaderRepository().loadClass(className); + return instantiateImpl(className, cls.getClassLoader(), null, parameters, args).mbean; + } + catch (ClassNotFoundException x) + { + throw new ReflectionException(x); + } + } + + public Object instantiate(String className, ObjectName loaderName) + throws ReflectionException, MBeanException, InstanceNotFoundException + { + return instantiate(className, loaderName, null, null); + } + + public Object instantiate(String className, ObjectName loaderName, Object[] args, String[] parameters) + throws ReflectionException, MBeanException, InstanceNotFoundException + { + if (className == null || className.trim().length() == 0) + { + throw new RuntimeOperationsException(new IllegalArgumentException("Class name cannot be null or empty")); + } + + // loaderName can be null: means using this class' ClassLoader + + loaderName = secureObjectName(loaderName); + if (loaderName != null && loaderName.isPattern()) + { + throw new RuntimeOperationsException(new IllegalArgumentException("ObjectName for the ClassLoader cannot be a pattern ObjectName: " + loaderName)); + } + + ClassLoader cl = getClassLoaderImpl(loaderName); + return instantiateImpl(className, cl, null, parameters, args).mbean; + } + + private MBeanMetaData instantiateImpl(String className, ClassLoader classloader, ObjectName name, String[] params, Object[] args) + throws ReflectionException, MBeanException + { + if (params == null) params = EMPTY_PARAMS; + if (args == null) args = EMPTY_ARGS; + + MBeanMetaData metadata = createMBeanMetaData(); + metadata.classloader = classloader; + metadata.name = secureObjectName(name); + + getHeadInterceptor().instantiate(metadata, className, params, args); + + return metadata; + } + + public ObjectInstance createMBean(String className, ObjectName objectName) + throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException + { + return createMBean(className, objectName, null, null); + } + + public ObjectInstance createMBean(String className, ObjectName objectName, Object[] args, String[] parameters) + throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException + { + try + { + Class cls = getModifiableClassLoaderRepository().loadClass(className); + MBeanMetaData metadata = instantiateImpl(className, cls.getClassLoader(), objectName, parameters, args); + + registerImpl(metadata, false); + + return metadata.instance; + } + catch (ClassNotFoundException x) + { + throw new ReflectionException(x); + } + } + + public ObjectInstance createMBean(String className, ObjectName objectName, ObjectName loaderName) + throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, InstanceNotFoundException + { + return createMBean(className, objectName, loaderName, null, null); + } + + public ObjectInstance createMBean(String className, ObjectName objectName, ObjectName loaderName, Object[] args, String[] parameters) + throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, InstanceNotFoundException + { + loaderName = secureObjectName(loaderName); + + ClassLoader cl = getClassLoaderImpl(loaderName); + + MBeanMetaData metadata = instantiateImpl(className, cl, objectName, parameters, args); + + registerImpl(metadata, false); + + return metadata.instance; + } + + public ObjectInstance registerMBean(Object mbean, ObjectName objectName) + throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException + { + return registerMBeanImpl(mbean, objectName, false); + } + + private ObjectInstance registerMBeanImpl(Object mbean, ObjectName objectName, boolean privileged) + throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException + { + if (mbean == null) + { + throw new RuntimeOperationsException(new IllegalArgumentException("MBean instance cannot be null")); + } + + MBeanMetaData metadata = createMBeanMetaData(); + metadata.mbean = mbean; + metadata.classloader = mbean.getClass().getClassLoader(); + metadata.name = secureObjectName(objectName); + + registerImpl(metadata, privileged); + + return metadata.instance; + } + + /** + * Returns a new instance of the metadata class used to store MBean information. + */ + private MBeanMetaData createMBeanMetaData() + { + return new MBeanMetaData(); + } + + /** + * This method is called only to register implementation MBeans from the constructor. + * Since to create an instance of this class already requires a permission, here we hide the registration + * of implementation MBeans to the client that thus need no further permissions. + */ + private ObjectInstance privilegedRegisterMBean(final Object mbean, final ObjectName name) + throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException + { + try + { + return (ObjectInstance)AccessController.doPrivileged(new PrivilegedExceptionAction() + { + public Object run() throws Exception + { + return registerMBeanImpl(mbean, name, true); + } + }); + } + catch (PrivilegedActionException x) + { + Exception xx = x.getException(); + if (xx instanceof InstanceAlreadyExistsException) + throw (InstanceAlreadyExistsException)xx; + else if (xx instanceof MBeanRegistrationException) + throw (MBeanRegistrationException)xx; + else if (xx instanceof NotCompliantMBeanException) + throw (NotCompliantMBeanException)xx; + else + throw new MBeanRegistrationException(xx); + } + } + + private void registerImpl(MBeanMetaData metadata, boolean privileged) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException + { + introspector.introspect(metadata); + + if (!introspector.isMBeanCompliant(metadata)) throw new NotCompliantMBeanException("MBean is not compliant"); + + MBeanServerInterceptor head = getHeadInterceptor(); + + try + { + // With this call, the MBean implementor can replace the ObjectName with a subclass that is not secure, secure it again + head.registration(metadata, MBeanServerInterceptor.PRE_REGISTER); + metadata.name = secureObjectName(metadata.name); + + metadata.instance = new ObjectInstance(metadata.name, metadata.info.getClassName()); + + register(metadata, privileged); + + head.registration(metadata, MBeanServerInterceptor.POST_REGISTER_TRUE); + } + catch (Throwable x) + { + try + { + head.registration(metadata, MBeanServerInterceptor.POST_REGISTER_FALSE); + } + catch (MBeanRegistrationException ignored) + {/* Ignore this one to rethrow the other one */ + } + + if (x instanceof SecurityException) + { + throw (SecurityException)x; + } + else if (x instanceof InstanceAlreadyExistsException) + { + throw (InstanceAlreadyExistsException)x; + } + else if (x instanceof MBeanRegistrationException) + { + throw (MBeanRegistrationException)x; + } + else if (x instanceof RuntimeOperationsException) + { + throw (RuntimeOperationsException)x; + } + else if (x instanceof JMRuntimeException) + { + throw (JMRuntimeException)x; + } + else if (x instanceof Exception) + { + throw new MBeanRegistrationException((Exception)x); + } + else if (x instanceof Error) + { + throw new MBeanRegistrationException(new RuntimeErrorException((Error)x)); + } + else + { + throw new ImplementationException(); + } + } + + if (metadata.mbean instanceof ClassLoader && !(metadata.mbean instanceof PrivateClassLoader)) + { + ClassLoader cl = (ClassLoader)metadata.mbean; + getModifiableClassLoaderRepository().addClassLoader(cl); + } + } + + private void register(MBeanMetaData metadata, boolean privileged) throws InstanceAlreadyExistsException + { + metadata.name = normalizeObjectName(metadata.name); + + ObjectName objectName = metadata.name; + if (objectName == null || objectName.isPattern()) + { + throw new RuntimeOperationsException(new IllegalArgumentException("ObjectName cannot be null or a pattern ObjectName")); + } + if (objectName.getDomain().equals("JMImplementation") && !privileged) + { + throw new JMRuntimeException("Domain 'JMImplementation' is reserved for the JMX Agent"); + } + + MBeanRepository repository = getMBeanRepository(); + synchronized (repository) + { + if (repository.get(objectName) != null) throw new InstanceAlreadyExistsException(objectName.toString()); + + repository.put(objectName, metadata); + } + addDomain(objectName.getDomain()); + + notify(objectName, MBeanServerNotification.REGISTRATION_NOTIFICATION); + } + + private void notify(ObjectName objectName, String notificationType) + { + long sequenceNumber = 0; + synchronized (MX4JMBeanServer.class) + { + sequenceNumber = notifications; + ++notifications; + } + + delegate.sendNotification(new MBeanServerNotification(notificationType, delegateName, sequenceNumber, objectName)); + } + + private void addDomain(String domain) + { + synchronized (domains) + { + Integer count = (Integer)domains.get(domain); + if (count == null) + domains.put(domain, new Integer(1)); + else + domains.put(domain, new Integer(count.intValue() + 1)); + } + } + + private void removeDomain(String domain) + { + synchronized (domains) + { + Integer count = (Integer)domains.get(domain); + if (count == null) throw new ImplementationException(); + if (count.intValue() < 2) + domains.remove(domain); + else + domains.put(domain, new Integer(count.intValue() - 1)); + } + } + + public void unregisterMBean(ObjectName objectName) + throws InstanceNotFoundException, MBeanRegistrationException + { + objectName = secureObjectName(objectName); + + if (objectName == null || objectName.isPattern()) + { + throw new RuntimeOperationsException(new IllegalArgumentException("ObjectName cannot be null or a pattern ObjectName")); + } + + if (objectName.getDomain().equals("JMImplementation")) + { + throw new RuntimeOperationsException(new IllegalArgumentException("Domain 'JMImplementation' is reserved for the JMX Agent")); + } + + MBeanMetaData metadata = findMBeanMetaData(objectName); + + try + { + MBeanServerInterceptor head = getHeadInterceptor(); + head.registration(metadata, MBeanServerInterceptor.PRE_DEREGISTER); + + unregister(metadata); + + getHeadInterceptor().registration(metadata, MBeanServerInterceptor.POST_DEREGISTER); + + if (metadata.mbean instanceof ClassLoader && !(metadata.mbean instanceof PrivateClassLoader)) + { + getModifiableClassLoaderRepository().removeClassLoader((ClassLoader)metadata.mbean); + } + } + catch (MBeanRegistrationException x) + { + throw x; + } + catch (SecurityException x) + { + throw x; + } + catch (Exception x) + { + throw new MBeanRegistrationException(x); + } + catch (Error x) + { + throw new MBeanRegistrationException(new RuntimeErrorException(x)); + } + } + + private void unregister(MBeanMetaData metadata) + { + ObjectName objectName = metadata.name; + + MBeanRepository repository = getMBeanRepository(); + synchronized (repository) + { + repository.remove(objectName); + } + removeDomain(objectName.getDomain()); + + notify(objectName, MBeanServerNotification.UNREGISTRATION_NOTIFICATION); + } + + public Object getAttribute(ObjectName objectName, String attribute) + throws InstanceNotFoundException, MBeanException, AttributeNotFoundException, ReflectionException + { + if (attribute == null || attribute.trim().length() == 0) + { + throw new RuntimeOperationsException(new IllegalArgumentException("Invalid attribute")); + } + + objectName = secureObjectName(objectName); + + MBeanMetaData metadata = findMBeanMetaData(objectName); + + return getHeadInterceptor().getAttribute(metadata, attribute); + } + + + public void setAttribute(ObjectName objectName, Attribute attribute) + throws InstanceNotFoundException, AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException + { + if (attribute == null || attribute.getName().trim().length() == 0) + { + throw new RuntimeOperationsException(new IllegalArgumentException("Invalid attribute")); + } + + objectName = secureObjectName(objectName); + + MBeanMetaData metadata = findMBeanMetaData(objectName); + + getHeadInterceptor().setAttribute(metadata, attribute); + } + + public AttributeList getAttributes(ObjectName objectName, String[] attributes) + throws InstanceNotFoundException, ReflectionException + { + if (attributes == null || attributes.length == 0) + { + throw new RuntimeOperationsException(new IllegalArgumentException("Invalid attribute list")); + } + + objectName = secureObjectName(objectName); + + MBeanMetaData metadata = findMBeanMetaData(objectName); + + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + { + // Must check if the user has the right to call this method, regardless of the attributes + sm.checkPermission(new MBeanPermission(metadata.info.getClassName(), "-", objectName, "getAttribute")); + } + + return getHeadInterceptor().getAttributes(metadata, attributes); + } + + public AttributeList setAttributes(ObjectName objectName, AttributeList attributes) + throws InstanceNotFoundException, ReflectionException + { + if (attributes == null) + { + throw new RuntimeOperationsException(new IllegalArgumentException("Invalid attribute list")); + } + + objectName = secureObjectName(objectName); + + MBeanMetaData metadata = findMBeanMetaData(objectName); + + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + { + // Must check if the user has the right to call this method, regardless of the attributes + sm.checkPermission(new MBeanPermission(metadata.info.getClassName(), "-", objectName, "setAttribute")); + } + + return getHeadInterceptor().setAttributes(metadata, attributes); + } + + public Object invoke(ObjectName objectName, String methodName, Object[] args, String[] parameters) + throws InstanceNotFoundException, MBeanException, ReflectionException + { + if (methodName == null || methodName.trim().length() == 0) + { + throw new RuntimeOperationsException(new IllegalArgumentException("Invalid operation name '" + methodName + "'")); + } + + if (args == null) args = EMPTY_ARGS; + if (parameters == null) parameters = EMPTY_PARAMS; + + objectName = secureObjectName(objectName); + + MBeanMetaData metadata = findMBeanMetaData(objectName); + + return getHeadInterceptor().invoke(metadata, methodName, parameters, args); + } + + public String getDefaultDomain() + { + return defaultDomain; + } + + public String[] getDomains() + { + synchronized (domains) + { + Set keys = domains.keySet(); + return (String[])keys.toArray(new String[keys.size()]); + } + } + + public Integer getMBeanCount() + { + MBeanRepository repository = getMBeanRepository(); + synchronized (repository) + { + return new Integer(repository.size()); + } + } + + public boolean isRegistered(ObjectName objectName) + { + try + { + return findMBeanMetaData(objectName) != null; + } + catch (InstanceNotFoundException x) + { + return false; + } + } + + public MBeanInfo getMBeanInfo(ObjectName objectName) + throws InstanceNotFoundException, IntrospectionException, ReflectionException + { + objectName = secureObjectName(objectName); + + MBeanMetaData metadata = findMBeanMetaData(objectName); + + MBeanInfo info = getHeadInterceptor().getMBeanInfo(metadata); + if (info == null) throw new JMRuntimeException("MBeanInfo returned for MBean " + objectName + " is null"); + return info; + } + + public ObjectInstance getObjectInstance(ObjectName objectName) + throws InstanceNotFoundException + { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + { + objectName = secureObjectName(objectName); + } + + MBeanMetaData metadata = findMBeanMetaData(objectName); + + if (sm != null) + { + sm.checkPermission(new MBeanPermission(metadata.info.getClassName(), "-", objectName, "getObjectInstance")); + } + + return metadata.instance; + } + + public boolean isInstanceOf(ObjectName objectName, String className) + throws InstanceNotFoundException + { + if (className == null || className.trim().length() == 0) + { + throw new RuntimeOperationsException(new IllegalArgumentException("Invalid class name")); + } + + objectName = secureObjectName(objectName); + + MBeanMetaData metadata = findMBeanMetaData(objectName); + + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + { + sm.checkPermission(new MBeanPermission(metadata.info.getClassName(), "-", objectName, "isInstanceOf")); + } + + try + { + ClassLoader loader = metadata.classloader; + Class cls = null; + if (loader != null) + cls = loader.loadClass(className); + else + cls = Class.forName(className, false, null); + + if (metadata.mbean instanceof StandardMBean) + { + Object impl = ((StandardMBean) metadata.mbean).getImplementation(); + return cls.isInstance(impl); + } + else + { + return cls.isInstance(metadata.mbean); + } + } + catch (ClassNotFoundException x) + { + return false; + } + } + + public Set queryMBeans(ObjectName patternName, QueryExp filter) + { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + { + patternName = secureObjectName(patternName); + // Must check if the user has the right to call this method, + // no matter which ObjectName has been passed. + sm.checkPermission(new MBeanPermission("-#-[-]", "queryMBeans")); + } + + Set match = queryObjectNames(patternName, filter, true); + + Set set = new HashSet(); + for (Iterator i = match.iterator(); i.hasNext();) + { + ObjectName name = (ObjectName)i.next(); + try + { + MBeanMetaData metadata = findMBeanMetaData(name); + set.add(metadata.instance); + } + catch (InstanceNotFoundException ignored) + { + // A concurrent thread removed the MBean after queryNames, ignore + } + } + return set; + } + + public Set queryNames(ObjectName patternName, QueryExp filter) + { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + { + patternName = secureObjectName(patternName); + // Must check if the user has the right to call this method, + // no matter which ObjectName has been passed. + sm.checkPermission(new MBeanPermission("-#-[-]", "queryNames")); + } + + return queryObjectNames(patternName, filter, false); + } + + /** + * Utility method for queryNames and queryMBeans that returns a set of ObjectNames. + * It does 3 things: + * 1) filter the MBeans following the given ObjectName pattern + * 2) filter the MBeans following the permissions that client code has + * 3) filter the MBeans following the given QueryExp + * It is important that these 3 operations are done in this order + */ + private Set queryObjectNames(ObjectName patternName, QueryExp filter, boolean instances) + { + // First, retrieve the scope of the query: all mbeans matching the patternName + Set scope = findMBeansByPattern(patternName); + + // Second, filter the scope by checking the caller's permissions + Set secureScope = filterMBeansBySecurity(scope, instances); + + // Third, filter the scope using the given QueryExp + Set match = filterMBeansByQuery(secureScope, filter); + + return match; + } + + /** + * Returns a set of ObjectNames of the registered MBeans that match the given ObjectName pattern + */ + private Set findMBeansByPattern(ObjectName pattern) + { + if (pattern == null) + { + try + { + pattern = new ObjectName("*:*"); + } + catch (MalformedObjectNameException ignored) + { + } + } + + pattern = normalizeObjectName(pattern); + + String patternDomain = pattern.getDomain(); + Hashtable patternProps = pattern.getKeyPropertyList(); + + Set set = new HashSet(); + + // Clone the repository, we are faster than holding the lock while iterating + MBeanRepository repository = (MBeanRepository)getMBeanRepository().clone(); + + for (Iterator i = repository.iterator(); i.hasNext();) + { + MBeanMetaData metadata = (MBeanMetaData)i.next(); + ObjectName name = metadata.name; + Hashtable props = name.getKeyPropertyList(); + + String domain = name.getDomain(); + if (Utils.wildcardMatch(patternDomain, domain)) + { + // Domain matches, now check properties + if (pattern.isPropertyPattern()) + { + // A property pattern with no entries, can only be '*' + if (patternProps.size() == 0) + { + // User wants all properties + set.add(name); + } + else + { + // Loop on the properties of the pattern. + // If one is not found then the current ObjectName does not match + boolean found = true; + for (Iterator j = patternProps.entrySet().iterator(); j.hasNext();) + { + Map.Entry entry = (Map.Entry)j.next(); + Object patternKey = entry.getKey(); + Object patternValue = entry.getValue(); + if (patternKey.equals("*")) + { + continue; + } + + // Try to see if the current ObjectName contains this entry + if (!props.containsKey(patternKey)) + { + // Not even the key is present + found = false; + break; + } + else + { + // The key is present, let's check if the values are equal + Object value = props.get(patternKey); + if (value == null && patternValue == null) + { + // Values are equal, go on with next pattern entry + continue; + } + if (value != null && value.equals(patternValue)) + { + // Values are equal, go on with next pattern entry + continue; + } + // Here values are different + found = false; + break; + } + } + if (found) set.add(name); + } + } + else + { + if (props.entrySet().equals(patternProps.entrySet())) set.add(name); + } + } + } + return set; + } + + /** + * Filters the given set of ObjectNames following the permission that client code has granted. + * Returns a set containing the allowed ObjectNames. + */ + private Set filterMBeansBySecurity(Set mbeans, boolean instances) + { + SecurityManager sm = System.getSecurityManager(); + if (sm == null) return mbeans; + + HashSet set = new HashSet(); + for (Iterator i = mbeans.iterator(); i.hasNext();) + { + ObjectName name = (ObjectName)i.next(); + try + { + MBeanMetaData metadata = findMBeanMetaData(name); + String className = metadata.info.getClassName(); + sm.checkPermission(new MBeanPermission(className, "-", name, instances ? "queryMBeans" : "queryNames")); + set.add(name); + } + catch (InstanceNotFoundException ignored) + { + // A concurrent thread removed this MBean, continue + continue; + } + catch (SecurityException ignored) + { + // Don't add the name to the list, and go on. + } + } + return set; + } + + /** + * Filters the given set of ObjectNames following the given QueryExp. + * Returns a set of ObjectNames that match the given QueryExp. + */ + private Set filterMBeansByQuery(Set scope, QueryExp filter) + { + if (filter == null) return scope; + + Set set = new HashSet(); + for (Iterator i = scope.iterator(); i.hasNext();) + { + ObjectName name = (ObjectName)i.next(); + filter.setMBeanServer(this); + try + { + if (filter.apply(name)) set.add(name); + } + catch (BadStringOperationException ignored) + { + } + catch (BadBinaryOpValueExpException ignored) + { + } + catch (BadAttributeValueExpException x) + { + } + catch (InvalidApplicationException x) + { + } + catch (SecurityException x) + { + } + catch (Exception x) + { + // The 1.2 spec says Exceptions must not be propagated + } + } + return set; + } + + /** + * Returns a normalized ObjectName from the given one. + * If an ObjectName is specified with the abbreviated notation for the default domain, that is ':key=value' + * this method returns an ObjectName whose domain is the default domain of this MBeanServer and with the same + * properties. + */ + private ObjectName normalizeObjectName(ObjectName name) + { + if (name == null) return null; + + String defaultDomain = getDefaultDomain(); + String domain = name.getDomain(); + + if (domain.length() == 0 && defaultDomain.length() > 0) + { + // The given object name specifies the abbreviated form to indicate the default domain, + // ie ':key=value', the empty string as domain. I must convert this abbreviated form + // to the full one, if the default domain of this mbeanserver is not the empty string as well + StringBuffer buffer = new StringBuffer(defaultDomain).append(":").append(name.getKeyPropertyListString()); + if (name.isPropertyPattern()) + { + if (name.getKeyPropertyList().size() > 0) + buffer.append(",*"); + else + buffer.append("*"); + } + try + { + name = new ObjectName(buffer.toString()); + } + catch (MalformedObjectNameException ignored) + { + } + } + return name; + } + + /** + * Returns an ObjectName instance even if the provided ObjectName is a subclass. + * This is done to avoid security holes: a nasty ObjectName subclass can bypass security checks. + */ + private ObjectName secureObjectName(ObjectName name) + { + // I cannot trust ObjectName, since a malicious user can send a subclass that overrides equals and hashcode + // to match another ObjectName for which it does not have permission, or returns different results from + // ObjectName.getCanonicalName() for different calls, so that passes the security checks but in fact will + // later refer to a different ObjectName for which it does not have permission. + if (name == null) return null; + return ObjectName.getInstance(name); + } +} Added: incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/MX4JMBeanServerBuilder.java URL: http://svn.apache.org/viewvc/incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/MX4JMBeanServerBuilder.java?rev=422696&view=auto ============================================================================== --- incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/MX4JMBeanServerBuilder.java (added) +++ incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/MX4JMBeanServerBuilder.java Mon Jul 17 05:14:31 2006 @@ -0,0 +1,71 @@ +/* + * Copyright (C) MX4J. + * All rights reserved. + * + * This software is distributed under the terms of the MX4J License version 1.0. + * See the terms of the MX4J License in the documentation provided with this software. + */ +/* + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.felix.mosgi.jmx.agent.mx4j.server; + + +import javax.management.MBeanServer; +import javax.management.MBeanServerBuilder; +import javax.management.MBeanServerDelegate; + +/** + *

This class is responsible for creating new instances of {@link MBeanServerDelegate} + * and {@link MBeanServer}. It creates instances from the implementation in the + * mx4j.server package.

+ * + *

The {@link javax.management.MBeanServerFactory} first creates the delegate, then it + * creates the MBeanServer and provides a reference to the created delegate to it. + * Note that the delegate passed to the MBeanServer might not be the instance returned + * by this builder; for example, it could be a wrapper around it.

+ * + * @see MBeanServer + * @see javax.management.MBeanServerFactory + * + * @author Olav Reinert + * @version $Revision: 1.1.1.1 $ + **/ + +public class MX4JMBeanServerBuilder extends MBeanServerBuilder +{ + /** + * Returns a new {@link MX4JMBeanServerDelegate} instance for a new MBeanServer. + * @return a new {@link MX4JMBeanServerDelegate} instance for a new MBeanServer. + **/ + public MBeanServerDelegate newMBeanServerDelegate() + { + return new MX4JMBeanServerDelegate(); + } + + /** + * Returns a new {@link MX4JMBeanServer} instance. + * @param defaultDomain the default domain name for the new server. + * @param outer the {@link MBeanServer} that is passed in calls to + * {@link javax.management.MBeanRegistration#preRegister(javax.management.MBeanServer, javax.management.ObjectName)}. + * @param delegate the {@link MBeanServerDelegate} instance for the new server. + * @return a new {@link MX4JMBeanServer} instance. + **/ + public MBeanServer newMBeanServer(String defaultDomain, MBeanServer outer, MBeanServerDelegate delegate) + { + return new MX4JMBeanServer(defaultDomain, outer, delegate); + } +} Added: incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/MX4JMBeanServerDelegate.java URL: http://svn.apache.org/viewvc/incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/MX4JMBeanServerDelegate.java?rev=422696&view=auto ============================================================================== --- incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/MX4JMBeanServerDelegate.java (added) +++ incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/MX4JMBeanServerDelegate.java Mon Jul 17 05:14:31 2006 @@ -0,0 +1,52 @@ +/* + * Copyright (C) MX4J. + * All rights reserved. + * + * This software is distributed under the terms of the MX4J License version 1.0. + * See the terms of the MX4J License in the documentation provided with this software. + */ +/* + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.felix.mosgi.jmx.agent.mx4j.server; + + +import javax.management.MBeanServerDelegate; + +/** + * The MBeanServerDelegate subclass typical of the MX4J implementation. + * + * @see javax.management.MBeanServerBuilder + * @author Simone Bordet + * @version $Revision: 1.1.1.1 $ + */ +public class MX4JMBeanServerDelegate extends MBeanServerDelegate +{ + public String getImplementationName() + { + return "MX4J"; + } + + public String getImplementationVendor() + { + return "The MX4J Team"; + } + + public String getImplementationVersion() + { + return "2.0-beta-1"; + } +} Added: incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/ModifiableClassLoaderRepository.java URL: http://svn.apache.org/viewvc/incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/ModifiableClassLoaderRepository.java?rev=422696&view=auto ============================================================================== --- incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/ModifiableClassLoaderRepository.java (added) +++ incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/ModifiableClassLoaderRepository.java Mon Jul 17 05:14:31 2006 @@ -0,0 +1,54 @@ +/* + * Copyright (C) MX4J. + * All rights reserved. + * + * This software is distributed under the terms of the MX4J License version 1.0. + * See the terms of the MX4J License in the documentation provided with this software. + */ +/* + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.felix.mosgi.jmx.agent.mx4j.server; + +import javax.management.loading.ClassLoaderRepository; + +/** + * Base class to extend to create custom ClassLoaderRepositories. + * MX4J's MBeanServer can use a custom ClassLoaderRepository instead of the default one + * by simply specifying a suitable system property, see {@link mx4j.MX4JSystemKeys}. + * It must be a class, otherwise it opens up a security hole, as anyone can cast the MBeanServer's + * ClassLoaderRepository down to this class and call addClassLoader or removeClassLoader + * since, if this class is an interface, they must be public. + * + * @author Simone Bordet + * @version $Revision: 1.1.1.1 $ + */ +public abstract class ModifiableClassLoaderRepository implements ClassLoaderRepository +{ + /** + * Adds, if does not already exist, the specified ClassLoader to this repository. + * @param cl The classloader to add + * @see #removeClassLoader + */ + protected abstract void addClassLoader(ClassLoader cl); + + /** + * Removes, if exists, the specified ClassLoader from this repository. + * @param cl The classloader to remove + * @see #addClassLoader + */ + protected abstract void removeClassLoader(ClassLoader cl); +} Added: incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/ReflectedMBeanInvoker.java URL: http://svn.apache.org/viewvc/incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/ReflectedMBeanInvoker.java?rev=422696&view=auto ============================================================================== --- incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/ReflectedMBeanInvoker.java (added) +++ incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/ReflectedMBeanInvoker.java Mon Jul 17 05:14:31 2006 @@ -0,0 +1,302 @@ +/* + * Copyright (C) MX4J. + * All rights reserved. + * + * This software is distributed under the terms of the MX4J License version 1.0. + * See the terms of the MX4J License in the documentation provided with this software. + */ +/* + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.felix.mosgi.jmx.agent.mx4j.server; + + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import javax.management.Attribute; +import javax.management.AttributeNotFoundException; +import javax.management.InvalidAttributeValueException; +import javax.management.JMRuntimeException; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanException; +import javax.management.MBeanInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanParameterInfo; +import javax.management.ReflectionException; +import javax.management.RuntimeErrorException; +import javax.management.RuntimeMBeanException; +import javax.management.RuntimeOperationsException; + +import org.apache.felix.mosgi.jmx.agent.mx4j.ImplementationException; +import org.apache.felix.mosgi.jmx.agent.mx4j.util.Utils; +import org.apache.felix.mosgi.jmx.agent.mx4j.util.MethodTernaryTree; + +/** + * MBeanInvoker that uses reflection to invoke on MBean instances. + * + * @author Simone Bordet + * @version $Revision: 1.1.1.1 $ + */ +public class ReflectedMBeanInvoker implements MBeanInvoker +{ + private static final String[] EMPTY_PARAMS = new String[0]; + private static final Object[] EMPTY_ARGS = new Object[0]; + + private final Map attributes = new HashMap(); + private final Map attributeNames = new HashMap(); + private final MethodTernaryTree operations = new MethodTernaryTree(); + private final MethodTernaryTree methods = new MethodTernaryTree(); + + public Object invoke(MBeanMetaData metadata, String method, String[] params, Object[] args) throws MBeanException, ReflectionException + { + MBeanOperationInfo oper = getStandardOperationInfo(metadata, method, params); + if (oper != null) + { + try + { + return invokeImpl(metadata, method, params, args); + } + catch (IllegalArgumentException x) + { + throw new RuntimeOperationsException(x); + } + } + else + { + throw new ReflectionException(new NoSuchMethodException("Operation " + method + " does not belong to the management interface")); + } + } + + public Object getAttribute(MBeanMetaData metadata, String attribute) throws MBeanException, AttributeNotFoundException, ReflectionException + { + MBeanAttributeInfo attr = getStandardAttributeInfo(metadata, attribute, false); + if (attr != null) + { + String attributeName = getAttributeName(attr, true); + try + { + return invokeImpl(metadata, attributeName, EMPTY_PARAMS, EMPTY_ARGS); + } + catch (IllegalArgumentException x) + { + // Never thrown, since there are no arguments + throw new ImplementationException(); + } + } + else + { + throw new AttributeNotFoundException(attribute); + } + } + + public void setAttribute(MBeanMetaData metadata, Attribute attribute) throws MBeanException, AttributeNotFoundException, InvalidAttributeValueException, ReflectionException + { + String name = attribute.getName(); + MBeanAttributeInfo attr = getStandardAttributeInfo(metadata, name, true); + if (attr != null) + { + String attributeName = getAttributeName(attr, false); + try + { + invokeImpl(metadata, attributeName, new String[]{attr.getType()}, new Object[]{attribute.getValue()}); + } + catch (IllegalArgumentException x) + { + throw new InvalidAttributeValueException("Invalid value for attribute " + name + ": " + attribute.getValue()); + } + } + else + { + throw new AttributeNotFoundException(name); + } + } + + protected Object invokeImpl(MBeanMetaData metadata, String method, String[] signature, Object[] args) throws ReflectionException, MBeanException, IllegalArgumentException + { + Method m = getStandardManagementMethod(metadata, method, signature); + + try + { + return m.invoke(metadata.mbean, args); + } + catch (IllegalAccessException x) + { + throw new ReflectionException(x); + } + catch (InvocationTargetException x) + { + Throwable t = x.getTargetException(); + if (t instanceof Error) throw new RuntimeErrorException((Error)t); + if (t instanceof JMRuntimeException) throw (JMRuntimeException)t; + if (t instanceof RuntimeException) throw new RuntimeMBeanException((RuntimeException)t); + throw new MBeanException((Exception)t); + } + } + + private MBeanAttributeInfo getStandardAttributeInfo(MBeanMetaData metadata, String attribute, boolean isWritable) + { + MBeanAttributeInfo attr = null; + synchronized (attributes) + { + attr = (MBeanAttributeInfo)attributes.get(attribute); + } + if (attr != null) + { + if (isWritable && attr.isWritable()) return attr; + if (!isWritable && attr.isReadable()) return attr; + } + else + { + MBeanAttributeInfo[] attrs = metadata.info.getAttributes(); + if (attrs != null) + { + for (int i = 0; i < attrs.length; ++i) + { + attr = attrs[i]; + String name = attr.getName(); + if (attribute.equals(name)) + { + synchronized (attributes) + { + attributes.put(attribute, attr); + } + if (isWritable && attr.isWritable()) return attr; + if (!isWritable && attr.isReadable()) return attr; + } + } + } + } + return null; + } + + private MBeanOperationInfo getStandardOperationInfo(MBeanMetaData metadata, String method, String[] signature) + { + MBeanOperationInfo oper = null; + + synchronized (operations) + { + oper = (MBeanOperationInfo)operations.get(method, signature); + } + + if (oper != null) return oper; + + // The MBeanOperationInfo is not in the cache, look it up + MBeanInfo info = metadata.info; + MBeanOperationInfo[] opers = info.getOperations(); + if (opers != null) + { + for (int i = 0; i < opers.length; ++i) + { + oper = opers[i]; + String name = oper.getName(); + if (method.equals(name)) + { + // Same method name, check number of parameters + MBeanParameterInfo[] params = oper.getSignature(); + if (signature.length == params.length) + { + boolean match = true; + for (int j = 0; j < params.length; ++j) + { + MBeanParameterInfo param = params[j]; + if (!signature[j].equals(param.getType())) + { + match = false; + break; + } + } + if (match) + { + synchronized (operations) + { + operations.put(method, signature, oper); + } + return oper; + } + } + } + } + } + return null; + } + + private Method getStandardManagementMethod(MBeanMetaData metadata, String name, String[] signature) throws ReflectionException + { + Method method = null; + synchronized (methods) + { + method = (Method)methods.get(name, signature); + } + if (method != null) return method; + + // Method is not in cache, look it up + try + { + Class[] params = Utils.loadClasses(metadata.classloader, signature); + method = metadata.mbean.getClass().getMethod(name, params); + synchronized (methods) + { + methods.put(name, signature, method); + } + return method; + } + catch (ClassNotFoundException x) + { + throw new ReflectionException(x); + } + catch (NoSuchMethodException x) + { + throw new ReflectionException(x); + } + } + + private String getAttributeName(MBeanAttributeInfo attribute, boolean getter) + { + AttributeName attributeName = null; + String name = attribute.getName(); + synchronized (attributeNames) + { + attributeName = (AttributeName)attributeNames.get(name); + } + if (attributeName == null) + { + String prefix = attribute.isIs() ? "is" : "get"; + attributeName = new AttributeName(prefix + name, "set" + name); + synchronized (attributeNames) + { + attributeNames.put(name, attributeName); + } + } + + if (getter) return attributeName.getter; + return attributeName.setter; + } + + private static class AttributeName + { + private final String getter; + private final String setter; + + public AttributeName(String getter, String setter) + { + this.getter = getter; + this.setter = setter; + } + } +} Added: incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/interceptor/ContextClassLoaderMBeanServerInterceptor.java URL: http://svn.apache.org/viewvc/incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/interceptor/ContextClassLoaderMBeanServerInterceptor.java?rev=422696&view=auto ============================================================================== --- incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/interceptor/ContextClassLoaderMBeanServerInterceptor.java (added) +++ incubator/felix/trunk/org.apache.felix.mosgi.jmx.agent/src/main/java/org/apache/felix/mosgi/jmx/agent/mx4j/server/interceptor/ContextClassLoaderMBeanServerInterceptor.java Mon Jul 17 05:14:31 2006 @@ -0,0 +1,329 @@ +/* + * Copyright (C) MX4J. + * All rights reserved. + * + * This software is distributed under the terms of the MX4J License version 1.0. + * See the terms of the MX4J License in the documentation provided with this software. + */ + +/* + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.felix.mosgi.jmx.agent.mx4j.server.interceptor; + +import java.security.PrivilegedAction; +import java.security.AccessController; + +import javax.management.Attribute; +import javax.management.AttributeList; +import javax.management.AttributeNotFoundException; +import javax.management.InvalidAttributeValueException; +import javax.management.ListenerNotFoundException; +import javax.management.MBeanException; +import javax.management.MBeanInfo; +import javax.management.MBeanRegistrationException; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; +import javax.management.ReflectionException; + +import org.apache.felix.mosgi.jmx.agent.mx4j.server.MBeanMetaData; + +/** + * This interceptor sets the context class loader to the proper value for incoming calls. + * It saves the current context class loader, set the context class loader to be the MBean's class loader for + * the current call, and on return re-set the context class loader to the previous value + * + * @author Simone Bordet + * @version $Revision: 1.1.1.1 $ + */ +public class ContextClassLoaderMBeanServerInterceptor extends DefaultMBeanServerInterceptor +{ + public ContextClassLoaderMBeanServerInterceptor() + { + // Disabled by default + setEnabled(false); + } + + public String getType() + { + return "contextclassloader"; + } + + public void addNotificationListener(MBeanMetaData metadata, NotificationListener listener, NotificationFilter filter, Object handback) + { + if (isEnabled()) + { + ClassLoader context = getContextClassLoader(); + if (metadata.classloader != context) + { + try + { + setContextClassLoader(metadata.classloader); + super.addNotificationListener(metadata, listener, filter, handback); + return; + } + finally + { + setContextClassLoader(context); + } + } + } + + super.addNotificationListener(metadata, listener, filter, handback); + } + + public void removeNotificationListener(MBeanMetaData metadata, NotificationListener listener) throws ListenerNotFoundException + { + if (isEnabled()) + { + ClassLoader context = getContextClassLoader(); + if (metadata.classloader != context) + { + try + { + setContextClassLoader(metadata.classloader); + super.removeNotificationListener(metadata, listener); + return; + } + finally + { + setContextClassLoader(context); + } + } + } + + super.removeNotificationListener(metadata, listener); + } + + public void removeNotificationListener(MBeanMetaData metadata, NotificationListener listener, NotificationFilter filter, Object handback) throws ListenerNotFoundException + { + if (isEnabled()) + { + ClassLoader context = getContextClassLoader(); + if (metadata.classloader != context) + { + try + { + setContextClassLoader(metadata.classloader); + super.removeNotificationListener(metadata, listener, filter, handback); + return; + } + finally + { + setContextClassLoader(context); + } + } + } + + super.removeNotificationListener(metadata, listener, filter, handback); + } + + public void instantiate(MBeanMetaData metadata, String className, String[] params, Object[] args) throws ReflectionException, MBeanException + { + if (isEnabled()) + { + ClassLoader context = getContextClassLoader(); + if (metadata.classloader != context) + { + try + { + setContextClassLoader(metadata.classloader); + super.instantiate(metadata, className, params, args); + return; + } + finally + { + setContextClassLoader(context); + } + } + } + + super.instantiate(metadata, className, params, args); + } + + public void registration(MBeanMetaData metadata, int operation) throws MBeanRegistrationException + { + if (isEnabled()) + { + ClassLoader context = getContextClassLoader(); + if (metadata.classloader != context) + { + try + { + setContextClassLoader(metadata.classloader); + super.registration(metadata, operation); + return; + } + finally + { + setContextClassLoader(context); + } + } + } + + super.registration(metadata, operation); + } + + public MBeanInfo getMBeanInfo(MBeanMetaData metadata) + { + if (isEnabled()) + { + ClassLoader context = getContextClassLoader(); + if (metadata.classloader != context) + { + try + { + setContextClassLoader(metadata.classloader); + return super.getMBeanInfo(metadata); + } + finally + { + setContextClassLoader(context); + } + } + } + + return super.getMBeanInfo(metadata); + } + + public Object invoke(MBeanMetaData metadata, String method, String[] params, Object[] args) throws MBeanException, ReflectionException + { + if (isEnabled()) + { + ClassLoader context = getContextClassLoader(); + if (metadata.classloader != context) + { + try + { + setContextClassLoader(metadata.classloader); + return super.invoke(metadata, method, params, args); + } + finally + { + setContextClassLoader(context); + } + } + } + + return super.invoke(metadata, method, params, args); + } + + public AttributeList getAttributes(MBeanMetaData metadata, String[] attributes) + { + if (isEnabled()) + { + ClassLoader context = getContextClassLoader(); + if (metadata.classloader != context) + { + try + { + setContextClassLoader(metadata.classloader); + return super.getAttributes(metadata, attributes); + } + finally + { + setContextClassLoader(context); + } + } + } + + return super.getAttributes(metadata, attributes); + } + + public AttributeList setAttributes(MBeanMetaData metadata, AttributeList attributes) + { + if (isEnabled()) + { + ClassLoader context = getContextClassLoader(); + if (metadata.classloader != context) + { + try + { + setContextClassLoader(metadata.classloader); + return super.setAttributes(metadata, attributes); + } + finally + { + setContextClassLoader(context); + } + } + } + + return super.setAttributes(metadata, attributes); + } + + public Object getAttribute(MBeanMetaData metadata, String attribute) throws MBeanException, AttributeNotFoundException, ReflectionException + { + if (isEnabled()) + { + ClassLoader context = getContextClassLoader(); + if (metadata.classloader != context) + { + try + { + setContextClassLoader(metadata.classloader); + return super.getAttribute(metadata, attribute); + } + finally + { + setContextClassLoader(context); + } + } + } + + return super.getAttribute(metadata, attribute); + } + + public void setAttribute(MBeanMetaData metadata, Attribute attribute) throws MBeanException, AttributeNotFoundException, InvalidAttributeValueException, ReflectionException + { + if (isEnabled()) + { + ClassLoader context = getContextClassLoader(); + if (metadata.classloader != context) + { + try + { + setContextClassLoader(metadata.classloader); + super.setAttribute(metadata, attribute); + return; + } + finally + { + setContextClassLoader(context); + } + } + } + + super.setAttribute(metadata, attribute); + } + + private ClassLoader getContextClassLoader() + { + return Thread.currentThread().getContextClassLoader(); + } + + private void setContextClassLoader(final ClassLoader cl) + { + AccessController.doPrivileged(new PrivilegedAction() + { + public Object run() + { + Thread.currentThread().setContextClassLoader(cl); + return null; + } + }); + } +}