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}