All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.as.arquillian.container.ServerSetupObserver Maven / Gradle / Ivy

There is a newer version: 5.1.0.Beta6
Show newest version
/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.jboss.as.arquillian.container;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.jboss.arquillian.container.spi.Container;
import org.jboss.arquillian.container.spi.client.deployment.DeploymentDescription;
import org.jboss.arquillian.container.spi.context.ContainerContext;
import org.jboss.arquillian.container.spi.event.container.AfterUnDeploy;
import org.jboss.arquillian.container.spi.event.container.BeforeDeploy;
import org.jboss.arquillian.core.api.Event;
import org.jboss.arquillian.core.api.Instance;
import org.jboss.arquillian.core.api.annotation.Inject;
import org.jboss.arquillian.core.api.annotation.Observes;
import org.jboss.arquillian.core.spi.ServiceLoader;
import org.jboss.arquillian.test.spi.TestEnricher;
import org.jboss.arquillian.test.spi.context.ClassContext;
import org.jboss.arquillian.test.spi.event.enrichment.AfterEnrichment;
import org.jboss.arquillian.test.spi.event.enrichment.BeforeEnrichment;
import org.jboss.arquillian.test.spi.event.enrichment.EnrichmentEvent;
import org.jboss.arquillian.test.spi.event.suite.AfterClass;
import org.jboss.arquillian.test.spi.event.suite.BeforeClass;
import org.jboss.as.arquillian.api.ServerSetup;
import org.jboss.as.arquillian.api.ServerSetupTask;
import org.jboss.logging.Logger;

/**
 * Observes the {@link BeforeDeploy}, {@link AfterUnDeploy} and {@link AfterClass} lifecycle events to ensure
 * {@linkplain ServerSetupTask setup tasks} are executed.
 *
 * @author Stuart Douglas
 * @author James R. Perkins
 */
@SuppressWarnings({ "unused", "InstanceVariableMayNotBeInitialized" })
public class ServerSetupObserver {

    private static final Logger log = Logger.getLogger(ServerSetupObserver.class);

    @Inject
    private Instance containerContext;

    @Inject
    private Instance managementClient;

    @Inject
    private Instance classContextInstance;

    @Inject
    private Instance serviceLoader;

    @Inject
    private Event enrichmentEvent;

    private final Map setupTasks = new HashMap<>();
    private boolean afterClassRun = false;

    /**
     * Observed only for state changes.
     *
     * @param beforeClass the lifecycle event
     */
    public synchronized void handleBeforeClass(@Observes BeforeClass beforeClass) {
        afterClassRun = false;
    }

    /**
     * Executed before deployments to lazily execute the {@link ServerSetupTask#setup(ManagementClient, String)
     * ServerSetupTask}.
     * 

* This is lazily loaded for manual mode tests. The server may not have been started at the * {@link org.jboss.arquillian.test.spi.event.suite.BeforeClass BeforeClass} event. *

* * @param event the lifecycle event * @param container the container the event is being invoked on * * @throws Throwable if an error occurs processing the event */ public synchronized void handleBeforeDeployment(@Observes BeforeDeploy event, Container container) throws Throwable { final String containerName = container.getName(); if (setupTasks.containsKey(containerName)) { setupTasks.get(containerName).deployments.add(event.getDeployment()); return; } final ClassContext classContext = classContextInstance.get(); if (classContext == null) { return; } final Class currentClass = classContext.getActiveId(); ServerSetup setup = currentClass.getAnnotation(ServerSetup.class); if (setup == null) { return; } final ManagementClient client = managementClient.get(); final ServerSetupTaskHolder holder = new ServerSetupTaskHolder(client, container.getName()); executeSetup(holder, setup, containerName, event.getDeployment()); } /** * Executed after the test class has completed. This ensures that any * {@linkplain ServerSetupTask#tearDown(ManagementClient, String) tear down tasks} have been executed if * all deployments have been undeployed. * * @param afterClass the lifecycle event * * @throws Exception if an error occurs processing the event */ public synchronized void afterTestClass(@Observes AfterClass afterClass) throws Exception { if (setupTasks.isEmpty()) { return; } // Clean up any remaining tasks from unmanaged deployments final Iterator> iter = setupTasks.entrySet().iterator(); while (iter.hasNext()) { final Map.Entry entry = iter.next(); final ServerSetupTaskHolder holder = entry.getValue(); // Only tearDown if all deployments have been removed from the container if (holder.deployments.isEmpty()) { entry.getValue().tearDown(entry.getKey()); iter.remove(); } } afterClassRun = true; } /** * Executed after each undeploy for the container. * * @param afterDeploy the lifecycle event * @param container the container the event is being invoked on * * @throws Exception if an error occurs processing the event */ public synchronized void handleAfterUndeploy(@Observes AfterUnDeploy afterDeploy, final Container container) throws Exception { final String containerName = container.getName(); final ServerSetupTaskHolder holder = setupTasks.get(containerName); if (holder == null) { return; } // Remove the deployment if (holder.deployments.remove(afterDeploy.getDeployment())) { // If the deployments are now empty and the AfterClass has been invoked we need to ensure the tearDown() has // happened. This should clean up any tasks left from managed deployments or unmanaged deployments that were // not undeployed manually. if (afterClassRun && holder.deployments.isEmpty()) { holder.tearDown(containerName); setupTasks.remove(containerName); } } } private void executeSetup(final ServerSetupTaskHolder holder, ServerSetup setup, String containerName, DeploymentDescription deployment) throws Exception { holder.deployments.add(deployment); setupTasks.put(containerName, holder); try { holder.setup(setup, containerName); } catch (Throwable t) { final Throwable toThrow = t; // We're going to throw on the underlying problem. But since that is going to // propagate to surefire and prevent further processing of the currently executing // test class, first we need to do cleanup work that's normally triggered by // handleAfterUndeploy and afterTestClass calls that won't be happening. Object failedSetup = null; try { // Run tearDown on any task that already successfully completed setup // The last setup is the one that just failed. We skip calling tearDown on it // by popping it off the holder's setupTasks queue. // As noted in the ServerSetupTask.setup javadoc, implementations that // throw assumption failure exceptions should do so before making any changes // that would normally be reversed in a call to tearDown failedSetup = holder.setupTasks.pollLast(); // Tell the holder to do the normal tearDown holder.tearDown(containerName); } catch (Exception logged) { // just to be safe String className = failedSetup != null ? failedSetup.getClass().getName() : ServerSetupTask.class.getSimpleName(); final String message = String .format("Failed tearing down ServerSetupTasks after a failed assumption in %s.setup()", className); toThrow.addSuppressed(new RuntimeException(message, logged)); log.errorf(logged, message); } finally { // Clean out our own state changes we made before calling holder.setup. // Otherwise, later classes that use the same container may fail. holder.deployments.remove(deployment); if (holder.deployments.isEmpty()) { setupTasks.remove(containerName); } } if (toThrow instanceof Exception) { throw (Exception) toThrow; } if (toThrow instanceof Error) { throw (Error) toThrow; } // Throw the error as an assertion error to abort the testing throw new AssertionError("Failed to invoke a ServerSetupTask.", toThrow); } } private class ServerSetupTaskHolder { private final ManagementClient client; private final Deque setupTasks; private final Set deployments; private final String containerName;; private ServerSetupTaskHolder(final ManagementClient client, final String containerName) { this.client = client; setupTasks = new ArrayDeque<>(); deployments = new HashSet<>(); this.containerName = containerName; } void setup(final ServerSetup setup, final String containerName) throws Throwable { final Class[] classes = setup.value(); for (Class clazz : classes) { final Constructor ctor = clazz.getDeclaredConstructor(); ctor.setAccessible(true); final ServerSetupTask task = ctor.newInstance(); enrich(task, clazz.getMethod("setup", ManagementClient.class, String.class)); setupTasks.add(task); task.setup(client, containerName); } } public void tearDown(final String containerName) { if (client.isClosed()) { log.errorf("The container '%s' may have been stopped. The management client has been closed and " + "tearing down setup tasks is not possible.", containerName); } else { ServerSetupTask task; while ((task = setupTasks.pollLast()) != null) { try { enrich(task, task.getClass().getMethod("tearDown", ManagementClient.class, String.class)); task.tearDown(client, containerName); } catch (Throwable e) { // Unlike with setup, here we don't propagate assumption failures. // Whatever was meant to be turned off by an assumption failure in setup has // already been turned off; here we want to ensure all tear down work proceeds. log.errorf(e, "Setup task failed during tear down. Offending class '%s'", task); } } } } @Override public String toString() { return ServerSetupTaskHolder.class.getName() + "[setupTasks=" + setupTasks + ", deployments" + deployments + "]"; } private void enrich(final ServerSetupTask task, final Method method) { try { containerContext.get().activate(containerName); enrichmentEvent.fire(new BeforeEnrichment(task, method)); Collection testEnrichers = serviceLoader.get().all(TestEnricher.class); for (TestEnricher enricher : testEnrichers) { enricher.enrich(task); } enrichmentEvent.fire(new AfterEnrichment(task, method)); } finally { containerContext.get().deactivate(); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy