| /* |
| * Copyright (c) 1998, 2021 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 v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // 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 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; |
| |
| 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 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 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; |
| |
| /** |
| * <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 <code>true</code>. |
| * </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 = 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. |
| */ |
| @Override |
| public CopyHelper getCopyHelper() { |
| return copyHelper; |
| } |
| |
| /** |
| * Return the DataFactory instance for this helper context. |
| */ |
| @Override |
| public DataFactory getDataFactory() { |
| return dataFactory; |
| } |
| |
| /** |
| * Return the DataHelper instance for this helper context. |
| */ |
| @Override |
| public DataHelper getDataHelper() { |
| return dataHelper; |
| } |
| |
| /** |
| * Return the EqualityHelper instance for this helper context. |
| */ |
| @Override |
| public EqualityHelper getEqualityHelper() { |
| return equalityHelper; |
| } |
| |
| /** |
| * Return the TypeHelper instance for this helper context. |
| */ |
| @Override |
| public TypeHelper getTypeHelper() { |
| return typeHelper; |
| } |
| |
| /** |
| * Return the XMLHelper instance for this helper context. |
| */ |
| @Override |
| public XMLHelper getXMLHelper() { |
| return xmlHelper; |
| } |
| |
| /** |
| * Return the XSDHelper instance for this helper context. |
| */ |
| @Override |
| 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<String, HelperContext> 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. |
| * |
| */ |
| 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; |
| } |
| |
| @Override |
| 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<String, String> 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); |
| } |
| } |