/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2017 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://oss.oracle.com/licenses/CDDL+GPL-1.1
* or LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.admin.amx.core.proxy;
import org.glassfish.admin.amx.base.DomainRoot;
import org.glassfish.admin.amx.config.AMXConfigProxy;
import org.glassfish.admin.amx.core.AMXProxy;
import org.glassfish.admin.amx.core.Util;
import org.glassfish.admin.amx.util.AMXDebugHelper;
import org.glassfish.admin.amx.util.ExceptionUtil;
import org.glassfish.admin.amx.util.StringUtil;
import org.glassfish.admin.amx.util.jmx.JMXUtil;
import org.glassfish.external.amx.AMXGlassfish;
import org.glassfish.external.arc.Stability;
import org.glassfish.external.arc.Taxonomy;
import javax.management.*;
import javax.management.relation.MBeanServerNotificationFilter;
import javax.management.remote.JMXConnectionNotification;
import java.io.IOException;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static org.glassfish.external.amx.AMX.DESC_STD_IMMUTABLE_INFO;
import static org.glassfish.external.amx.AMX.NAME_KEY;
//import org.glassfish.api.amx.AMXUtil;
/**
* @deprecated Factory for {@link AMXProxy} proxies.
*/
@Deprecated
@Taxonomy(stability = Stability.UNCOMMITTED)
public final class ProxyFactory implements NotificationListener {
private final MBeanServerConnection mMBeanServerConnection;
private final String mMBeanServerID;
private final ObjectName mDomainRootObjectName;
private final DomainRoot mDomainRoot;
/**
* For immutable MBeanInfo, we want to pay the cost once and only once of a
* trip to the server. <p> Can we assume it's unique per *type* so that we
* can cache it once per type? If we could so so, the size of the cache
* would stay much smaller.
*/
private final ConcurrentMap<ObjectName, MBeanInfo> mMBeanInfoCache = new ConcurrentHashMap<ObjectName, MBeanInfo>();
private static final AMXDebugHelper mDebug =
new AMXDebugHelper(ProxyFactory.class.getName());
private static void debug(final Object... args) {
//mDebug.println( args );
System.out.println(StringUtil.toString(", ", args));
}
private static final Map<MBeanServerConnection, ProxyFactory> INSTANCES =
Collections.synchronizedMap(new HashMap<MBeanServerConnection, ProxyFactory>());
/**
* Because ProxyFactory is used on both client and server, emitting anything
* to stdout or to the log is unacceptable in some circumstances. Warnings
* remain available if the AMX-DEBUG system property allows it.
*/
private static void warning(final Object... args) {
debug(args);
}
private ProxyFactory(final MBeanServerConnection conn) {
mDebug.setEchoToStdOut(true);
assert (conn != null);
mMBeanServerConnection = conn;
try {
mMBeanServerID = JMXUtil.getMBeanServerID(conn);
mDomainRootObjectName = AMXGlassfish.DEFAULT.domainRoot();
if (mDomainRootObjectName == null) {
throw new IllegalStateException("ProxyFactory: AMX has not been started");
}
mDomainRoot = getProxy(mDomainRootObjectName, DomainRoot.class);
// we should always be able to listen to MBeans--
// but the http connector does not support listeners
try {
final MBeanServerNotificationFilter filter = new MBeanServerNotificationFilter();
filter.enableAllObjectNames();
filter.disableAllTypes();
filter.enableType(MBeanServerNotification.UNREGISTRATION_NOTIFICATION);
JMXUtil.listenToMBeanServerDelegate(conn, this, filter, null);
} catch (Exception e) {
warning("ProxyFactory: connection does not support notifications: ",
mMBeanServerID, conn);
}
} catch (Exception e) {
warning("ProxyFactory.ProxyFactory:\n", e);
throw new RuntimeException(e);
}
}
/**
* The connection is bad. Tell each proxy its gone and remove it.
*/
private void connectionBad() {
final Set<AMXProxy> proxies = new HashSet<AMXProxy>();
for (final AMXProxy amx : proxies) {
final AMXProxyHandler proxy = AMXProxyHandler.unwrap(amx);
proxy.connectionBad();
}
}
/**
* Verify that the connection is still alive.
*/
public boolean checkConnection() {
boolean connectionGood = true;
try {
getMBeanServerConnection().isRegistered(JMXUtil.getMBeanServerDelegateObjectName());
connectionGood = true;
} catch (Exception e) {
connectionBad();
}
return (connectionGood);
}
void notifsLost() {
// should probably check each proxy for validity, but not clear if it's important...
}
/**
* Listens for MBeanServerNotification.UNREGISTRATION_NOTIFICATION and
* JMXConnectionNotification and takes appropriate action. <br> Used
* internally as callback for {@link javax.management.NotificationListener}.
* <b>DO NOT CALL THIS METHOD</b>.
*/
public void handleNotification(
final Notification notifIn,
final Object handback) {
final String type = notifIn.getType();
if (type.equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
// do nothing
} else if (notifIn instanceof JMXConnectionNotification) {
if (type.equals(JMXConnectionNotification.CLOSED)
|| type.equals(JMXConnectionNotification.FAILED)) {
debug("ProxyFactory.handleNotification: connection closed or failed: ", notifIn);
connectionBad();
} else if (type.equals(JMXConnectionNotification.NOTIFS_LOST)) {
debug("ProxyFactory.handleNotification: notifications lost: ", notifIn);
notifsLost();
}
} else {
debug("ProxyFactory.handleNotification: UNKNOWN notification: ", notifIn);
}
}
private final static String DOMAIN_ROOT_KEY = "DomainRoot";
public DomainRoot createDomainRoot()
throws IOException {
return (mDomainRoot);
}
public DomainRoot initDomainRoot()
throws IOException {
final ObjectName domainRootObjectName = getDomainRootObjectName();
final DomainRoot dr = getProxy(domainRootObjectName, DomainRoot.class);
return (dr);
}
/**
* Return the ObjectName for the DomainMBean.
*/
public ObjectName getDomainRootObjectName() {
return (mDomainRootObjectName);
}
/**
* Return the DomainRoot. AMX is guaranteed to be ready after this call
* returns.
*
* @return the DomainRoot for this factory.
*/
public DomainRoot getDomainRootProxy() {
return getDomainRootProxy(false);
}
/**
* If 'waitReady' is true, then upon return AMX is guaranteed to be fully
* loaded. Otherwise AMX MBeans may continue to initialize asynchronously.
*
* @param waitReady
* @return the DomainRoot for this factory.
*/
public DomainRoot getDomainRootProxy(boolean waitReady) {
if (waitReady) {
mDomainRoot.waitAMXReady();
}
return (mDomainRoot);
}
/**
* @return the JMX MBeanServerID for the MBeanServer in which MBeans reside.
*/
public String getMBeanServerID() {
return (mMBeanServerID);
}
/**
* Get an instance of the ProxyFactory for the MBeanServer. Generally not
* applicable for remote clients.
*
* @param server
*/
public static ProxyFactory getInstance(final MBeanServer server) {
return getInstance(server, true);
}
/**
* Get an instance of the ProxyFactory for the MBeanServerConnection.
* Creates a ConnectionSource for it and calls getInstance( connSource, true
* ).
*/
public static ProxyFactory getInstance(final MBeanServerConnection conn) {
return getInstance(conn, true);
}
/**
* Get an instance. If 'useMBeanServerID' is false, and the ConnectionSource
* is not one that has been passed before, a new ProxyFactory is
* instantiated which will not share its proxies with any
* previously-instantiated ones. Such usage is discouraged, as it duplicates
* proxies. Pass 'true' unless there is an excellent reason to pass 'false'.
*
* @param connSource the ConnectionSource
* @param useMBeanServerID use the MBeanServerID to determine if it's the
* same server
*/
public static synchronized ProxyFactory getInstance(
final MBeanServerConnection conn,
final boolean useMBeanServerID) {
ProxyFactory instance = findInstance(conn);
if (instance == null) {
try {
// if not found, match based on MBeanServerID as requested, or if this
// is an in-process MBeanServer
if (useMBeanServerID) {
final String id = JMXUtil.getMBeanServerID(conn);
instance = findInstanceByID(id);
}
if (instance == null) {
//debug( "Creating new ProxyFactory for ConnectionSource / conn", connSource, conn );
instance = new ProxyFactory(conn);
INSTANCES.put(conn, instance);
}
} catch (Exception e) {
warning("ProxyFactory.getInstance: failure creating ProxyFactory: ", e);
throw new RuntimeException(e);
}
}
return (instance);
}
/**
* @return ProxyFactory corresponding to the MBeanServerConnection
*/
public static synchronized ProxyFactory findInstance(final MBeanServerConnection conn) {
ProxyFactory instance = null;
final Collection<ProxyFactory> values = INSTANCES.values();
for (final ProxyFactory factory : values) {
if (factory.getMBeanServerConnection() == conn) {
instance = factory;
break;
}
}
return (instance);
}
/**
* @return ProxyFactory corresponding to the MBeanServerID
*/
public static synchronized ProxyFactory findInstanceByID(final String mbeanServerID) {
ProxyFactory instance = null;
final Collection<ProxyFactory> values = INSTANCES.values();
for (final ProxyFactory factory : values) {
if (factory.getMBeanServerID().equals(mbeanServerID)) {
instance = factory;
break;
}
}
return (instance);
}
/**
* Return (possibly cached) MBeanInfo. If the MBean does not exist, then
* null is returned.
*/
public MBeanInfo getMBeanInfo(final ObjectName objectName) {
try {
MBeanInfo info = mMBeanInfoCache.get(objectName);
if (info == null) {
// race condition: doesn't matter if two threads both get it
info = getMBeanServerConnection().getMBeanInfo(objectName);
if (invariantMBeanInfo(info)) {
mMBeanInfoCache.put(objectName, info);
}
}
return info;
} catch (final InstanceNotFoundException e) {
// OK, return null
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}
public static boolean invariantMBeanInfo(final MBeanInfo info) {
final Descriptor d = info.getDescriptor();
final String value = "" + d.getFieldValue(DESC_STD_IMMUTABLE_INFO);
return Boolean.valueOf(value);
}
/**
* @return MBeanServerConnection used by this factory
*/
protected MBeanServerConnection getMBeanServerConnection() {
return mMBeanServerConnection;
}
/**
* Get any existing proxy, returning null if none exists and 'create' is
* false. If an MBean is no longer registered, the proxy returned will be
* null.
*
* @param objectName ObjectName for which a proxy should be created
* @param intf class of returned proxy, avoids casts and compiler warnings
* @return an appropriate {@link AMXProxy} interface for the ObjectName
*/
public <T extends AMXProxy> T getProxy(
final ObjectName objectName,
Class<T> intf) {
final MBeanInfo info = getMBeanInfo(objectName);
if (info == null) {
return null;
}
final T proxy = getProxy(objectName, info, intf);
return proxy;
}
/**
* Call getProxy(objectName, getGenericAMXInterface()
*/
public AMXProxy getProxy(final ObjectName objectName) {
final MBeanInfo info = getMBeanInfo(objectName);
if (info == null) {
return null;
}
final Class<? extends AMXProxy> intf = genericInterface(info);
final AMXProxy proxy = getProxy(objectName, info, intf);
return proxy;
}
public static Class<? extends AMXProxy> genericInterface(final MBeanInfo info) {
final String intfName = AMXProxyHandler.genericInterfaceName(info);
Class<? extends AMXProxy> intf = AMXProxy.class;
if (intfName == null || AMXProxy.class.getName().equals(intfName)) {
intf = AMXProxy.class;
} else if (AMXConfigProxy.class.getName().equals(intfName)) {
intf = AMXConfigProxy.class;
} else if (intfName.startsWith(AMXProxy.class.getPackage().getName())) {
try {
intf = Class.forName(intfName, false, ProxyFactory.class.getClassLoader()).asSubclass(AMXProxy.class);
} catch (final Exception e) {
// ok, use generic
debug("ProxyFactory.getInterfaceClass(): Unable to load interface " + intfName);
}
} else {
intf = AMXProxy.class;
}
return intf;
}
/**
* NOTE: a null proxy may be returned if the MBean is no longer registered
*/
<T extends AMXProxy> T getProxy(
final ObjectName objectName,
final MBeanInfo mbeanInfoIn,
final Class<T> intfIn) {
//debug( "ProxyFactory.createProxy: " + objectName + " of class " + expected.getName() + " with interface " + JMXUtil.interfaceName(mbeanInfo) + ", descriptor = " + mbeanInfo.getDescriptor() );
AMXProxy proxy = null;
try {
MBeanInfo mbeanInfo = mbeanInfoIn;
if (mbeanInfo == null) {
mbeanInfo = getMBeanInfo(objectName);
}
// if it's a plain AMXProxy, it might have a more generic sub-interface we should use.
Class<? extends AMXProxy> intf = intfIn;
if (AMXProxy.class == intf) {
intf = genericInterface(mbeanInfoIn);
}
final AMXProxyHandler handler = new AMXProxyHandler(getMBeanServerConnection(), objectName, mbeanInfo);
proxy = (AMXProxy) Proxy.newProxyInstance(intf.getClassLoader(), new Class[]{intf}, handler);
//debug( "CREATED proxy of type " + intf.getName() + ", metadata specifies " + AMXProxyHandler.interfaceName(mbeanInfo) );
} catch (IllegalArgumentException e) {
//debug( "createProxy", e );
throw e;
} catch (Exception e) {
final Throwable rootCause = ExceptionUtil.getRootCause(e);
if (!(rootCause instanceof InstanceNotFoundException)) {
//debug( "createProxy", e );
throw new RuntimeException(e);
}
proxy = null;
}
return proxy == null ? null : intfIn.cast(proxy);
}
protected static String toString(final Object o) {
//return( org.glassfish.admin.amx.util.stringifier.SmartStringifier.toString( o ) );
return "" + o;
}
/**
* Array entries for MBeans that are no longer registered will contain null
* values.
*/
public AMXProxy[] toProxy(final ObjectName[] objectNames) {
final AMXProxy[] result = new AMXProxy[objectNames.length];
for (int i = 0; i < objectNames.length; ++i) {
result[i] = getProxy(objectNames[i]);
}
return result;
}
/**
* Convert a Set of ObjectName to a Set of AMX. The resulting Set may be
* smaller than the original if, for example, some MBeans are no longer
* registered.
*/
public Set<AMXProxy> toProxySet(final Set<ObjectName> objectNames) {
final Set<AMXProxy> s = new HashSet<AMXProxy>();
for (final ObjectName objectName : objectNames) {
try {
final AMXProxy proxy = getProxy(objectName);
if (proxy != null) {
s.add(proxy);
}
} catch (final Exception e) {
debug("ProxyFactory.toProxySet: exception for MBean ",
objectName, " = ", ExceptionUtil.getRootCause(e));
}
}
return (s);
}
/**
* Convert a Set of ObjectName to a Set of AMX. The resulting Set may be
* smaller than the original if, for example, some MBeans are no longer
* registered.
*/
public Set<AMXProxy> toProxySet(final ObjectName[] objectNames, final Class<? extends AMXProxy> intf) {
final Set<AMXProxy> result = new HashSet<AMXProxy>();
for (final ObjectName objectName : objectNames) {
final AMXProxy proxy = getProxy(objectName, intf);
if (proxy != null) {
result.add(proxy);
}
}
return (result);
}
/**
* Convert a Collection of ObjectName to a List of AMX. Resulting Map could
* differ in size if some MBeans are no longer registered.
*
* @return a List of AMX from a List of ObjectName.
*/
public List<AMXProxy> toProxyList(final Collection<ObjectName> objectNames) {
final List<AMXProxy> list = new ArrayList<AMXProxy>();
for (final ObjectName objectName : objectNames) {
try {
final AMXProxy proxy = getProxy(objectName);
if (proxy != null) {
list.add(proxy);
}
} catch (final Exception e) {
debug("ProxyFactory.toProxySet: exception for MBean ",
objectName, " = ", ExceptionUtil.getRootCause(e));
}
}
return (list);
}
/**
* Convert a Map of ObjectName, and convert it to a Map of AMX, with the
* same keys. Resulting Map could differ in size if some MBeans are no
* longer registered.
*
* @return a Map of AMX from a Map of ObjectName.
*/
public Map<String, AMXProxy> toProxyMap(
final Map<String, ObjectName> objectNameMap) {
final Map<String, AMXProxy> resultMap = new HashMap<String, AMXProxy>();
for (final Map.Entry<String, ObjectName> me : objectNameMap.entrySet()) {
final ObjectName objectName = me.getValue();
try {
final AMXProxy proxy = getProxy(objectName);
if (proxy != null) {
resultMap.put(me.getKey(), proxy);
}
} catch (final Exception e) {
debug("ProxyFactory.toProxySet: exception for MBean ",
objectName, " = ", ExceptionUtil.getRootCause(e));
}
}
return (resultMap);
}
/**
* Resulting Map could differ in size if some MBeans are no longer
* registered
*/
public Map<String, AMXProxy> toProxyMap(final ObjectName[] objectNames, final Class<? extends AMXProxy> intf) {
final Map<String, AMXProxy> resultMap = new HashMap<String, AMXProxy>();
for (final ObjectName objectName : objectNames) {
final String key = Util.unquoteIfNeeded(objectName.getKeyProperty(NAME_KEY));
final AMXProxy proxy = getProxy(objectName, intf);
if (proxy != null) {
resultMap.put(key, proxy);
}
}
return (resultMap);
}
/**
* Resulting list could differ in size if some MBeans are no longer
* registered
*/
public List<AMXProxy> toProxyList(final ObjectName[] objectNames, final Class<? extends AMXProxy> intf) {
final List<AMXProxy> result = new ArrayList<AMXProxy>();
for (final ObjectName objectName : objectNames) {
final AMXProxy proxy = getProxy(objectName, intf);
if (proxy != null) {
result.add(proxy);
}
}
return (result);
}
}