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