/* * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * bstefanescu */ package org.nuxeo.runtime.test.runner; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.net.URL; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.inject.Inject; import org.junit.rules.MethodRule; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.Statement; import org.nuxeo.runtime.model.RuntimeContext; import org.nuxeo.runtime.osgi.OSGiRuntimeService; import org.osgi.framework.Bundle; import com.google.common.base.Supplier; import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; /** * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> */ public class RuntimeDeployment { Set<String> bundles = new HashSet<>(); Map<String, Set<TargetExtensions>> partialBundles = new HashMap<>(); Map<String, Collection<String>> mainContribs = new HashMap<>(); SetMultimap<String, String> mainIndex = Multimaps.newSetMultimap(mainContribs, new Supplier<Set<String>>() { @Override public Set<String> get() { return new HashSet<>(); } }); Map<String, Collection<String>> localContribs = new HashMap<>(); SetMultimap<String, String> localIndex = Multimaps.newSetMultimap(localContribs, new Supplier<Set<String>>() { @Override public Set<String> get() { return new HashSet<>(); } }); protected LinkedList<RuntimeContext> contexts = new LinkedList<>(); protected void index(Class<?> clazz) { AnnotationScanner scanner = FeaturesRunner.scanner; scanner.scan(clazz); List<? extends Annotation> annos = scanner.getAnnotations(clazz); if (annos == null) { return; } for (Annotation anno : annos) { if (anno.annotationType() == Deploy.class) { index((Deploy) anno); } else if (anno.annotationType() == LocalDeploy.class) { index((LocalDeploy) anno); } else if (anno.annotationType() == PartialDeploy.class) { index((PartialDeploy) anno); } } } protected void index(RunnerFeature feature) { index(feature.getClass()); } protected void index(Method method) { index(method.getAnnotation(Deploy.class)); index(method.getAnnotation(LocalDeploy.class)); } protected void index(Deploy config) { if (config == null) { return; } for (String each : config.value()) { index(each, mainIndex); } } protected void index(LocalDeploy config) { if (config == null) { return; } for (String each : config.value()) { index(each, localIndex); } } /** * @since 9.1 * @param config */ protected void index(PartialDeploy config) { if (config == null) { return; } Set<TargetExtensions> pairs = partialBundles.computeIfAbsent(config.bundle(), key -> new HashSet<>()); Arrays.stream(config.extensions()).map(c -> { try { return c.newInstance(); } catch (ReflectiveOperationException e) { throw new IllegalStateException(e); } }).forEach(pairs::add); } protected void index(Features features) { for (Class<?> each : features.value()) { index(each); } } protected void index(String directive, SetMultimap<String, String> contribs) { int sepIndex = directive.indexOf(':'); if (sepIndex == -1) { bundles.add(directive); } else { String bundle = directive.substring(0, sepIndex); String resource = directive.substring(sepIndex + 1); contribs.put(bundle, resource); } } protected void deploy(FeaturesRunner runner, RuntimeHarness harness) { AssertionError errors = new AssertionError("deployment errors"); OSGiRuntimeService runtime = (OSGiRuntimeService) harness.getContext().getRuntime(); for (String name : bundles) { Bundle bundle = harness.getOSGiAdapter().getBundle(name); if (bundle == null) { try { harness.deployBundle(name); bundle = harness.getOSGiAdapter().getBundle(name); if (bundle == null) { throw new UnsupportedOperationException("Should not occur"); } } catch (Exception error) { errors.addSuppressed(error); continue; } contexts.add(runtime.getContext(bundle)); } try { // deploy bundle contribs for (String resource : mainIndex.removeAll(name)) { try { harness.deployContrib(name, resource); } catch (Exception error) { errors.addSuppressed(error); } } // deploy local contribs for (String resource : localIndex.removeAll(name)) { URL url = runner.getTargetTestResource(resource); if (url == null) { url = bundle.getEntry(resource); } if (url == null) { url = runner.getTargetTestClass().getClassLoader().getResource(resource); } if (url == null) { throw new AssertionError("Cannot find " + resource + " in " + name); } contexts.add(harness.deployTestContrib(name, url)); } } catch (Exception error) { errors.addSuppressed(error); } } for (Map.Entry<String, String> resource : mainIndex.entries()) { try { harness.deployContrib(resource.getKey(), resource.getValue()); } catch (Exception error) { errors.addSuppressed(error); } } for (Map.Entry<String, String> resource : localIndex.entries()) { try { contexts.add(harness.deployTestContrib(resource.getKey(), resource.getValue())); } catch (Exception error) { errors.addSuppressed(error); } } for (Map.Entry<String, Set<TargetExtensions>> resource : partialBundles.entrySet()) { try { contexts.add(harness.deployPartial(resource.getKey(), resource.getValue())); } catch (Exception e) { errors.addSuppressed(e); } } if (errors.getSuppressed().length > 0) { throw errors; } } void undeploy() { AssertionError errors = new AssertionError("deployment errors"); Iterator<RuntimeContext> it = contexts.descendingIterator(); while (it.hasNext()) { RuntimeContext each = it.next(); it.remove(); try { each.destroy(); } catch (RuntimeException error) { errors.addSuppressed(error); } } if (errors.getSuppressed().length > 0) { throw errors; } } public static RuntimeDeployment onTest(FeaturesRunner runner) { RuntimeDeployment deployment = new RuntimeDeployment(); deployment.index(runner.getDescription().getTestClass()); for (RunnerFeature each : runner.getFeatures()) { deployment.index(each); } return deployment; } public static MethodRule onMethod() { return new OnMethod(); } protected static class OnMethod implements MethodRule { @Inject protected FeaturesRunner runner; @Override public Statement apply(Statement base, FrameworkMethod method, Object target) { RuntimeDeployment deployment = new RuntimeDeployment(); deployment.index(method.getMethod()); return deployment.onStatement(runner, runner.getFeature(RuntimeFeature.class).harness, base); } } protected Statement onStatement(FeaturesRunner runner, RuntimeHarness harness, Statement base) { return new DeploymentStatement(runner, harness, base); } protected class DeploymentStatement extends Statement { protected final FeaturesRunner runner; protected final RuntimeHarness harness; protected final Statement base; public DeploymentStatement(FeaturesRunner runner, RuntimeHarness harness, Statement base) { this.runner = runner; this.harness = harness; this.base = base; } @Override public void evaluate() throws Throwable { deploy(runner, harness); try { base.evaluate(); } finally { undeploy(); } } } }