blob: 030a025cb9be0ed6fbebd624e91f416ba7a133c8 [file] [log] [blame]
/*
* 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 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 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);
}
}