/* * Copyright 2015 Red Hat, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jboss.as.arquillian.service; import static org.jboss.as.server.deployment.Services.JBOSS_DEPLOYMENT; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.management.MBeanServer; import org.jboss.arquillian.container.test.spi.TestRunner; import org.jboss.arquillian.protocol.jmx.JMXTestRunner; import org.jboss.arquillian.test.spi.TestResult; import org.jboss.as.arquillian.protocol.jmx.ExtendedJMXProtocolConfiguration; import org.jboss.as.jmx.MBeanServerService; import org.jboss.as.server.deployment.Attachments; import org.jboss.as.server.deployment.DeploymentUnit; import org.jboss.as.server.deployment.Phase; import org.jboss.logging.Logger; import org.jboss.modules.Module; import org.jboss.msc.service.AbstractServiceListener; import org.jboss.msc.service.Service; import org.jboss.msc.service.ServiceBuilder; import org.jboss.msc.service.ServiceController; import org.jboss.msc.service.ServiceName; import org.jboss.msc.service.ServiceTarget; import org.jboss.msc.service.StartContext; import org.jboss.msc.service.StartException; import org.jboss.msc.service.StopContext; import org.jboss.msc.value.InjectedValue; import org.wildfly.security.manager.WildFlySecurityManager; /** * Service responsible for creating and managing the life-cycle of the Arquillian service. * * * @author Thomas.Diesler@jboss.com * @author Kabir Khan * @since 17-Nov-2010 */ public class ArquillianService implements Service<ArquillianService> { public static final String TEST_CLASS_PROPERTY = "org.jboss.as.arquillian.testClass"; public static final ServiceName SERVICE_NAME = ServiceName.JBOSS.append("arquillian", "testrunner"); private static final Logger log = Logger.getLogger("org.jboss.as.arquillian"); private final InjectedValue<MBeanServer> injectedMBeanServer = new InjectedValue<MBeanServer>(); private final Set<ArquillianConfig> deployedTests = new HashSet<>(); private volatile JMXTestRunner jmxTestRunner; private volatile AbstractServiceListener<Object> listener; public static void addService(final ServiceTarget serviceTarget) { ArquillianService service = new ArquillianService(); ServiceBuilder<?> builder = serviceTarget.addService(ArquillianService.SERVICE_NAME, service); builder.addDependency(MBeanServerService.SERVICE_NAME, MBeanServer.class, service.injectedMBeanServer); builder.setInitialMode(ServiceController.Mode.ACTIVE); builder.install(); } public synchronized void start(StartContext context) throws StartException { log.debugf("Starting Arquillian Test Runner"); final MBeanServer mbeanServer = injectedMBeanServer.getValue(); try { jmxTestRunner = new ExtendedJMXTestRunner(); jmxTestRunner.registerMBean(mbeanServer); } catch (Throwable t) { throw new StartException("Failed to start Arquillian Test Runner", t); } listener = new ArquillianServiceListener(context.getChildTarget()); context.getController().getServiceContainer().addListener(listener); } public synchronized void stop(StopContext context) { log.debugf("Stopping Arquillian Test Runner"); try { if (jmxTestRunner != null) { jmxTestRunner.unregisterMBean(injectedMBeanServer.getValue()); } } catch (Exception ex) { log.errorf(ex, "Cannot stop Arquillian Test Runner"); } finally { context.getController().getServiceContainer().removeListener(listener); } } @Override public synchronized ArquillianService getValue() throws IllegalStateException { return this; } void registerArquillianConfig(ArquillianConfig arqConfig) { synchronized (deployedTests) { log.debugf("Register Arquillian config: %s", arqConfig.getServiceName()); deployedTests.add(arqConfig); deployedTests.notifyAll(); } } void unregisterArquillianConfig(ArquillianConfig arqConfig) { synchronized (deployedTests) { log.debugf("Unregister Arquillian config: %s", arqConfig.getServiceName()); deployedTests.remove(arqConfig); } } private ArquillianConfig getArquillianConfig(final String className, long timeout) { synchronized (deployedTests) { log.debugf("Getting Arquillian config for: %s", className); for (ArquillianConfig arqConfig : deployedTests) { for (String aux : arqConfig.getTestClasses()) { if (aux.equals(className)) { log.debugf("Found Arquillian config for: %s", className); return arqConfig; } } } if (timeout <= 0) { throw new IllegalStateException("Cannot obtain Arquillian config for: " + className); } try { log.debugf("Waiting on Arquillian config for: %s", className); deployedTests.wait(timeout); } catch (InterruptedException e) { // ignore } } return getArquillianConfig(className, -1); } class ExtendedJMXTestRunner extends JMXTestRunner { ExtendedJMXTestRunner() { super(new ExtendedTestClassLoader()); } @Override public byte[] runTestMethod(final String className, final String methodName, Map<String, String> protocolProps) { // Setup the ContextManager ArquillianConfig config = getArquillianConfig(className, 30000L); Map<String, Object> properties = Collections.singletonMap(TEST_CLASS_PROPERTY, className); ContextManager contextManager = setupContextManager(config, properties); try { ClassLoader runWithClassLoader = ClassLoader.getSystemClassLoader(); if (Boolean.parseBoolean(protocolProps.get(ExtendedJMXProtocolConfiguration.PROPERTY_ENABLE_TCCL))) { DeploymentUnit depUnit = config.getDeploymentUnit().getValue(); Module module = depUnit.getAttachment(Attachments.MODULE); if (module != null) { runWithClassLoader = module.getClassLoader(); } } ClassLoader tccl = WildFlySecurityManager.setCurrentContextClassLoaderPrivileged(runWithClassLoader); try { return super.runTestMethod(className, methodName, protocolProps); } finally { WildFlySecurityManager.setCurrentContextClassLoaderPrivileged(tccl); } } finally { if(contextManager != null) { contextManager.teardown(properties); } } } @Override protected TestResult doRunTestMethod(TestRunner runner, Class<?> testClass, String methodName, Map<String, String> protocolProps) { ClassLoader runWithClassLoader = ClassLoader.getSystemClassLoader(); if (Boolean.parseBoolean(protocolProps.get(ExtendedJMXProtocolConfiguration.PROPERTY_ENABLE_TCCL))) { ArquillianConfig config = getArquillianConfig(testClass.getName(), 30000L); DeploymentUnit depUnit = config.getDeploymentUnit().getValue(); Module module = depUnit.getAttachment(Attachments.MODULE); if (module != null) { runWithClassLoader = module.getClassLoader(); } } ClassLoader tccl = WildFlySecurityManager.setCurrentContextClassLoaderPrivileged(runWithClassLoader); try { return super.doRunTestMethod(runner, testClass, methodName, protocolProps); } finally { WildFlySecurityManager.setCurrentContextClassLoaderPrivileged(tccl); } } private ContextManager setupContextManager(final ArquillianConfig config, final Map<String, Object> properties) { try { final DeploymentUnit depUnit = config.getDeploymentUnit().getValue(); final ContextManagerBuilder builder = new ContextManagerBuilder(config).addAll(depUnit); ContextManager contextManager = builder.build(); contextManager.setup(properties); return contextManager; } catch (Throwable t) { return null; } } } class ExtendedTestClassLoader implements JMXTestRunner.TestClassLoader { @Override public Class<?> loadTestClass(final String className) throws ClassNotFoundException { final ArquillianConfig arqConfig = getArquillianConfig(className, -1); if (arqConfig == null) throw new ClassNotFoundException("No Arquillian config found for: " + className); return arqConfig.loadClass(className); } } private static class ArquillianServiceListener extends AbstractServiceListener<Object> { private ServiceTarget serviceTarget; private ArquillianServiceListener(ServiceTarget serviceTarget) { this.serviceTarget = serviceTarget; } @Override public void transition(ServiceController<? extends Object> serviceController, ServiceController.Transition transition) { switch (transition.getAfter()) { case UP: { ServiceName serviceName = serviceController.getName(); String simpleName = serviceName.getSimpleName(); if (JBOSS_DEPLOYMENT.isParentOf(serviceName) && simpleName.equals(Phase.INSTALL.toString())) { ServiceName parentName = serviceName.getParent(); ServiceController<?> parentController = serviceController.getServiceContainer().getService(parentName); DeploymentUnit depUnit = (DeploymentUnit) parentController.getValue(); ArquillianConfig arqConfig = ArquillianConfigBuilder.processDeployment(depUnit); if (arqConfig != null) { log.infof("Arquillian deployment detected: %s", arqConfig); ServiceBuilder<ArquillianConfig> builder = serviceTarget.addService(arqConfig.getServiceName(), arqConfig) .addDependency(ArquillianService.SERVICE_NAME, ArquillianService.class, arqConfig.getArquillianService()) .addDependency(parentController.getName(), DeploymentUnit.class, arqConfig.getDeploymentUnit()); arqConfig.addDeps(builder, serviceController); builder.setInitialMode(ServiceController.Mode.ACTIVE); builder.install(); } } } break; case STARTING: { ServiceName serviceName = serviceController.getName(); String simpleName = serviceName.getSimpleName(); if(JBOSS_DEPLOYMENT.isParentOf(serviceName) && simpleName.equals(Phase.DEPENDENCIES.toString())) { ServiceName parentName = serviceName.getParent(); ServiceController<?> parentController = serviceController.getServiceContainer().getService(parentName); DeploymentUnit depUnit = (DeploymentUnit) parentController.getValue(); ArquillianConfigBuilder.handleParseAnnotations(depUnit); } } break; } } } }