/******************************************************************************* * Copyright (c) 1998, 2016 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink * dmccann - Nov. 7/2008 - Added delegate key logic from AbstractHelperDelegator * Dmitry Kornilov - 2014/11/25 - ApplicationAccessWLS fixed to reflect changes in WLS ******************************************************************************/ package org.eclipse.persistence.sdo.helper; import commonj.sdo.Type; import commonj.sdo.helper.CopyHelper; import commonj.sdo.helper.DataFactory; import commonj.sdo.helper.DataHelper; import commonj.sdo.helper.EqualityHelper; import commonj.sdo.helper.HelperContext; import commonj.sdo.helper.TypeHelper; import commonj.sdo.helper.XMLHelper; import commonj.sdo.helper.XSDHelper; import commonj.sdo.impl.ExternalizableDelegator; import org.eclipse.persistence.exceptions.SDOException; import org.eclipse.persistence.internal.helper.Helper; import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; import org.eclipse.persistence.internal.security.PrivilegedGetMethod; import org.eclipse.persistence.sdo.SDOConstants; import org.eclipse.persistence.sdo.SDOResolvable; import org.eclipse.persistence.sdo.helper.delegates.SDODataFactoryDelegate; import org.eclipse.persistence.sdo.helper.delegates.SDOTypeHelperDelegate; import org.eclipse.persistence.sdo.helper.delegates.SDOTypeHelperDelegate.SDOWrapperTypeId; import org.eclipse.persistence.sdo.helper.delegates.SDOXMLHelperDelegate; import org.eclipse.persistence.sdo.helper.delegates.SDOXSDHelperDelegate; import org.eclipse.persistence.sdo.types.SDOWrapperType; import javax.management.AttributeChangeNotification; import javax.management.MBeanServer; import javax.management.MBeanServerFactory; import javax.management.Notification; import javax.management.NotificationFilterSupport; import javax.management.NotificationListener; import javax.management.ObjectName; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import java.io.File; import java.lang.ref.WeakReference; import java.lang.reflect.Method; import java.security.AccessController; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import java.util.logging.Logger; /** * <b>Purpose:</b> * <ul> * <li>This class represents a local HelperContext. The global * HelperContext can be accessed as HelperProvider.getDefaultContext().</li> * </ul> * <b>Responsibilities:</b> * <ul> * <li>Provide access to instances of helper objects.</li> * <li>Provide an OSGi compatible HelperContext (when the constructor that takes * a ClassLoader is used).</li> * </ul> * * @since Oracle TopLink 11.1.1.0.0 */ public class SDOHelperContext implements HelperContext { private static final Logger LOGGER = Logger.getLogger(SDOHelperContext.class.getName()); protected CopyHelper copyHelper; protected DataFactory dataFactory; protected DataHelper dataHelper; protected EqualityHelper equalityHelper; protected XMLHelper xmlHelper; protected TypeHelper typeHelper; protected XSDHelper xsdHelper; private String identifier; private Map<String, Object> properties; private boolean isStrictTypeCheckingEnabled = PrivilegedAccessHelper.getSystemPropertyBoolean( STRICT_TYPE_CHECKING_PROPERTY_NAME, true); /** * Property controls strictness of {@link Type#getInstanceClass()} type checking. * * <p> * See {@link #isStrictTypeCheckingEnabled()} for more details. * By this property, the initial value can be changed. * Default value is <tt>true</tt>. * </p> */ public static final String STRICT_TYPE_CHECKING_PROPERTY_NAME = "eclipselink.sdo.strict.type.checking"; // Each application will have its own helper context - it is assumed that application // names/loaders are unique within each active server instance private static ConcurrentHashMap<Object, ConcurrentHashMap<String, HelperContext>> helperContexts = new ConcurrentHashMap<Object, ConcurrentHashMap<String, HelperContext>>(); // Each application will have a Map of alias' to identifiers private static ConcurrentHashMap<Object, ConcurrentHashMap<String, String>> aliasMap = new ConcurrentHashMap<Object, ConcurrentHashMap<String, String>>(); // Each application could have separate HelperContextResolver private static final ConcurrentHashMap<Object, HelperContextResolver> HELPER_CONTEXT_RESOLVERS = new ConcurrentHashMap<>(); // allow users to set their own classloader to context map pairs private static WeakHashMap<ClassLoader, WeakHashMap<String, WeakReference<HelperContext>>> userSetHelperContexts = new WeakHashMap<ClassLoader, WeakHashMap<String, WeakReference<HelperContext>>>(); // keep a map of application names to application class loaders to handle redeploy private static ConcurrentHashMap<String, ClassLoader> appNameToClassLoaderMap = new ConcurrentHashMap<String, ClassLoader>(); // keep a map of application name to the wrapper types for that application private static final ConcurrentHashMap<String,Map<SDOWrapperTypeId,SDOWrapperType>> SDO_WRAPPER_TYPES = new ConcurrentHashMap<>(); // Application server identifiers private static String OC4J_CLASSLOADER_NAME = "oracle"; private static String WLS_CLASSLOADER_NAME = "weblogic"; private static String WAS_CLASSLOADER_NAME = "com.ibm.ws"; private static String JBOSS_CLASSLOADER_NAME = "jboss"; private static String GLOBAL_HELPER_IDENTIFIER = ""; private static final int WLS_IDENTIFIER = 0; private static final int JBOSS_IDENTIFIER = 1; // Common private static final int COUNTER_LIMIT = 20; // For WebLogic private static volatile ApplicationAccessWLS applicationAccessWLS = null; private static MBeanServer wlsMBeanServer = null; private static ObjectName wlsThreadPoolRuntime = null; private static final String WLS_ENV_CONTEXT_LOOKUP = "java:comp/env/jmx/runtime"; private static final String WLS_CONTEXT_LOOKUP = "java:comp/jmx/runtime"; private static final String WLS_RUNTIME_SERVICE = "RuntimeService"; private static final String WLS_SERVICE_KEY = "com.bea:Name=RuntimeService,Type=weblogic.management.mbeanservers.runtime.RuntimeServiceMBean"; private static final String WLS_APP_RUNTIMES = "ApplicationRuntimes"; private static final String WLS_SERVER_RUNTIME = "ServerRuntime"; private static final String WLS_THREADPOOL_RUNTIME = "ThreadPoolRuntime"; private static final String WLS_EXECUTE_THREAD = "ExecuteThread"; private static final String WLS_MBEAN_SERVER = "MBeanServer"; private static final String WLS_EXECUTE_THREAD_GET_METHOD_NAME = "getExecuteThread"; private static final String WLS_APPLICATION_NAME = "ApplicationName"; private static final String WLS_APPLICATION_VERSION = "ApplicationVersion"; private static final String WLS_APPLICATION_NAME_GET_METHOD_NAME = "getApplicationName"; private static final String WLS_ACTIVE_VERSION_STATE = "ActiveVersionState"; private static final Class[] WLS_PARAMETER_TYPES = {}; // For WebSphere private static final String WAS_NEWLINE = "\n"; private static final String WAS_APP_COLON = "[app:"; private static final String WAS_CLOSE_BRACKET = "]"; // For JBoss private static MBeanServer jbossMBeanServer = null; private static String JBOSS_SERVICE_CONTROLLER = "jboss.system:service=ServiceController"; private static String JBOSS_TYPE_STOP = "org.jboss.system.ServiceMBean.stop"; private static String JBOSS_ID_KEY = "id"; private static final String JBOSS_DEFAULT_DOMAIN_NAME = "jboss"; private static final String JBOSS_VFSZIP = "vfszip:"; private static final String JBOSS_VFSFILE = "vfsfile:"; private static final String JBOSS_EAR = ".ear"; private static final String JBOSS_JAR = ".jar"; private static final String JBOSS_WAR = ".war"; private static final int JBOSS_VFSZIP_OFFSET = JBOSS_VFSZIP.length(); private static final int JBOSS_VFSFILE_OFFSET = JBOSS_VFSFILE.length(); private static final int JBOSS_EAR_OFFSET = JBOSS_EAR.length(); private static final int JBOSS_TRIM_COUNT = 2; // for stripping off the remaining '/}' chars // allow users to provide application info private static ApplicationResolver appResolver; private static boolean isAppResolverSet = false; /** * Default strategy for HelperContext creation. * Singleton. */ private static final HelperContextResolver DEFAULT_HCR = new DefaultHelperContextResolver(); /** * ADVANCED: * Used to set an ApplicationResolver instance that will be used to retrieve * info pertaining to a given application, such as the application name, in * the case where our logic fails. * * This method can be called once and only once per active server instance. * * @param aResolver the ApplicationResolver instance that will be used to retrieve * info pertaining to a given application. Note that null is * considered a valid set operation. * @throws SDOException if more than one call is made to this method * in an active server instance. */ public static void setApplicationResolver(ApplicationResolver aResolver) { // we only allow one set operation per running server instance if (isApplicationResolverSet()) { throw SDOException.attemptToResetApplicationResolver(); } appResolver = aResolver; isAppResolverSet = true; } /** * Indicates if a call to setApplicationResolver has been made. * * @return true if a prior call to setApplicationResolver has * been made, false otherwise */ public static boolean isApplicationResolverSet() { return isAppResolverSet; } /** * Create a local HelperContext. The current thread's context ClassLoader * will be used to find static instance classes. In OSGi environments the * construct that takes a ClassLoader parameter should be used instead. */ public SDOHelperContext() { this(Thread.currentThread().getContextClassLoader()); } /** * Create a local HelperContext with the given identifier. The current * thread's context ClassLoader will be used to find static instance * classes. In OSGi environments the construct that takes a ClassLoader * parameter should be used instead. * * @param identifier The unique label for this HelperContext. */ public SDOHelperContext(String identifier) { this(identifier, Thread.currentThread().getContextClassLoader()); } /** * Create a local HelperContext. This constructor should be used in OSGi * environments. * * @param aClassLoader This class loader will be used to find static * instance classes. */ public SDOHelperContext(ClassLoader aClassLoader) { super(); this.identifier = this.GLOBAL_HELPER_IDENTIFIER; initialize(aClassLoader); } /** * Create a local HelperContext with the given identifier. This constructor * should be used in OSGi environments. * * @param identifier The unique label for this HelperContext. * @param aClassLoader This class loader will be used to find static * instance classes. */ public SDOHelperContext(String identifier, ClassLoader aClassLoader) { super(); this.identifier = identifier; initialize(aClassLoader); } /** * The underlying helpers for this instance will be instantiated * in this method. * * @param aClassLoader */ protected void initialize(ClassLoader aClassLoader) { copyHelper = new SDOCopyHelper(this); dataFactory = new SDODataFactoryDelegate(this); dataHelper = new SDODataHelper(this); equalityHelper = new SDOEqualityHelper(this); xmlHelper = new SDOXMLHelperDelegate(this, aClassLoader); typeHelper = new SDOTypeHelperDelegate(this); xsdHelper = new SDOXSDHelperDelegate(this); } /** * Reset the Type,XML and XSD helper instances. */ public void reset() { ((SDOTypeHelper)getTypeHelper()).reset(); ((SDOXMLHelper)getXMLHelper()).reset(); ((SDOXSDHelper)getXSDHelper()).reset(); } /** * Return the CopyHelper instance for this helper context. */ public CopyHelper getCopyHelper() { return copyHelper; } /** * Return the DataFactory instance for this helper context. */ public DataFactory getDataFactory() { return dataFactory; } /** * Return the DataHelper instance for this helper context. */ public DataHelper getDataHelper() { return dataHelper; } /** * Return the EqualityHelper instance for this helper context. */ public EqualityHelper getEqualityHelper() { return equalityHelper; } /** * Return the TypeHelper instance for this helper context. */ public TypeHelper getTypeHelper() { return typeHelper; } /** * Return the XMLHelper instance for this helper context. */ public XMLHelper getXMLHelper() { return xmlHelper; } /** * Return the XSDHelper instance for this helper context. */ public XSDHelper getXSDHelper() { return xsdHelper; } /** * Create and return a new ExternalizableDelegator.Resolvable instance based * on this helper context. * * @return */ public ExternalizableDelegator.Resolvable createResolvable() { return new SDOResolvable(this); } /** * Create and return a new ExternalizableDelegator.Resolvable instance based * on this helper context and a given target. * * @param target * @return */ public ExternalizableDelegator.Resolvable createResolvable(Object target) { return new SDOResolvable(target, this); } /** * INTERNAL: * Put a ClassLoader/HelperContext key/value pair in the Thread HelperContext * map. If Thread.currentThread().getContextClassLoader() == key during * getHelperContext() call then the HelperContext (value) will be returned. * This method will overwrite an existing entry in the map with the same * ClassLoader key. * * @param key class loader * @param value helper context */ public static void putHelperContext(ClassLoader key, HelperContext value) { if (key == null || value == null) { return; } WeakHashMap<String, WeakReference<HelperContext>> currentMap = userSetHelperContexts.get(key); if(currentMap == null) { currentMap = new WeakHashMap<String, WeakReference<HelperContext>>(); userSetHelperContexts.put(key, currentMap); } currentMap.put(((SDOHelperContext) value).getIdentifier(), new WeakReference(value)); } /** * INTERNAL: * Retrieve the HelperContext for a given ClassLoader from the Thread * HelperContext map. * * @param key class loader * @return HelperContext for the given key if key exists in the map, otherwise null */ private static HelperContext getUserSetHelperContext(String identifier, ClassLoader key) { if (key == null) { return null; } WeakHashMap<String, WeakReference<HelperContext>> currentMap = userSetHelperContexts.get(key); if(currentMap == null) { return null; } WeakReference<HelperContext> ref = currentMap.get(identifier); if(ref == null) { return null; } return ref.get(); } /** * INTERNAL: * Remove a ClassLoader/HelperContext key/value pair from the Thread * HelperContext map. If there are multiple local helper contexts associated * with this ClassLoader, they will all be removed from the map. * * @param key class loader */ public static void removeHelperContext(ClassLoader key) { if (key == null) { return; } userSetHelperContexts.remove(key); } /** * INTERNAL * @param identifier the specific identifier of the HelperContext to be removed. "" for a Global helper * @param key the ClassLoader associated with the HelperContext to be removed */ public static void removeHelperContext(String identifier, ClassLoader key) { if(key == null) { return; } WeakHashMap<String, WeakReference<HelperContext>> currentMap = userSetHelperContexts.get(key); if(currentMap != null) { currentMap.remove(identifier); } } /** * INTERNAL: * Return the helper context for a given key. The key will either * be a ClassLoader or a String (representing an application name). * A new context will be created and put in the map if none exists * for the given key. * * The key is assumed to be non-null - getDelegateKey should always * return either a string representing the application name (for WLS, * WAS and JBoss if available) or a class loader. This is relevant * since 'putIfAbsent' will throw a null pointer exception if the * key is null. */ public static HelperContext getHelperContext() { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); // check the map for contextClassLoader and return it if it exists HelperContext hCtx = getUserSetHelperContext(GLOBAL_HELPER_IDENTIFIER, contextClassLoader); if (hCtx != null) { return hCtx; } return getHelperContext(GLOBAL_HELPER_IDENTIFIER); } /** * Return the local helper context associated with the given identifier, or * create one if it does not already exist. If identifier is an alias, the * value associated with it in the alias Map will be used as the identifier * value. * * @param identifier the identifier or alias to use for lookup/creation * @return HelperContext associated with identifier, or a new HelperContext * keyed on identifier if none eixsts */ public static HelperContext getHelperContext(String identifier) { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); return getHelperContext(identifier, contextClassLoader); } /** * Return the local helper context with the given identifier, or create * one if it does not already exist. */ public static HelperContext getHelperContext(String identifier, ClassLoader classLoader) { // if identifier is an alias, we need the actual id value ConcurrentMap<String, String> aliasEntries = getAliasMap(); if (aliasEntries.containsKey(identifier)) { identifier = aliasEntries.get(identifier); } HelperContext helperContext = getUserSetHelperContext(identifier, classLoader); if (helperContext != null) { return helperContext; } ConcurrentMap<String, HelperContext> contextMap = getContextMap(); helperContext = contextMap.get(identifier); if (null == helperContext) { LOGGER.fine("helperContext not found."); helperContext = getHelperContextResolver().getHelperContext(identifier, classLoader); HelperContext existingContext = contextMap.putIfAbsent(identifier, helperContext); if (existingContext != null) { LOGGER.fine(String.format("contextMap already has context for id: %s. Existing one will be used.", identifier)); helperContext = existingContext; } } return helperContext; } /** * Returns the map of helper contexts, keyed on Identifier, for the current application * @return */ static ConcurrentMap<String, HelperContext> getContextMap() { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); String classLoaderName = contextClassLoader.getClass().getName(); // get a MapKeyLookupResult instance based on the context loader MapKeyLookupResult hCtxMapKey = getContextMapKey(contextClassLoader, classLoaderName); // at this point we will have a loader and possibly an application name String appName = hCtxMapKey.getApplicationName(); ClassLoader appLoader = hCtxMapKey.getLoader(); // we will use the application name as the map key if set; otherwise we use the loader Object contextMapKey = appName != null ? appName : appLoader; ConcurrentHashMap<String, HelperContext> contextMap = helperContexts.get(contextMapKey); // handle possible redeploy // the following block only applies to WAS - hence the loader name check if (contextMap != null && appName != null && classLoaderName.contains(WAS_CLASSLOADER_NAME)) { // at this point there is an existing entry in the map - if the context is keyed // on application name we need to check to see if a redeployment occurred; in // that case the app names will match, but the class loaders will not ClassLoader currentAppLoader = appNameToClassLoaderMap.get(appName); if (currentAppLoader != null && currentAppLoader != appLoader) { // concurrency - use remove(key, value) to ensure we don't remove a newly added entry appNameToClassLoaderMap.remove(appName, currentAppLoader); helperContexts.remove(appName, contextMap); contextMap = null; } } // may need to add a new entry if (null == contextMap) { contextMap = new ConcurrentHashMap<String, HelperContext>(); // use putIfAbsent to avoid concurrent entries in the map ConcurrentHashMap existingMap = helperContexts.putIfAbsent(contextMapKey, contextMap); if (existingMap != null) { // if a new entry was just added, use it instead of the one we just created contextMap = existingMap; } else if (appName != null) { // add an appName/appLoader pair to the appNameToClassLoader map appNameToClassLoaderMap.put(appName, appLoader); if (classLoaderName.contains(WLS_CLASSLOADER_NAME)) { // add a loader/context pair to the helperContexts map to handle case where appName // is no longer available, but the loader from a previous lookup is being used helperContexts.put(appLoader, contextMap); // add a notification listener to handle redeploy addWLSNotificationListener(appName); } else if (classLoaderName.contains(JBOSS_CLASSLOADER_NAME)) { // add a notification listener to handle redeploy - the listener will only be added once addJBossNotificationListener(); } } } return contextMap; } /** * Replaces the provided helper context in the map of identifiers to * helper contexts for this application. ctx.getIdentifier() will be * used to obtain the identifier value. If identifier is a key in the * the alias Map, i.e. was previously set as alias, the corresponding * entry will be removed from the alias Map. * * @param ctx the HelperContext to be added to the context Map for * the current application */ public static void putHelperContext(HelperContext ctx) { String identifier = ((SDOHelperContext) ctx).getIdentifier(); if (GLOBAL_HELPER_IDENTIFIER.equals(identifier)) { // The global HelperContext cannot be replaced return; } getContextMap().put(identifier, ctx); // identifier may have been an alias at one point getAliasMap().remove(identifier); } /** * ADVANCED: * Remove the HelperContext for the application associated with a * given key, if it exists in the map. */ private static void resetHelperContext(String key) { // remove entry from helperContext map boolean successHc = removeAppFromMap(helperContexts, key, false); // remove app's helperContextResolver boolean successHcr = removeAppFromMap(HELPER_CONTEXT_RESOLVERS, key, true); if (LOGGER.isLoggable(Level.WARNING) && !successHc && !successHcr) { LOGGER.warning("No entries found in maps for application:" + key); } // remove the appName entry in the appNameToClassLoader map appNameToClassLoaderMap.remove(key); // remove static SDOWrapperType instances bound to this application SDO_WRAPPER_TYPES.remove(key); // remove the alias map for this app aliasMap.remove(key); } /** * Trying to remove entry for a given app the provided map. * * @param map from which app value should be removed * @param appName application name * @param removeDefaultClassloader whether to try removing the default classloader * @return true if any removal took place */ private static boolean removeAppFromMap(Map map, String appName, boolean removeDefaultClassloader) { boolean result = map.remove(appName) != null; // there may be a loader/context pair to remove ClassLoader appLoader = appNameToClassLoaderMap.get(appName); if (appLoader != null) { result = result | map.remove(appLoader) != null; } else if (removeDefaultClassloader) { // try with Thread ContextClassLoader result = result | map.remove(Thread.currentThread().getContextClassLoader()) != null; } return result; } /** * INTERNAL: * Return the key to be used for Map lookups based in the current thread's * context loader. The returned value will be the application name (if * available) or the context loader. * */ private static Object getMapKey() { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); String classLoaderName = contextClassLoader.getClass().getName(); // get a MapKeyLookupResult instance based on the context loader MapKeyLookupResult hCtxMapKey = getContextMapKey(contextClassLoader, classLoaderName); // at this point we will have a loader and possibly an application name String appName = hCtxMapKey.getApplicationName(); ClassLoader appLoader = hCtxMapKey.getLoader(); // we will use the application name as the map key if set; otherwise we use the loader return appName != null ? appName : appLoader; } /** * Bug #506919 * Retrieves the application name for current {@link ClassLoader}. * * @param classLoader the ClassLoader to use for searching application name * @return the application name */ private static String getApplicationName(ClassLoader classLoader) { String classLoaderName = classLoader.getClass().getName(); // get a MapKeyLookupResult instance based on the context loader MapKeyLookupResult hCtxMapKey = getContextMapKey(classLoader, classLoaderName); // at this point we will have a loader and possibly an application name String applicationName = hCtxMapKey.getApplicationName(); return applicationName != null? applicationName : "DEFAULT"; } /** * INTERNAL: * This method will return the MapKeyLookupResult instance to be used to * store/retrieve the global helper context for a given application. * * OC4J classLoader levels: * 0 - APP.web (servlet/jsp) or APP.wrapper (ejb) * 1 - APP.root (parent for helperContext) * 2 - default.root * 3 - system.root * 4 - oc4j.10.1.3 (remote EJB) or org.eclipse.persistence:11.1.1.0.0 * 5 - api:1.4.0 * 6 - jre.extension:0.0.0 * 7 - jre.bootstrap:1.5.0_07 (with various J2SE versions) * * @return MapKeyLookupResult wrapping the application classloader for OC4J, * the application name for WebLogic and WebSphere, the archive file * name for JBoss - if available; otherwise a MapKeyLookupResult * wrapping Thread.currentThread().getContextClassLoader() */ private static MapKeyLookupResult getContextMapKey(ClassLoader classLoader, String classLoaderName) { // Helper contexts in OC4J server will be keyed on classloader if (classLoaderName.startsWith(OC4J_CLASSLOADER_NAME)) { // Check to see if we are running in a Servlet container or a local EJB container if ((classLoader.getParent() != null) // && ((classLoader.toString().indexOf(SDOConstants.CLASSLOADER_WEB_FRAGMENT) != -1) // || (classLoader.toString().indexOf(SDOConstants.CLASSLOADER_EJB_FRAGMENT) != -1))) { classLoader = classLoader.getParent(); } return new MapKeyLookupResult(classLoader); } // Helper contexts in WebLogic server will be keyed on application name if available if (classLoaderName.contains(WLS_CLASSLOADER_NAME)) { if (null == applicationAccessWLS) { synchronized (SDOHelperContext.class) { if (applicationAccessWLS == null) { applicationAccessWLS = new ApplicationAccessWLS(); } } } Object appName = applicationAccessWLS.getApplicationName(classLoader); if (appName != null) { return new MapKeyLookupResult(appName.toString(), classLoader); } Object executeThread = getExecuteThread(); if (executeThread != null) { try { Method getMethod = PrivilegedAccessHelper.getPublicMethod(executeThread.getClass(), WLS_APPLICATION_NAME_GET_METHOD_NAME, WLS_PARAMETER_TYPES, false); appName = PrivilegedAccessHelper.invokeMethod(getMethod, executeThread); } catch (Exception e) { throw SDOException.errorInvokingWLSMethodReflectively(WLS_APPLICATION_NAME_GET_METHOD_NAME, WLS_EXECUTE_THREAD, e); } } // if ExecuteThread is null or doesn't return the app name, attempt // to use the user-set ApplicationResolver (if set) if (appName == null && appResolver != null) { appName = appResolver.getApplicationName(); } // use the application name if set, otherwise key on the class loader if (appName != null) { return new MapKeyLookupResult(appName.toString(), classLoader); } // couldn't get the application name, so default to the context loader return new MapKeyLookupResult(classLoader); } // Helper contexts in WebSphere server will be keyed on application name if available if (classLoaderName.contains(WAS_CLASSLOADER_NAME)) { return getContextMapKeyForWAS(classLoader); } // Helper contexts in JBoss server will be keyed on archive file name if available if (classLoaderName.contains(JBOSS_CLASSLOADER_NAME)) { return getContextMapKeyForJBoss(classLoader); } // at this point we will default to the context loader return new MapKeyLookupResult(classLoader); } /** * Lazy load the WebLogic MBeanServer instance. * * @return */ private static MBeanServer getWLSMBeanServer() { if (wlsMBeanServer == null) { Context weblogicContext = null; try { weblogicContext = new InitialContext(); try { // The lookup string used depends on the context from which this class is being // accessed, i.e. servlet, EJB, etc. Try java:comp/env lookup wlsMBeanServer = (MBeanServer) weblogicContext.lookup(WLS_ENV_CONTEXT_LOOKUP); } catch (NamingException e) { // Lookup failed - try java:comp try { wlsMBeanServer = (MBeanServer) weblogicContext.lookup(WLS_CONTEXT_LOOKUP); } catch (NamingException ne) { throw SDOException.errorPerformingWLSLookup(WLS_MBEAN_SERVER, ne); } } } catch (NamingException nex) { throw SDOException.errorCreatingWLSInitialContext(nex); } } return wlsMBeanServer; } /** * INTERNAL: * This convenience method will look up a WebLogic execute thread from the runtime * MBean tree. The execute thread contains application information. This code * will use the name of the current thread to lookup the corresponding ExecuteThread. * The ExecuteThread will allow us to obtain the application name (and version, etc). * * @return application name or null if the name cannot be obtained */ private static Object getExecuteThread() { if (getWLSMBeanServer() != null) { // Lazy load the ThreadPoolRuntime instance if (wlsThreadPoolRuntime == null) { ObjectName service = null; ObjectName serverRuntime = null; try { service = new ObjectName(WLS_SERVICE_KEY); } catch (Exception x) { throw SDOException.errorGettingWLSObjectName(WLS_RUNTIME_SERVICE + " [" + WLS_SERVICE_KEY + "]", x); } try { serverRuntime = (ObjectName) wlsMBeanServer.getAttribute(service, WLS_SERVER_RUNTIME); } catch (Exception x) { throw SDOException.errorGettingWLSObjectName(WLS_SERVER_RUNTIME, x); } try { wlsThreadPoolRuntime = (ObjectName) wlsMBeanServer.getAttribute(serverRuntime, WLS_THREADPOOL_RUNTIME); } catch (Exception x) { throw SDOException.errorGettingWLSObjectName(WLS_THREADPOOL_RUNTIME, x); } } try { return wlsMBeanServer.invoke(wlsThreadPoolRuntime, WLS_EXECUTE_THREAD_GET_METHOD_NAME, new Object[] { Thread.currentThread().getName() }, new String[] { String.class.getName() }); } catch (Exception x) { throw SDOException.errorInvokingWLSMethodReflectively(WLS_EXECUTE_THREAD_GET_METHOD_NAME, WLS_THREADPOOL_RUNTIME, x); } } return null; } /** * INTERNAL: * Adds a notification listener to the ApplicationRuntimeMBean instance with "ApplicationName" * attribute equals to 'mapKey.applicationName'. The listener will handle application * re-deployment. * * If any errors occur, we will fail silently, i.e. the listener will not be added. * * This method should only be called when running in an active WLS instance. * * @param applicationName */ private static void addWLSNotificationListener(String applicationName) { try { if (getWLSMBeanServer() != null) { ObjectName service = new ObjectName(WLS_SERVICE_KEY); ObjectName serverRuntime = (ObjectName) wlsMBeanServer.getAttribute(service, WLS_SERVER_RUNTIME); ObjectName[] appRuntimes = (ObjectName[]) wlsMBeanServer.getAttribute(serverRuntime, WLS_APP_RUNTIMES); for (int i=0; i < appRuntimes.length; i++) { try { ObjectName appRuntime = appRuntimes[i]; Object appName = wlsMBeanServer.getAttribute(appRuntime, WLS_APPLICATION_NAME); Object appVersion = wlsMBeanServer.getAttribute(appRuntime, WLS_APPLICATION_VERSION); String appIdentifier = null; if (appName != null) { if (appVersion != null) { appIdentifier = appName.toString() + "#" + appVersion.toString(); } else { appIdentifier = appName.toString(); } if (appIdentifier != null && appIdentifier.equals(applicationName)) { wlsMBeanServer.addNotificationListener(appRuntime, new MyNotificationListener(applicationName, WLS_IDENTIFIER), null, null); break; } } } catch (Exception ex) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Failed to retrieve application name in runtime " + appRuntimes[i].toString() + ":\n" + Helper.printStackTraceToString(ex)); } } } } } catch (Exception x) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.warning("Failed to add notification listener for application " + applicationName + ":\n" + Helper.printStackTraceToString(x)); } } } /** * INTERNAL: * The listener will handle application re-deployment. If any errors occur, we will * fail silently, i.e. the listener will not be added. Since we cannot register for * notifications for a particular application, and hence recieve notifications for * all apps that are redeployed, we only add the listener once - each time we receive * notification we will check the map and remove the corresponding entry if it exists. * * This method should only be called when running in an active JBoss instance. * */ private static void addJBossNotificationListener() { if (jbossMBeanServer == null) { List<MBeanServer> mbeanServers = MBeanServerFactory.findMBeanServer(null); for (MBeanServer server : mbeanServers) { if (server.getDefaultDomain().equals(JBOSS_DEFAULT_DOMAIN_NAME)) { jbossMBeanServer = server; try { jbossMBeanServer.addNotificationListener(new ObjectName(JBOSS_SERVICE_CONTROLLER), new MyNotificationListener(JBOSS_IDENTIFIER), new MyNotificationFilter(), null); } catch (Exception e) {} break; } } } } /** * Getter for HelperContextResolver * @return actual strategy */ public static HelperContextResolver getHelperContextResolver() { Object key = getMapKey(); HelperContextResolver result = HELPER_CONTEXT_RESOLVERS.get(key); if (result == null) { result = DEFAULT_HCR; HELPER_CONTEXT_RESOLVERS.putIfAbsent(key, result); } return result; } /** * Method allows dynamically change HelperContext creation strategy. * * @param helperContextResolver on this object {@link HelperContextResolver#getHelperContext(String, ClassLoader)} will be called. * If it is null - then default strategy will be set. */ public static void setHelperContextResolver(Object helperContextResolver) { Object key = getMapKey(); if (helperContextResolver == null) HELPER_CONTEXT_RESOLVERS.put(key, DEFAULT_HCR); else HELPER_CONTEXT_RESOLVERS.put(key, new ReflectionHelperContextResolver(helperContextResolver)); } /** * Method allows dynamically change HelperContext creation strategy. * * @param helperContextResolver strategy to be used. If it is null - then default strategy will be set. */ public static void setHelperContextResolver(HelperContextResolver helperContextResolver) { Object key = getMapKey(); if (helperContextResolver == null) HELPER_CONTEXT_RESOLVERS.put(key, DEFAULT_HCR); else HELPER_CONTEXT_RESOLVERS.put(key, helperContextResolver); } /** * Removes HelperContextResolver for the current application. * Application is resolved based on applicationName or classLoader. */ public static void removeHelerContextResolver() { HELPER_CONTEXT_RESOLVERS.remove(getMapKey()); } /** * INTERNAL: * This class will be handed in as a parameter when adding a JBoss notification listener. * The purpose of this class is to restrict notifications to "stop", i.e. we don't * care about "destroy", "start" etc. * */ public static class MyNotificationFilter extends NotificationFilterSupport { MyNotificationFilter() { super.enableType(JBOSS_TYPE_STOP); } } /** * INTERNAL: * Inner class used to catch application re-deployment. Upon notification of this event, * the helper context for the given application will be removed from the helper context * to application map. This method will also remove the corresponding entry in the * application name to application loader map if necessary. * * For WebLogic, 'appName' will be set; we will regester a listener for each application * name we key a context on, and will only receive a notification if a particular app * is redeployed. Upon notification we will remove the context entry for 'appName'. * * For JBoss, 'appName' will not be set; we will only register the listener once, * and each time we're notified of an application redeployment, we will reset the * associated helper context if one exists. * */ private static class MyNotificationListener implements NotificationListener { int server; String appName; /** * This is the default constructor - typically used when running in an * actove JBoss instance. * * @param server */ public MyNotificationListener(int server) { this.server = server; } /** * This constructor will set 'appName' - typically used when running in * an active WebLogic instance. * * @param appName * @param server */ public MyNotificationListener(String appName, int server) { this.server = server; this.appName = appName; } public void handleNotification(Notification notification, Object handback) { switch (server) { case 0: { // handle WebLogic notification if (notification instanceof AttributeChangeNotification) { AttributeChangeNotification acn = (AttributeChangeNotification) notification; if (acn.getAttributeName().equals(WLS_ACTIVE_VERSION_STATE)) { if (acn.getNewValue().equals(0)) { resetHelperContext(appName); } } } break; } case 1: { // handle JBoss notification // assumes 'user data' is an ObjectName containing an 'id' property which indicates the archive file name appName = getApplicationNameFromJBossClassLoader(((ObjectName) notification.getUserData()).getKeyProperty(JBOSS_ID_KEY)); // we receive notifications for all service stops in JBoss; only call reset if necessary if (helperContexts.containsKey(appName)) { resetHelperContext(appName); } break; } } } } /** * ADVANCED * Promote this helper context to be the default or global one. * This will completely replace the existing default context including * all types and properties defined. */ public void makeDefaultContext() { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); MapKeyLookupResult hCtxMapKey = getContextMapKey(contextClassLoader, contextClassLoader.getClass().getName()); String appName = hCtxMapKey.getApplicationName(); ClassLoader appLoader = hCtxMapKey.getLoader(); Object contextMapKey = appName != null ? appName : appLoader; ConcurrentHashMap<String, HelperContext> contexts = helperContexts.get(contextMapKey); if (contexts == null) { contexts = new ConcurrentHashMap<String, HelperContext>(); ConcurrentHashMap<String, HelperContext> existingContexts = helperContexts.putIfAbsent(contextMapKey, contexts); if (existingContexts != null) { contexts = existingContexts; } else if (appName != null) { appNameToClassLoaderMap.put(appName, appLoader); } } this.identifier = GLOBAL_HELPER_IDENTIFIER; contexts.put(GLOBAL_HELPER_IDENTIFIER, this); } /** * Attempt to return the WAS application name based on a given class loader. * For WAS, the application loader's toString will contain "[app:". * * @param loader * @return String representing the application name, or null if the loader's toString * doesn't contain "[app:". */ private static String getApplicationNameFromWASClassLoader(final ClassLoader loader) { String applicationName = null; String loaderString = loader.toString().trim(); while ((loaderString.startsWith(WAS_NEWLINE)) && (loaderString.length() > 0)) { loaderString = loaderString.substring(1).trim(); } String loaderStringLines[] = loaderString.split(WAS_NEWLINE, 2); if (loaderStringLines.length > 0) { String firstLine = loaderStringLines[0].trim(); int appPos = firstLine.indexOf(WAS_APP_COLON); if ((appPos >= 0) && (appPos + WAS_APP_COLON.length() < firstLine.length())) { String appNameSegment = firstLine.substring(appPos + WAS_APP_COLON.length()); int closingBracketPosition = appNameSegment.indexOf(WAS_CLOSE_BRACKET); if (closingBracketPosition > 0) { applicationName = appNameSegment.substring(0, closingBracketPosition); } else { applicationName = appNameSegment; } } } return applicationName; } /** * Attempt to return a MapKeyLookupResult instance wrapping the application name and * application loader based on a given WAS classloader. Here we will traverse up the * loader hierarchy looking for the top-most application loader. * * For WAS, the application loader's toString (and those of it's children) will * contain "[app:". * * @param loader * @return a MapKeyLookupResult instance wrapping application name/loader if * successfully retrieved (i.e. at least one loader exists in the * hierarchy with toString containing "[app:"), or a MapKeyLookupResult * instance wrapping the given loader if not found */ private static MapKeyLookupResult getContextMapKeyForWAS(ClassLoader loader) { ClassLoader applicationLoader = loader; String applicationName = null; // Safety counter to keep from taking too long or looping forever, just in case of some unexpected circumstance. int i = 0; // iterate up the loader hierarchy looking for the top-level application loader while (i < COUNTER_LIMIT) { if (wasClassLoaderHasApplicationName(loader)) { // current loader has application name info - store it applicationLoader = loader; } final ClassLoader parent = loader.getParent(); // once we have hit the top we will stop looking if (parent == null || parent == loader) { // get the application name from the loader we are going to return applicationName = getApplicationNameFromWASClassLoader(applicationLoader); break; } // move up and try again loader = parent; i++; } // if we found the application name, use it as the key if (applicationName != null) { return new MapKeyLookupResult(applicationName, applicationLoader); } // at this point we don't know the application name so the loader will be the key return new MapKeyLookupResult(applicationLoader); } /** * Indicates if a given WAS class loader contains a application name. * * Assumptions: * 1 - The toString of a WAS application loader will contain "[app:". * * @param loader * @return true if the WAS class loader's toString contains "[app:"; false otherwise */ private static boolean wasClassLoaderHasApplicationName(ClassLoader loader) { String loaderString = loader.toString().trim(); while ((loaderString.startsWith(WAS_NEWLINE)) && (loaderString.length() > 0)) { loaderString = loaderString.substring(1).trim(); } String loaderStringLines[] = loaderString.split(WAS_NEWLINE, 2); if (loaderStringLines.length > 0) { String firstLine = loaderStringLines[0].trim(); int appPos = firstLine.indexOf(WAS_APP_COLON); if ((appPos >= 0) && (appPos + WAS_APP_COLON.length() < firstLine.length())) { return true; } } return false; } /** * Attempt to get the application name (archive file name) based on a given JBoss classloader. * * Here is an example toString result of the classloader which loaded the application in JBoss: * BaseClassLoader@1316dd{vfszip:/ade/xidu_j2eev5/oracle/work/utp/resultout/functional/jrf/ * jboss-jrfServer/deploy/jrftestapp.jar/} * or {vfsfile:/net/stott18.ca.oracle.com/scratch/xidu/view_storage/xidu_j2eev5/work/jboss/ * server/default/deploy/testapp.ear/} in exploded deployment * war: BaseClassLoader@bfe0e4{vfszip:/ade/xidu_j2eebug/oracle/work/utp/resultout/functional/ * jrf/jboss-jrfServer/jrfServer/deploy/jrftestapp.ear/jrftestweb.war/} * * Assumptions: * 1 - A given toString may contain both .ear and .jar, or .ear and .war, i.e. * "{vfszip:/.../xxx.ear/.../xxx.war/}". In this case we want to return * xxx.ear as the application name. * 2 - A given toString will end in '/}'. * 3 - A toString containing the application name will have one of "vfszip:" or "vfsfile:". * * @param loaderToString the toString of the loader being processed * @return application name (archive file name) if successfully retrieved (i.e. loader * exists in the hierarchy with toString containing "vfszip:" or "vfsfile:") * or null */ private static String getApplicationNameFromJBossClassLoader(String loaderToString) { String appNameSegment = null; int idx; // handle "vfszip:<archive-file-name>" if ((idx = loaderToString.indexOf(JBOSS_VFSZIP)) != -1) { appNameSegment = loaderToString.substring(idx + JBOSS_VFSZIP_OFFSET, loaderToString.length() - JBOSS_TRIM_COUNT); // handle case where the string contains both .ear and .war (remove the .war portion) if ((appNameSegment.indexOf(JBOSS_WAR) != -1) && (appNameSegment.indexOf(JBOSS_EAR) != -1)) { appNameSegment = appNameSegment.substring(0, appNameSegment.indexOf(JBOSS_EAR) + JBOSS_EAR_OFFSET); } // handle case where the string contains both .ear and .jar (remove the .jar portion) else if ((appNameSegment.indexOf(JBOSS_JAR) != -1) && (appNameSegment.indexOf(JBOSS_EAR) != -1)) { appNameSegment = appNameSegment.substring(0, appNameSegment.indexOf(JBOSS_EAR) + JBOSS_EAR_OFFSET); } } // handle "vfsfile:<archive-file-name>" else if ((idx = loaderToString.indexOf(JBOSS_VFSFILE)) != -1) { appNameSegment = loaderToString.substring(idx + JBOSS_VFSFILE_OFFSET, loaderToString.length() - JBOSS_TRIM_COUNT); } return appNameSegment != null ? new File(appNameSegment).getName() : null; } /** * Attempt to return a MapKeyLookupResult instance wrapping the archive file name and * application loader based on a given JBoss classloader. Here we will traverse up the * loader hierarchy looking for the top-most application loader. * * @param loader * @return a MapKeyLookupResult instance wrapping archive file name/loader if * successfully retrieved (i.e. at least one loader exists in the * hierarchy with toString containing containing "vfszip:" or "vfsfile:"), * or a MapKeyLookupResult instance wrapping the given loader if not found */ private static MapKeyLookupResult getContextMapKeyForJBoss(ClassLoader loader) { ClassLoader applicationLoader = loader; String archiveFileName = null; // safety counter to keep from taking too long or looping forever, just in case of some unexpected circumstance int i = 0; // iterate up the loader hierarchy looking for the top-level application loader while (i < COUNTER_LIMIT) { if (jBossClassLoaderHasArchiveFileInfo(loader)) { // current loader has archive file info - store it applicationLoader = loader; } final ClassLoader parent = loader.getParent(); // once we have hit the top we will stop looking if (parent == null || parent == loader) { // get the archive file name from the loader we are going to return archiveFileName = getApplicationNameFromJBossClassLoader(applicationLoader.toString()); break; } // move up and try again loader = parent; i++; } // if we found the archive file name, use it as the key if (archiveFileName != null) { return new MapKeyLookupResult(archiveFileName, applicationLoader); } // at this point we don't know the archive file name so the loader will be the key return new MapKeyLookupResult(applicationLoader); } /** * Indicates if a given JBoss class loader contains an archive file name; i.e. is an application * loader. * * Here is an example toString result of the classloader which loaded the application in JBoss: * BaseClassLoader@1316dd{vfszip:/ade/xidu_j2eev5/oracle/work/utp/resultout/functional/jrf/ * jboss-jrfServer/deploy/jrftestapp.jar/} * or {vfsfile:/net/stott18.ca.oracle.com/scratch/xidu/view_storage/xidu_j2eev5/work/jboss/ * server/default/deploy/testapp.ear/} in exploded deployment * war: BaseClassLoader@bfe0e4{vfszip:/ade/xidu_j2eebug/oracle/work/utp/resultout/functional/ * jrf/jboss-jrfServer/jrfServer/deploy/jrftestapp.ear/jrftestweb.war/} * * Assumptions: * 1 - The toString of an application loader will have one of "vfszip:" or "vfsfile:". * * @param loader * @return true if the given JBoss loader has a toString containing "vfszip:" or "vfsfile:"); * false otherwise */ private static boolean jBossClassLoaderHasArchiveFileInfo(ClassLoader loader) { // look for "vfszip:<archive-file-name>" or "vfsfile:<archive-file-name>" return (loader.toString().indexOf(JBOSS_VFSZIP) != -1 || loader.toString().indexOf(JBOSS_VFSFILE) != -1); } /** * Return the unique label for this HelperContext. * * @return String representing the unique label for this HelperContext */ public String getIdentifier() { return this.identifier; } /** * Return true if a HelperContext corresponding to this identifier or alias * already exists, else false. If identifer is an alias, the corresponding * value in the alias Map will be used as the identifier for the lookup. * * @param identifier the alias or identifier used to lookup a helper context * @return true if an entry exists in the helper context map for identifier (or * the associated identifier value if identifier is an alias), false otherwise. */ public static boolean hasHelperContext(String identifier) { String id = identifier; Object appKey = getMapKey(); // if identifier is an alias, we need the actual id value ConcurrentMap<String, String> aliasEntries = getAliasMap(appKey); if (aliasEntries.containsKey(identifier)) { id = aliasEntries.get(identifier); } // now check the Map of user set identifiers to helperContexts WeakHashMap<String, WeakReference<HelperContext>> userSetMap = userSetHelperContexts.get(appKey); if (userSetMap != null && userSetMap.containsKey(id)) { return true; } // lastly, check the Map of identifiers to helperContexts ConcurrentHashMap<String, HelperContext> contextMap = helperContexts.get(appKey); return (contextMap != null && contextMap.containsKey(id)); } /** * Add an alias to identifier pair to the alias Map for the current * application. * * @param identifier assumed to be a key in the helper context Map * @param alias the alias to be associated with identifier */ public static void addAlias(String identifier, String alias) { getAliasMap().put(alias, identifier); } /** * INTERNAL: * Returns the map of alias' to identifiers for the current application. * * @return Map of alias' to identifiers for the current application */ private static ConcurrentMap<String, String> getAliasMap() { return getAliasMap(getMapKey()); } /** * INTERNAL: * Returns the map of alias' to identifiers for the current application. * * @param mapKey application name or classloader used to lookup the alias map * @return Map of alias' to identifiers for the current application */ private static ConcurrentMap<String, String> getAliasMap(Object mapKey) { ConcurrentHashMap<String, String> alias = aliasMap.get(mapKey); // may need to add a new entry if (null == alias) { alias = new ConcurrentHashMap<String, String>(); // use putIfAbsent to avoid concurrent entries in the map ConcurrentHashMap existingMap = aliasMap.putIfAbsent(mapKey, alias); if (existingMap != null) { // if a new entry was just added, use it instead of the one we just created alias = existingMap; } } return alias; } /** * Lazily initialize the Map of user properties. */ private Map<String, Object> getProperties() { if (properties == null) { properties = new HashMap<String, Object>(); } return properties; } /** * Add a name/value pair to the properties Map. If name is * null, nothing will be done. If value is null, the entry * in the Map will be removed (if an entry exists for name). * * @param name the name of the property * @param value the value of the property */ public void setProperty(String name, Object value) { // if the key is null there is nothing to do if (name == null) { return; } // if value is null, remove the entry if (value == null) { getProperties().remove(name); } else { // put the name/value pair in the map getProperties().put(name, value); } } /** * Return the value stored in the properties Map for a given * name, or null if an entry for name does not exist. * * @param name the name of the property to be returned * @return the value associated with name, or null */ public Object getProperty(String name) { return getProperties().get(name); } /** * Indicates whether strict type checking is enabled. * * <p> * If strict type checking is enabled then {@link Type#getInstanceClass()} * interface is checked whether it contains getters for all the properties * of the {@link Type} upon initialization of the {@link Type}. * If any getter is missing then the interface is ignored and * {@link Type#getInstanceClass()} will return {@code null}. * </p> * * <p> * The getters are not checked if the strict type checking is disabled. * </p> * * @return boolean value */ public boolean isStrictTypeCheckingEnabled() { return this.isStrictTypeCheckingEnabled; } /** * Controls type checking strictness. * * See {@link #isStrictTypeCheckingEnabled()} for more details. * * @param enabled new value ({@code true} to enable the strict validation) */ public void setStrictTypeCheckingEnabled(boolean enabled) { this.isStrictTypeCheckingEnabled = enabled; } /** * Strategy for {@link HelperContext} creation. * * If is not set explicitly the default one is used. */ public interface HelperContextResolver { /** * Should return instance of {@link HelperContext}. * * @param id the unique label for this HelperContext * @param classLoader this class loader will be used to find static instance classes * @return instance of {@link HelperContext} */ HelperContext getHelperContext(String id, ClassLoader classLoader); } /** * Default implementation of {@link HelperContextResolver} */ private static class DefaultHelperContextResolver implements HelperContextResolver { @Override public HelperContext getHelperContext(String id, ClassLoader classLoader) { LOGGER.fine(String.format("DefaultHelperContextResolver: new HelperContext will be created for id: %s and classLoader: %s", id, classLoader)); return new SDOHelperContext(id, classLoader); } } /** * Implementation of {@link HelperContextResolver} which uses reflection to get {@link HelperContext} instance. */ private static class ReflectionHelperContextResolver implements HelperContextResolver { private Method method; private Object target; /** * 'getHelperContext' method will be called on the provided target object. * * @param target object which has 'getHelperContext(String, Classloader)' method. In case it's not - RuntimeException will be thrown */ public ReflectionHelperContextResolver(Object target) { this.target = target; try { this.method = findMethod(target.getClass(), "getHelperContext", new Class[]{String.class, ClassLoader.class}); } catch (Exception e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); throw new IllegalStateException(e); } } private Method findMethod(Class clazz, String methodName, Class[] params) throws java.security.PrivilegedActionException, NoSuchMethodException { if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) return AccessController.doPrivileged(new PrivilegedGetMethod(clazz, methodName, params, true)); return PrivilegedAccessHelper.getMethod(clazz, methodName, params, true); } /** * Calling 'getHelperContext' method on provided target object * * @param id the unique label for this HelperContext * @param classLoader this class loader will be used to find static instance classes * @return in case if something goes wrong with method invocation and Exception is thrown - default implementation of * {@link HelperContextResolver} will be called */ @Override public HelperContext getHelperContext(String id, ClassLoader classLoader) { try { return (HelperContext) method.invoke(target, id, classLoader); } catch (Exception e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); } return DEFAULT_HCR.getHelperContext(id, classLoader); } } /** * Returns the {@link SDOWrapperType} instances for current application */ public static Map<SDOWrapperTypeId,SDOWrapperType> getWrapperTypes() { return SDO_WRAPPER_TYPES.get(getApplicationName(Thread.currentThread().getContextClassLoader())); } /** * Replaces the {@link SDOWrapperType} instances for current application with the ones passed as an argument * * @param wrapperTypes the SDOWrapperType instances to use for current application * @return SDOWrapperType instances configured for current application */ public static Map<SDOWrapperTypeId,SDOWrapperType> putWrapperTypes(Map<SDOWrapperTypeId,SDOWrapperType> wrapperTypes) { return SDO_WRAPPER_TYPES.put(getApplicationName(Thread.currentThread().getContextClassLoader()), wrapperTypes); } }