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.Arrays;
025import java.util.Collection;
026import java.util.HashMap;
027import java.util.HashSet;
028import java.util.Iterator;
029import java.util.LinkedList;
030import java.util.List;
031import java.util.Map;
032import java.util.Set;
033
034import javax.inject.Inject;
035
036import org.junit.rules.MethodRule;
037import org.junit.runners.model.FrameworkMethod;
038import org.junit.runners.model.Statement;
039import org.nuxeo.runtime.model.RuntimeContext;
040import org.nuxeo.runtime.osgi.OSGiRuntimeService;
041import org.osgi.framework.Bundle;
042
043import com.google.common.base.Supplier;
044import com.google.common.collect.Multimaps;
045import com.google.common.collect.SetMultimap;
046
047/**
048 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
049 */
050public class RuntimeDeployment {
051
052    Set<String> bundles = new HashSet<>();
053
054    Map<String, Set<TargetExtensions>> partialBundles = new HashMap<>();
055
056    Map<String, Collection<String>> mainContribs = new HashMap<>();
057
058    SetMultimap<String, String> mainIndex = Multimaps.newSetMultimap(mainContribs, new Supplier<Set<String>>() {
059        @Override
060        public Set<String> get() {
061            return new HashSet<>();
062        }
063    });
064
065    Map<String, Collection<String>> localContribs = new HashMap<>();
066
067    SetMultimap<String, String> localIndex = Multimaps.newSetMultimap(localContribs, new Supplier<Set<String>>() {
068        @Override
069        public Set<String> get() {
070            return new HashSet<>();
071        }
072    });
073
074    protected LinkedList<RuntimeContext> contexts = new LinkedList<>();
075
076    protected void index(Class<?> clazz) {
077        AnnotationScanner scanner = FeaturesRunner.scanner;
078        scanner.scan(clazz);
079        List<? extends Annotation> annos = scanner.getAnnotations(clazz);
080        if (annos == null) {
081            return;
082        }
083        for (Annotation anno : annos) {
084            if (anno.annotationType() == Deploy.class) {
085                index((Deploy) anno);
086            } else if (anno.annotationType() == LocalDeploy.class) {
087                index((LocalDeploy) anno);
088            } else if (anno.annotationType() == PartialDeploy.class) {
089                index((PartialDeploy) anno);
090            }
091        }
092    }
093
094    protected void index(RunnerFeature feature) {
095        index(feature.getClass());
096    }
097
098    protected void index(Method method) {
099        index(method.getAnnotation(Deploy.class));
100        index(method.getAnnotation(LocalDeploy.class));
101    }
102
103    protected void index(Deploy config) {
104        if (config == null) {
105            return;
106        }
107        for (String each : config.value()) {
108            index(each, mainIndex);
109        }
110    }
111
112    protected void index(LocalDeploy config) {
113        if (config == null) {
114            return;
115        }
116        for (String each : config.value()) {
117            index(each, localIndex);
118        }
119    }
120
121    /**
122     * @since 9.1
123     * @param config
124     */
125    protected void index(PartialDeploy config) {
126        if (config == null) {
127            return;
128        }
129
130        Set<TargetExtensions> pairs = partialBundles.computeIfAbsent(config.bundle(), key -> new HashSet<>());
131        Arrays.stream(config.extensions()).map(c -> {
132            try {
133                return c.newInstance();
134            } catch (ReflectiveOperationException e) {
135                throw new IllegalStateException(e);
136            }
137        }).forEach(pairs::add);
138    }
139
140    protected void index(Features features) {
141        for (Class<?> each : features.value()) {
142            index(each);
143        }
144    }
145
146    protected void index(String directive, SetMultimap<String, String> contribs) {
147        int sepIndex = directive.indexOf(':');
148        if (sepIndex == -1) {
149            bundles.add(directive);
150        } else {
151            String bundle = directive.substring(0, sepIndex);
152            String resource = directive.substring(sepIndex + 1);
153            contribs.put(bundle, resource);
154        }
155    }
156
157    protected void deploy(FeaturesRunner runner, RuntimeHarness harness) {
158        AssertionError errors = new AssertionError("deployment errors");
159        OSGiRuntimeService runtime = (OSGiRuntimeService) harness.getContext().getRuntime();
160        for (String name : bundles) {
161            Bundle bundle = harness.getOSGiAdapter().getBundle(name);
162            if (bundle == null) {
163                try {
164                    harness.deployBundle(name);
165                    bundle = harness.getOSGiAdapter().getBundle(name);
166                    if (bundle == null) {
167                        throw new UnsupportedOperationException("Should not occur");
168                    }
169                } catch (Exception error) {
170                    errors.addSuppressed(error);
171                    continue;
172                }
173                contexts.add(runtime.getContext(bundle));
174            }
175            try {
176                // deploy bundle contribs
177                for (String resource : mainIndex.removeAll(name)) {
178                    try {
179                        harness.deployContrib(name, resource);
180                    } catch (Exception error) {
181                        errors.addSuppressed(error);
182                    }
183                }
184                // deploy local contribs
185                for (String resource : localIndex.removeAll(name)) {
186                    URL url = runner.getTargetTestResource(resource);
187                    if (url == null) {
188                        url = bundle.getEntry(resource);
189                    }
190                    if (url == null) {
191                        url = runner.getTargetTestClass().getClassLoader().getResource(resource);
192                    }
193                    if (url == null) {
194                        throw new AssertionError("Cannot find " + resource + " in " + name);
195                    }
196                    contexts.add(harness.deployTestContrib(name, url));
197                }
198            } catch (Exception error) {
199                errors.addSuppressed(error);
200            }
201        }
202
203        for (Map.Entry<String, String> resource : mainIndex.entries()) {
204            try {
205                harness.deployContrib(resource.getKey(), resource.getValue());
206            } catch (Exception error) {
207                errors.addSuppressed(error);
208            }
209        }
210        for (Map.Entry<String, String> resource : localIndex.entries()) {
211            try {
212                contexts.add(harness.deployTestContrib(resource.getKey(), resource.getValue()));
213            } catch (Exception error) {
214                errors.addSuppressed(error);
215            }
216        }
217
218        for (Map.Entry<String, Set<TargetExtensions>> resource : partialBundles.entrySet()) {
219            try {
220                contexts.add(harness.deployPartial(resource.getKey(), resource.getValue()));
221            } catch (Exception e) {
222                errors.addSuppressed(e);
223            }
224        }
225
226        if (errors.getSuppressed().length > 0) {
227            throw errors;
228        }
229
230    }
231
232    void undeploy() {
233        AssertionError errors = new AssertionError("deployment errors");
234
235        Iterator<RuntimeContext> it = contexts.descendingIterator();
236        while (it.hasNext()) {
237            RuntimeContext each = it.next();
238            it.remove();
239            try {
240                each.destroy();
241            } catch (RuntimeException error) {
242                errors.addSuppressed(error);
243            }
244        }
245
246        if (errors.getSuppressed().length > 0) {
247            throw errors;
248        }
249    }
250
251    public static RuntimeDeployment onTest(FeaturesRunner runner) {
252        RuntimeDeployment deployment = new RuntimeDeployment();
253        deployment.index(runner.getDescription().getTestClass());
254        for (RunnerFeature each : runner.getFeatures()) {
255            deployment.index(each);
256        }
257        return deployment;
258    }
259
260    public static MethodRule onMethod() {
261        return new OnMethod();
262    }
263
264    protected static class OnMethod implements MethodRule {
265
266        @Inject
267        protected FeaturesRunner runner;
268
269        @Override
270        public Statement apply(Statement base, FrameworkMethod method, Object target) {
271            RuntimeDeployment deployment = new RuntimeDeployment();
272            deployment.index(method.getMethod());
273            return deployment.onStatement(runner, runner.getFeature(RuntimeFeature.class).harness, base);
274        }
275
276    }
277
278    protected Statement onStatement(FeaturesRunner runner, RuntimeHarness harness, Statement base) {
279        return new DeploymentStatement(runner, harness, base);
280    }
281
282    protected class DeploymentStatement extends Statement {
283
284        protected final FeaturesRunner runner;
285
286        protected final RuntimeHarness harness;
287
288        protected final Statement base;
289
290        public DeploymentStatement(FeaturesRunner runner, RuntimeHarness harness, Statement base) {
291            this.runner = runner;
292            this.harness = harness;
293            this.base = base;
294        }
295
296        @Override
297        public void evaluate() throws Throwable {
298            deploy(runner, harness);
299            try {
300                base.evaluate();
301            } finally {
302                undeploy();
303            }
304        }
305
306    }
307
308}