001/*
002 * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 */
019package org.nuxeo.runtime.test.runner;
020
021import java.lang.annotation.Annotation;
022import java.lang.reflect.Method;
023import java.net.URL;
024import java.util.Collection;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.Iterator;
028import java.util.LinkedList;
029import java.util.List;
030import java.util.Map;
031import java.util.Set;
032
033import javax.inject.Inject;
034
035import org.junit.rules.MethodRule;
036import org.junit.runners.model.FrameworkMethod;
037import org.junit.runners.model.Statement;
038
039import org.nuxeo.runtime.model.RuntimeContext;
040import org.nuxeo.runtime.osgi.OSGiRuntimeService;
041
042import org.osgi.framework.Bundle;
043
044import com.google.common.base.Supplier;
045import com.google.common.collect.Multimaps;
046import com.google.common.collect.SetMultimap;
047
048/**
049 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
050 */
051public class RuntimeDeployment {
052
053    Set<String> bundles = new HashSet<>();
054
055    Map<String, Collection<String>> mainContribs = new HashMap<>();
056
057    SetMultimap<String, String> mainIndex = Multimaps.newSetMultimap(mainContribs, new Supplier<Set<String>>() {
058        @Override
059        public Set<String> get() {
060            return new HashSet<>();
061        }
062    });
063
064    Map<String, Collection<String>> localContribs = new HashMap<>();
065
066    SetMultimap<String, String> localIndex = Multimaps.newSetMultimap(localContribs, new Supplier<Set<String>>() {
067        @Override
068        public Set<String> get() {
069            return new HashSet<>();
070        }
071    });
072
073    protected LinkedList<RuntimeContext> contexts = new LinkedList<>();
074
075    protected void index(Class<?> clazz) {
076        AnnotationScanner scanner = FeaturesRunner.scanner;
077        scanner.scan(clazz);
078        List<? extends Annotation> annos = scanner.getAnnotations(clazz);
079        if (annos == null) {
080            return;
081        }
082        for (Annotation anno : annos) {
083            if (anno.annotationType() == Deploy.class) {
084                index((Deploy) anno);
085            } else if (anno.annotationType() == LocalDeploy.class) {
086                index((LocalDeploy) anno);
087            }
088        }
089    }
090
091    protected void index(RunnerFeature feature) {
092        index(feature.getClass());
093    }
094
095    protected void index(Method method) {
096        index(method.getAnnotation(Deploy.class));
097        index(method.getAnnotation(LocalDeploy.class));
098    }
099
100    protected void index(Deploy config) {
101        if (config == null) {
102            return;
103        }
104        for (String each : config.value()) {
105            index(each, mainIndex);
106        }
107    }
108
109    protected void index(LocalDeploy config) {
110        if (config == null) {
111            return;
112        }
113        for (String each : config.value()) {
114            index(each, localIndex);
115        }
116    }
117
118    protected void index(Features features) {
119        for (Class<?> each : features.value()) {
120            index(each);
121        }
122    }
123
124    protected void index(String directive, SetMultimap<String, String> contribs) {
125        int sepIndex = directive.indexOf(':');
126        if (sepIndex == -1) {
127            bundles.add(directive);
128        } else {
129            String bundle = directive.substring(0, sepIndex);
130            String resource = directive.substring(sepIndex + 1);
131            contribs.put(bundle, resource);
132        }
133    }
134
135    protected void deploy(FeaturesRunner runner, RuntimeHarness harness) {
136        AssertionError errors = new AssertionError("deployment errors");
137        OSGiRuntimeService runtime = (OSGiRuntimeService) harness.getContext().getRuntime();
138        for (String name : bundles) {
139            Bundle bundle = harness.getOSGiAdapter().getBundle(name);
140            if (bundle == null) {
141                try {
142                    harness.deployBundle(name);
143                    bundle = harness.getOSGiAdapter().getBundle(name);
144                    if (bundle == null) {
145                        throw new UnsupportedOperationException("Should not occur");
146                    }
147                } catch (Exception error) {
148                    errors.addSuppressed(error);
149                    continue;
150                }
151                contexts.add(runtime.getContext(bundle));
152            }
153            try {
154                // deploy bundle contribs
155                for (String resource : mainIndex.removeAll(name)) {
156                    try {
157                        harness.deployContrib(name, resource);
158                    } catch (Exception error) {
159                        errors.addSuppressed(error);
160                    }
161                }
162                // deploy local contribs
163                for (String resource : localIndex.removeAll(name)) {
164                    URL url = runner.getTargetTestResource(resource);
165                    if (url == null) {
166                        url = bundle.getEntry(resource);
167                    }
168                    if (url == null) {
169                        url = runner.getTargetTestClass().getClassLoader().getResource(resource);
170                    }
171                    if (url == null) {
172                        throw new AssertionError("Cannot find " + resource + " in " + name);
173                    }
174                    contexts.add(harness.deployTestContrib(name, url));
175                }
176            } catch (Exception error) {
177                errors.addSuppressed(error);
178            }
179        }
180
181        for (Map.Entry<String, String> resource : mainIndex.entries()) {
182            try {
183                harness.deployContrib(resource.getKey(), resource.getValue());
184            } catch (Exception error) {
185                errors.addSuppressed(error);
186            }
187        }
188        for (Map.Entry<String, String> resource : localIndex.entries()) {
189            try {
190                contexts.add(harness.deployTestContrib(resource.getKey(), resource.getValue()));
191            } catch (Exception error) {
192                errors.addSuppressed(error);
193            }
194        }
195
196        if (errors.getSuppressed().length > 0) {
197            throw errors;
198        }
199
200    }
201
202    void undeploy() {
203        AssertionError errors = new AssertionError("deployment errors");
204
205        Iterator<RuntimeContext> it = contexts.descendingIterator();
206        while (it.hasNext()) {
207            RuntimeContext each = it.next();
208            it.remove();
209            try {
210                each.destroy();
211            } catch (RuntimeException error) {
212                errors.addSuppressed(error);
213            }
214        }
215
216        if (errors.getSuppressed().length > 0) {
217            throw errors;
218        }
219    }
220
221    public static RuntimeDeployment onTest(FeaturesRunner runner) {
222        RuntimeDeployment deployment = new RuntimeDeployment();
223        deployment.index(runner.getDescription().getTestClass());
224        for (RunnerFeature each : runner.getFeatures()) {
225            deployment.index(each);
226        }
227        return deployment;
228    }
229
230    public static MethodRule onMethod() {
231        return new OnMethod();
232    }
233
234    protected static class OnMethod implements MethodRule {
235
236        @Inject
237        protected FeaturesRunner runner;
238
239        @Override
240        public Statement apply(Statement base, FrameworkMethod method, Object target) {
241            RuntimeDeployment deployment = new RuntimeDeployment();
242            deployment.index(method.getMethod());
243            return deployment.onStatement(runner, runner.getFeature(RuntimeFeature.class).harness, base);
244        }
245
246    }
247
248    protected Statement onStatement(FeaturesRunner runner, RuntimeHarness harness, Statement base) {
249        return new DeploymentStatement(runner, harness, base);
250    }
251
252    protected class DeploymentStatement extends Statement {
253
254        protected final FeaturesRunner runner;
255
256        protected final RuntimeHarness harness;
257
258        protected final Statement base;
259
260        public DeploymentStatement(FeaturesRunner runner, RuntimeHarness harness, Statement base) {
261            this.runner = runner;
262            this.harness = harness;
263            this.base = base;
264        }
265
266        @Override
267        public void evaluate() throws Throwable {
268            deploy(runner, harness);
269            try {
270                base.evaluate();
271            } finally {
272                undeploy();
273            }
274        }
275
276    }
277
278}