/* * JBoss, Home of Professional Open Source. * Copyright 2013, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.ee.concurrent; import org.jboss.as.ee.concurrent.handle.ResetContextHandle; import org.jboss.as.ee.concurrent.handle.SetupContextHandle; import org.jboss.as.ee.logging.EeLogger; import org.jboss.as.ee.concurrent.handle.ContextHandleFactory; import org.jboss.as.naming.util.ThreadLocalStack; import org.jboss.as.server.CurrentServiceContainer; import org.jboss.modules.Module; import org.jboss.modules.ModuleIdentifier; import org.jboss.msc.service.ServiceContainer; import org.jboss.msc.service.ServiceController; import org.jboss.msc.service.ServiceName; import javax.enterprise.concurrent.ContextService; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import static java.lang.Thread.currentThread; /** * Manages context handle factories, it is used by EE Context Services to save the invocation context. * * @author Eduardo Martins */ public class ConcurrentContext { /** * the name of the factory used by the chained context handles */ public static final String CONTEXT_HANDLE_FACTORY_NAME = "CONCURRENT_CONTEXT"; /** * a thread local stack with the contexts pushed */ private static final ThreadLocalStack<ConcurrentContext> current = new ThreadLocalStack<ConcurrentContext>(); /** * Sets the specified context as the current one, in the current thread. * * @param context The current context */ public static void pushCurrent(final ConcurrentContext context) { current.push(context); } /** * Pops the current context in the current thread. * * @return */ public static ConcurrentContext popCurrent() { return current.pop(); } /** * Retrieves the current context in the current thread. * * @return */ public static ConcurrentContext current() { return current.peek(); } private final Map<String, ContextHandleFactory> factoryMap = new HashMap<>(); private List<ContextHandleFactory> factoryOrderedList; private volatile ServiceName serviceName; /** * * @param serviceName */ public void setServiceName(ServiceName serviceName) { this.serviceName = serviceName; } /** * Adds a new factory. * @param factory */ public synchronized void addFactory(ContextHandleFactory factory) { final String factoryName = factory.getName(); if(factoryMap.containsKey(factoryName)) { throw EeLogger.ROOT_LOGGER.factoryAlreadyExists(this, factoryName); } factoryMap.put(factoryName, factory); final Comparator<ContextHandleFactory> comparator = new Comparator<ContextHandleFactory>() { @Override public int compare(ContextHandleFactory o1, ContextHandleFactory o2) { return Integer.compare(o1.getChainPriority(),o2.getChainPriority()); } }; SortedSet<ContextHandleFactory> sortedSet = new TreeSet<>(comparator); sortedSet.addAll(factoryMap.values()); factoryOrderedList = new ArrayList<>(sortedSet); } /** * Saves the current invocation context on a chained context handle. * @param contextService * @param contextObjectProperties * @return */ public SetupContextHandle saveContext(ContextService contextService, Map<String, String> contextObjectProperties) { final List<SetupContextHandle> handles = new ArrayList<>(factoryOrderedList.size()); for (ContextHandleFactory factory : factoryOrderedList) { handles.add(factory.saveContext(contextService, contextObjectProperties)); } return new ChainedSetupContextHandle(this, handles); } /** * A setup context handle that is a chain of other setup context handles */ private static class ChainedSetupContextHandle implements SetupContextHandle { private transient ConcurrentContext concurrentContext; private transient List<SetupContextHandle> setupHandles; private ChainedSetupContextHandle(ConcurrentContext concurrentContext, List<SetupContextHandle> setupHandles) { this.concurrentContext = concurrentContext; this.setupHandles = setupHandles; } @Override public ResetContextHandle setup() throws IllegalStateException { final LinkedList<ResetContextHandle> resetHandles = new LinkedList<>(); final ResetContextHandle resetContextHandle = new ChainedResetContextHandle(resetHandles); try { ConcurrentContext.pushCurrent(concurrentContext); for (SetupContextHandle handle : setupHandles) { resetHandles.addFirst(handle.setup()); } } catch (Error | RuntimeException e) { resetContextHandle.reset(); throw e; } return resetContextHandle; } @Override public String getFactoryName() { return CONTEXT_HANDLE_FACTORY_NAME; } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); // write the concurrent context service name out.writeObject(concurrentContext.serviceName); // write the number of setup handles out.write(setupHandles.size()); // write each handle ContextHandleFactory factory = null; String factoryName = null; for(SetupContextHandle handle : setupHandles) { factoryName = handle.getFactoryName(); factory = concurrentContext.factoryMap.get(factoryName); if(factory == null) { throw EeLogger.ROOT_LOGGER.factoryNotFound(concurrentContext, factoryName); } out.writeUTF(factoryName); factory.writeSetupContextHandle(handle, out); } } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { // switch to EE module classloader, otherwise, due to serialization, deployments would need to have dependencies to other internal modules final Module module; try { module = Module.getBootModuleLoader().loadModule(ModuleIdentifier.create("org.jboss.as.ee")); } catch (Throwable e) { throw new IOException(e); } final SecurityManager sm = System.getSecurityManager(); final ClassLoader classLoader; if (sm == null) { classLoader = currentThread().getContextClassLoader(); } else { classLoader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { @Override public ClassLoader run() { return currentThread().getContextClassLoader(); } }); } if (sm == null) { currentThread().setContextClassLoader(module.getClassLoader()); } else { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { currentThread().setContextClassLoader(module.getClassLoader()); return null; } }); } try { in.defaultReadObject(); // restore concurrent context from msc final ServiceName serviceName = (ServiceName) in.readObject(); final ServiceController<?> serviceController = currentServiceContainer().getService(serviceName); if(serviceController == null) { throw EeLogger.ROOT_LOGGER.concurrentContextServiceNotInstalled(serviceName); } concurrentContext = (ConcurrentContext) serviceController.getValue(); // read setup handles setupHandles = new ArrayList<>(); ContextHandleFactory factory = null; String factoryName = null; for(int i = in.read(); i > 0; i--) { factoryName = in.readUTF(); factory = concurrentContext.factoryMap.get(factoryName); if(factory == null) { throw EeLogger.ROOT_LOGGER.factoryNotFound(concurrentContext, factoryName); } setupHandles.add(factory.readSetupContextHandle(in)); } } finally { if (sm == null) { currentThread().setContextClassLoader(classLoader); } else { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { currentThread().setContextClassLoader(classLoader); return null; } }); } } } } /** * A reset context handle that is a chain of other reset context handles */ private static class ChainedResetContextHandle implements ResetContextHandle { private transient List<ResetContextHandle> resetHandles; private ChainedResetContextHandle(List<ResetContextHandle> resetHandles) { this.resetHandles = resetHandles; } @Override public void reset() { if(resetHandles != null) { for (ResetContextHandle handle : resetHandles) { try { handle.reset(); } catch (Throwable e) { EeLogger.ROOT_LOGGER.debug("failed to reset handle",e); } } resetHandles = null; ConcurrentContext.popCurrent(); } } @Override public String getFactoryName() { return CONTEXT_HANDLE_FACTORY_NAME; } } private static ServiceContainer currentServiceContainer() { if(System.getSecurityManager() == null) { return CurrentServiceContainer.getServiceContainer(); } return AccessController.doPrivileged(CurrentServiceContainer.GET_ACTION); } }