001/* 002 * (C) Copyright 2006-2018 Nuxeo (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 * Nuxeo - initial API and implementation 018 */ 019package org.nuxeo.runtime.test; 020 021import static org.junit.Assert.assertEquals; 022import static org.junit.Assert.assertNotNull; 023import static org.junit.Assert.fail; 024 025import java.io.File; 026import java.io.IOException; 027import java.net.MalformedURLException; 028import java.net.URI; 029import java.net.URISyntaxException; 030import java.net.URL; 031import java.util.ArrayList; 032import java.util.Arrays; 033import java.util.Enumeration; 034import java.util.HashMap; 035import java.util.HashSet; 036import java.util.List; 037import java.util.Map; 038import java.util.Objects; 039import java.util.Properties; 040import java.util.Set; 041import java.util.jar.Attributes; 042import java.util.jar.Manifest; 043import java.util.stream.Collectors; 044import java.util.stream.Stream; 045 046import org.apache.commons.io.FileUtils; 047import org.apache.commons.logging.Log; 048import org.apache.commons.logging.LogFactory; 049import org.jmock.Mockery; 050import org.jmock.integration.junit4.JUnit4Mockery; 051import org.junit.After; 052import org.junit.Before; 053import org.junit.Ignore; 054import org.junit.runner.RunWith; 055import org.nuxeo.common.Environment; 056import org.nuxeo.osgi.BundleFile; 057import org.nuxeo.osgi.BundleImpl; 058import org.nuxeo.osgi.DirectoryBundleFile; 059import org.nuxeo.osgi.JarBundleFile; 060import org.nuxeo.osgi.OSGiAdapter; 061import org.nuxeo.osgi.SystemBundle; 062import org.nuxeo.osgi.SystemBundleFile; 063import org.nuxeo.osgi.application.StandaloneBundleLoader; 064import org.nuxeo.runtime.AbstractRuntimeService; 065import org.nuxeo.runtime.RuntimeServiceException; 066import org.nuxeo.runtime.api.Framework; 067import org.nuxeo.runtime.model.Extension; 068import org.nuxeo.runtime.model.RegistrationInfo; 069import org.nuxeo.runtime.model.RuntimeContext; 070import org.nuxeo.runtime.model.StreamRef; 071import org.nuxeo.runtime.model.URLStreamRef; 072import org.nuxeo.runtime.model.impl.DefaultRuntimeContext; 073import org.nuxeo.runtime.osgi.OSGiRuntimeContext; 074import org.nuxeo.runtime.osgi.OSGiRuntimeService; 075import org.nuxeo.runtime.test.protocols.inline.InlineURLFactory; 076import org.nuxeo.runtime.test.runner.ConditionalIgnoreRule; 077import org.nuxeo.runtime.test.runner.Features; 078import org.nuxeo.runtime.test.runner.FeaturesRunner; 079import org.nuxeo.runtime.test.runner.MDCFeature; 080import org.nuxeo.runtime.test.runner.RandomBug; 081import org.nuxeo.runtime.test.runner.RuntimeHarness; 082import org.nuxeo.runtime.test.runner.TargetExtensions; 083import org.nuxeo.runtime.transaction.TransactionHelper; 084import org.osgi.framework.Bundle; 085import org.osgi.framework.FrameworkEvent; 086 087import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner; 088 089/** 090 * Abstract base class for test cases that require a test runtime service. 091 * <p> 092 * The runtime service itself is conveniently available as the <code>runtime</code> instance variable in derived 093 * classes. 094 * <p> 095 * <b>Warning:</b> NXRuntimeTestCase subclasses <b>must</b> 096 * <ul> 097 * <li>not declare they own @Before and @After. 098 * <li>override doSetUp and doTearDown (and postSetUp if needed) instead of setUp and tearDown. 099 * <li>never call deployXXX methods outside the doSetUp method. 100 * 101 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 102 */ 103// Make sure this class is kept in sync with with RuntimeHarness 104@RunWith(FeaturesRunner.class) 105@Features({ MDCFeature.class, ConditionalIgnoreRule.Feature.class, RandomBug.Feature.class }) 106@Ignore 107public class NXRuntimeTestCase implements RuntimeHarness { 108 109 protected Mockery jmcontext = new JUnit4Mockery(); 110 111 static { 112 // jul to jcl redirection may pose problems (infinite loops) in some 113 // environment 114 // where slf4j to jul, and jcl over slf4j is deployed 115 System.setProperty(AbstractRuntimeService.REDIRECT_JUL, "false"); 116 } 117 118 private static final Log log = LogFactory.getLog(NXRuntimeTestCase.class); 119 120 protected OSGiRuntimeService runtime; 121 122 protected URL[] urls; // classpath urls, used for bundles lookup 123 124 protected File workingDir; 125 126 protected StandaloneBundleLoader bundleLoader; 127 128 private Set<URI> readUris; 129 130 protected Map<String, BundleFile> bundles; 131 132 protected boolean restart = false; 133 134 protected List<String[]> deploymentStack = new ArrayList<>(); 135 136 /** 137 * Whether or not the runtime components were started. This is useful to ensure the runtime is started once. 138 */ 139 protected boolean frameworkStarted = false; 140 141 @Override 142 public boolean isRestart() { 143 return restart; 144 } 145 146 protected OSGiAdapter osgi; 147 148 protected Bundle runtimeBundle; 149 150 protected final List<WorkingDirectoryConfigurator> wdConfigs = new ArrayList<>(); 151 152 protected final TargetResourceLocator targetResourceLocator; 153 154 /** 155 * Set to true when the instance of this class is a JUnit test case. Set to false when the instance of this class is 156 * instantiated by the FeaturesRunner to manage the framework If the class is a JUnit test case then the runtime 157 * components will be started at the end of the setUp method 158 */ 159 protected final boolean isTestUnit; 160 161 /** 162 * Used when subclassing to create standalone test cases 163 */ 164 public NXRuntimeTestCase() { 165 targetResourceLocator = new TargetResourceLocator(this.getClass()); 166 isTestUnit = true; 167 } 168 169 /** 170 * Used by the features runner to manage the Nuxeo framework 171 */ 172 public NXRuntimeTestCase(Class<?> clazz) { 173 targetResourceLocator = new TargetResourceLocator(clazz); 174 isTestUnit = false; 175 } 176 177 @Override 178 public void addWorkingDirectoryConfigurator(WorkingDirectoryConfigurator config) { 179 wdConfigs.add(config); 180 } 181 182 @Override 183 public File getWorkingDir() { 184 return workingDir; 185 } 186 187 /** 188 * Restarts the runtime and preserve homes directory. 189 */ 190 @Override 191 public void restart() throws Exception { 192 restart = true; 193 try { 194 tearDown(); 195 setUp(); 196 } finally { 197 restart = false; 198 } 199 } 200 201 @Override 202 public void start() throws Exception { 203 startRuntime(); 204 } 205 206 @Before 207 public void startRuntime() throws Exception { 208 System.setProperty("org.nuxeo.runtime.testing", "true"); 209 // super.setUp(); 210 wipeRuntime(); 211 initUrls(); 212 if (urls == null) { 213 throw new UnsupportedOperationException("no bundles available"); 214 } 215 initOsgiRuntime(); 216 setUp(); // let a chance to the subclasses to contribute bundles and/or components 217 if (isTestUnit) { // if this class is running as a test case start the runtime components 218 fireFrameworkStarted(); 219 } 220 postSetUp(); 221 } 222 223 /** 224 * Implementors should override this method to setup tests and not the {@link #startRuntime()} method. This method 225 * should contain all the bundle or component deployments needed by the tests. At the time this method is called the 226 * components are not yet started. If you need to perform component/service lookups use instead the 227 * {@link #postSetUp()} method 228 */ 229 protected void setUp() throws Exception { 230 } 231 232 /** 233 * Implementors should override this method to implement any specific test tear down and not the 234 * {@link #stopRuntime()} method 235 * 236 * @throws Exception 237 */ 238 protected void tearDown() throws Exception { 239 deploymentStack = new ArrayList<>(); 240 } 241 242 /** 243 * Called after framework was started (at the end of setUp). Implementors may use this to use deployed services to 244 * initialize fields etc. 245 */ 246 protected void postSetUp() throws Exception { 247 } 248 249 /** 250 * Fire the event {@code FrameworkEvent.STARTED}. This will start all the resolved Nuxeo components 251 * 252 * @see OSGiRuntimeService#frameworkEvent(FrameworkEvent) 253 */ 254 @Override 255 public void fireFrameworkStarted() throws Exception { 256 if (frameworkStarted) { 257 // avoid starting twice the runtime (fix situations where tests are starting themselves the runtime) 258 // If this happens the faulty test should be fixed 259 // TODO NXP-22534 - throw an exception? 260 return; 261 } 262 frameworkStarted = true; 263 boolean txStarted = !TransactionHelper.isTransactionActiveOrMarkedRollback() 264 && TransactionHelper.startTransaction(); 265 boolean txFinished = false; 266 try { 267 osgi.fireFrameworkEvent(new FrameworkEvent(FrameworkEvent.STARTED, runtimeBundle, null)); 268 txFinished = true; 269 } finally { 270 if (!txFinished) { 271 TransactionHelper.setTransactionRollbackOnly(); 272 } 273 if (txStarted) { 274 TransactionHelper.commitOrRollbackTransaction(); 275 } 276 } 277 } 278 279 @After 280 public void stopRuntime() throws Exception { 281 tearDown(); 282 wipeRuntime(); 283 if (workingDir != null) { 284 if (!restart) { 285 if (workingDir.exists() && !FileUtils.deleteQuietly(workingDir)) { 286 log.warn("Cannot delete " + workingDir); 287 } 288 workingDir = null; 289 } 290 } 291 readUris = null; 292 bundles = null; 293 } 294 295 @Override 296 public void stop() throws Exception { 297 stopRuntime(); 298 } 299 300 @Override 301 public boolean isStarted() { 302 return runtime != null; 303 } 304 305 protected void initOsgiRuntime() throws Exception { 306 try { 307 if (!restart) { 308 Environment.setDefault(null); 309 if (System.getProperties().remove("nuxeo.home") != null) { 310 log.warn("Removed System property nuxeo.home."); 311 } 312 workingDir = File.createTempFile("nxruntime-" + Thread.currentThread().getName() + "-", null, 313 new File("target")); 314 workingDir.delete(); 315 } 316 } catch (IOException e) { 317 log.error("Could not init working directory", e); 318 throw e; 319 } 320 osgi = new OSGiAdapter(workingDir); 321 BundleFile bf = new SystemBundleFile(workingDir); 322 bundleLoader = new StandaloneBundleLoader(osgi, NXRuntimeTestCase.class.getClassLoader()); 323 SystemBundle systemBundle = new SystemBundle(osgi, bf, bundleLoader.getSharedClassLoader().getLoader()); 324 osgi.setSystemBundle(systemBundle); 325 Thread.currentThread().setContextClassLoader(bundleLoader.getSharedClassLoader().getLoader()); 326 327 for (WorkingDirectoryConfigurator cfg : wdConfigs) { 328 cfg.configure(this, workingDir); 329 } 330 331 bundleLoader.setScanForNestedJARs(false); // for now 332 bundleLoader.setExtractNestedJARs(false); 333 334 BundleFile bundleFile = lookupBundle("org.nuxeo.runtime"); 335 runtimeBundle = new RootRuntimeBundle(osgi, bundleFile, bundleLoader.getClass().getClassLoader(), true); 336 runtimeBundle.start(); 337 338 runtime = handleNewRuntime((OSGiRuntimeService) Framework.getRuntime()); 339 340 assertNotNull(runtime); 341 } 342 343 protected OSGiRuntimeService handleNewRuntime(OSGiRuntimeService aRuntime) { 344 return aRuntime; 345 } 346 347 public static URL[] introspectClasspath(ClassLoader loader) { 348 return new FastClasspathScanner().getUniqueClasspathElements().stream().map(file -> { 349 try { 350 return file.toURI().toURL(); 351 } catch (MalformedURLException cause) { 352 throw new Error("Could not get URL from " + file, cause); 353 } 354 }).toArray(URL[]::new); 355 } 356 357 protected void initUrls() throws Exception { 358 ClassLoader classLoader = NXRuntimeTestCase.class.getClassLoader(); 359 urls = introspectClasspath(classLoader); 360 if (log.isDebugEnabled()) { 361 StringBuilder sb = new StringBuilder(); 362 sb.append("URLs on the classpath: "); 363 for (URL url : urls) { 364 sb.append(url.toString()); 365 sb.append('\n'); 366 } 367 log.debug(sb.toString()); 368 } 369 readUris = new HashSet<>(); 370 bundles = new HashMap<>(); 371 } 372 373 /** 374 * Makes sure there is no previous runtime hanging around. 375 * <p> 376 * This happens for instance if a previous test had errors in its <code>setUp()</code>, because 377 * <code>tearDown()</code> has not been called. 378 */ 379 protected void wipeRuntime() throws Exception { 380 // Make sure there is no active runtime (this might happen if an 381 // exception is raised during a previous setUp -> tearDown is not called 382 // afterwards). 383 runtime = null; 384 frameworkStarted = false; 385 if (Framework.getRuntime() != null) { 386 Framework.shutdown(); 387 } 388 } 389 390 public static URL getResource(String name) { 391 final ClassLoader loader = Thread.currentThread().getContextClassLoader(); 392 String callerName = Thread.currentThread().getStackTrace()[2].getClassName(); 393 final String relativePath = callerName.replace('.', '/').concat(".class"); 394 final String fullPath = loader.getResource(relativePath).getPath(); 395 final String basePath = fullPath.substring(0, fullPath.indexOf(relativePath)); 396 Enumeration<URL> resources; 397 try { 398 resources = loader.getResources(name); 399 while (resources.hasMoreElements()) { 400 URL resource = resources.nextElement(); 401 if (resource.getPath().startsWith(basePath)) { 402 return resource; 403 } 404 } 405 } catch (IOException e) { 406 return null; 407 } 408 return loader.getResource(name); 409 } 410 411 protected void deployContrib(URL url) { 412 assertEquals(runtime, Framework.getRuntime()); 413 log.info("Deploying contribution from " + url.toString()); 414 try { 415 runtime.getContext().deploy(url); 416 } catch (Exception e) { 417 fail("Failed to deploy contrib " + url.toString()); 418 } 419 } 420 421 /** 422 * Deploys a contribution from a given bundle. 423 * <p> 424 * The path will be relative to the bundle root. Example: <code> 425 * deployContrib("org.nuxeo.ecm.core", "OSGI-INF/CoreExtensions.xml") 426 * </code> 427 * <p> 428 * For compatibility reasons the name of the bundle may be a jar name, but this use is discouraged and deprecated. 429 * 430 * @param name the name of the bundle to peek the contrib in 431 * @param contrib the path to contrib in the bundle. 432 */ 433 @Override 434 public void deployContrib(String name, String contrib) throws Exception { 435 RuntimeContext context = runtime.getContext(name); 436 if (context == null) { 437 context = runtime.getContext(); 438 BundleFile file = lookupBundle(name); 439 URL location = file.getEntry(contrib); 440 if (location == null) { 441 throw new AssertionError("Cannot locate " + contrib + " in " + name); 442 } 443 context.deploy(location); 444 return; 445 } 446 context.deploy(contrib); 447 } 448 449 /** 450 * Deploy a contribution specified as a "bundleName:path" uri 451 */ 452 public void deployContrib(String uri) throws Exception { 453 int i = uri.indexOf(':'); 454 if (i == -1) { 455 throw new IllegalArgumentException( 456 "Invalid deployment URI: " + uri + ". Must be of the form bundleSymbolicName:pathInBundleJar"); 457 } 458 deployContrib(uri.substring(0, i), uri.substring(i + 1)); 459 } 460 461 /** 462 * Deploy an XML contribution from outside a bundle. 463 * <p> 464 * This should be used by tests wiling to deploy test contribution as part of a real bundle. 465 * <p> 466 * The bundle owner is important since the contribution may depend on resources deployed in that bundle. 467 * <p> 468 * Note that the owner bundle MUST be an already deployed bundle. 469 * 470 * @param bundle the bundle that becomes the contribution owner 471 * @param contrib the contribution to deploy as part of the given bundle 472 * @deprecated since 10.1, use {@link #deployContrib(String, String)} 473 */ 474 @Override 475 @Deprecated 476 public RuntimeContext deployTestContrib(String bundle, String contrib) throws Exception { 477 URL url = targetResourceLocator.getTargetTestResource(contrib); 478 return deployTestContrib(bundle, url); 479 } 480 481 /** 482 * @deprecated since 10.1, use {@link #deployContrib(String, String)} 483 */ 484 @Override 485 @Deprecated 486 public RuntimeContext deployTestContrib(String bundle, URL contrib) throws Exception { 487 Bundle b = bundleLoader.getOSGi().getRegistry().getBundle(bundle); 488 if (b == null) { 489 b = osgi.getSystemBundle(); 490 } 491 OSGiRuntimeContext ctx = new OSGiRuntimeContext(runtime, b); 492 ctx.deploy(contrib); 493 return ctx; 494 } 495 496 @Override 497 public RuntimeContext deployPartial(String name, Set<TargetExtensions> targetExtensions) throws Exception { 498 // Do not install bundle; we only need the Object to list his components 499 Bundle bundle = new BundleImpl(osgi, lookupBundle(name), null); 500 501 RuntimeContext ctx = new OSGiRuntimeContext(runtime, bundle); 502 listBundleComponents(bundle).map(URLStreamRef::new).forEach(component -> { 503 try { 504 deployPartialComponent(ctx, targetExtensions, component); 505 } catch (IOException e) { 506 log.error("PartialBundle: " + name + " failed to load: " + component, e); 507 } 508 }); 509 return ctx; 510 } 511 512 /** 513 * Read a component from his StreamRef and create a new component (suffixed with `-partial`, and the base component 514 * name aliased) with only matching contributions of the extensionPoints parameter. 515 * 516 * @param ctx RuntimeContext in which the new component will be deployed 517 * @param extensionPoints Set of white listed TargetExtensions 518 * @param component Reference to the original component 519 */ 520 protected void deployPartialComponent(RuntimeContext ctx, Set<TargetExtensions> extensionPoints, 521 StreamRef component) throws IOException { 522 RegistrationInfo ri = ((DefaultRuntimeContext) ctx).createRegistrationInfo(component); 523 String name = ri.getName().getName() + "-partial"; 524 525 // Flatten Target Extension Points 526 Set<String> targets = extensionPoints.stream() 527 .map(TargetExtensions::getTargetExtensions) 528 .flatMap(Set::stream) 529 .collect(Collectors.toSet()); 530 531 String ext = Arrays.stream(ri.getExtensions()) 532 .filter(e -> targets.contains(TargetExtensions.newTargetExtension( 533 e.getTargetComponent().getName(), e.getExtensionPoint()))) 534 .map(Extension::toXML) 535 .collect(Collectors.joining()); 536 537 InlineURLFactory.install(); 538 ctx.deploy(new InlineRef(name, String.format("<component name=\"%s\">%s</component>", name, ext))); 539 } 540 541 /** 542 * Listing component's urls of a bundle. Inspired from org.nuxeo.runtime.osgi.OSGiRuntimeService#loadComponents but 543 * without deploying anything. 544 * 545 * @param bundle Bundle to be read 546 */ 547 protected Stream<URL> listBundleComponents(Bundle bundle) { 548 String list = OSGiRuntimeService.getComponentsList(bundle); 549 String name = bundle.getSymbolicName(); 550 log.debug("PartialBundle: " + name + " components: " + list); 551 if (list == null) { 552 return null; 553 } 554 555 return Arrays.stream(list.split("[, \t\n\r\f]")).map(bundle::getEntry).filter(Objects::nonNull); 556 } 557 558 /** 559 * Undeploys a contribution from a given bundle. 560 * <p> 561 * The path will be relative to the bundle root. Example: <code> 562 * undeployContrib("org.nuxeo.ecm.core", "OSGI-INF/CoreExtensions.xml") 563 * </code> 564 * 565 * @param name the bundle 566 * @param contrib the contribution 567 */ 568 @Override 569 public void undeployContrib(String name, String contrib) throws Exception { 570 RuntimeContext context = runtime.getContext(name); 571 if (context == null) { 572 context = runtime.getContext(); 573 } 574 context.undeploy(contrib); 575 } 576 577 public void undeployContrib(String uri) throws Exception { 578 int i = uri.indexOf(':'); 579 if (i == -1) { 580 throw new IllegalArgumentException( 581 "Invalid deployment URI: " + uri + ". Must be of the form bundleSymbolicName:pathInBundleJar"); 582 } 583 undeployContrib(uri.substring(0, i), uri.substring(i + 1)); 584 } 585 586 protected static boolean isVersionSuffix(String s) { 587 if (s.length() == 0) { 588 return true; 589 } 590 return s.matches("-(\\d+\\.?)+(-SNAPSHOT)?(\\.\\w+)?"); 591 } 592 593 /** 594 * Resolves an URL for bundle deployment code. 595 * <p> 596 * TODO: Implementation could be finer... 597 * 598 * @return the resolved url 599 */ 600 protected URL lookupBundleUrl(String bundle) { 601 for (URL url : urls) { 602 String[] pathElts = url.getPath().split("/"); 603 for (int i = 0; i < pathElts.length; i++) { 604 if (pathElts[i].startsWith(bundle) && isVersionSuffix(pathElts[i].substring(bundle.length()))) { 605 // we want the main version of the bundle 606 boolean isTestVersion = false; 607 for (int j = i + 1; j < pathElts.length; j++) { 608 // ok for Eclipse (/test) and Maven (/test-classes) 609 if (pathElts[j].startsWith("test")) { 610 isTestVersion = true; 611 break; 612 } 613 } 614 if (!isTestVersion) { 615 log.info("Resolved " + bundle + " as " + url.toString()); 616 return url; 617 } 618 } 619 } 620 } 621 throw new RuntimeException("Could not resolve bundle " + bundle); 622 } 623 624 /** 625 * Deploys a whole OSGI bundle. 626 * <p> 627 * The lookup is first done on symbolic name, as set in <code>MANIFEST.MF</code> and then falls back to the bundle 628 * url (e.g., <code>nuxeo-platform-search-api</code>) for backwards compatibility. 629 * 630 * @param name the symbolic name 631 */ 632 @Override 633 public void deployBundle(String name) throws Exception { 634 // install only if not yet installed 635 Bundle bundle = bundleLoader.getOSGi().getRegistry().getBundle(name); 636 if (bundle == null) { 637 BundleFile bundleFile = lookupBundle(name); 638 bundleLoader.loadBundle(bundleFile); 639 bundleLoader.installBundle(bundleFile); 640 bundle = bundleLoader.getOSGi().getRegistry().getBundle(name); 641 } 642 if (runtime.getContext(bundle) == null) { 643 runtime.createContext(bundle); 644 } 645 } 646 647 protected String readSymbolicName(BundleFile bf) { 648 Manifest manifest = bf.getManifest(); 649 if (manifest == null) { 650 return null; 651 } 652 Attributes attrs = manifest.getMainAttributes(); 653 String name = attrs.getValue("Bundle-SymbolicName"); 654 if (name == null) { 655 return null; 656 } 657 String[] sp = name.split(";", 2); 658 return sp[0]; 659 } 660 661 public BundleFile lookupBundle(String bundleName) throws Exception { 662 BundleFile bundleFile = bundles.get(bundleName); 663 if (bundleFile != null) { 664 return bundleFile; 665 } 666 for (URL url : urls) { 667 URI uri = url.toURI(); 668 if (readUris.contains(uri)) { 669 continue; 670 } 671 File file = new File(uri); 672 readUris.add(uri); 673 try { 674 if (file.isDirectory()) { 675 bundleFile = new DirectoryBundleFile(file); 676 } else { 677 bundleFile = new JarBundleFile(file); 678 } 679 } catch (IOException e) { 680 // no manifest => not a bundle 681 continue; 682 } 683 String symbolicName = readSymbolicName(bundleFile); 684 if (symbolicName != null) { 685 log.info(String.format("Bundle '%s' has URL %s", symbolicName, url)); 686 bundles.put(symbolicName, bundleFile); 687 } 688 if (bundleName.equals(symbolicName)) { 689 return bundleFile; 690 } 691 } 692 throw new RuntimeServiceException(String.format("No bundle with symbolic name '%s';", bundleName)); 693 } 694 695 @Override 696 public void deployFolder(File folder, ClassLoader loader) throws Exception { 697 DirectoryBundleFile bf = new DirectoryBundleFile(folder); 698 BundleImpl bundle = new BundleImpl(osgi, bf, loader); 699 osgi.install(bundle); 700 } 701 702 @Override 703 public Properties getProperties() { 704 return runtime.getProperties(); 705 } 706 707 @Override 708 public RuntimeContext getContext() { 709 return runtime.getContext(); 710 } 711 712 @Override 713 public OSGiAdapter getOSGiAdapter() { 714 return osgi; 715 } 716 717 /* 718 * (non-Javadoc) 719 * @see org.nuxeo.runtime.test.runner.RuntimeHarness#getClassLoaderFiles() 720 */ 721 @Override 722 public List<String> getClassLoaderFiles() throws URISyntaxException { 723 List<String> files = new ArrayList<>(urls.length); 724 for (URL url : urls) { 725 files.add(url.toURI().getPath()); 726 } 727 return files; 728 } 729 730 /** 731 * Should be called by subclasses after one or more inline deployments are made inside a test method. Without 732 * calling this the inline deployment(s) will not have any effects. 733 * <p /> 734 * <b>Be Warned</b> that if you reference runtime services or components you should lookup them again after calling 735 * this method! 736 * <p /> 737 * This method also calls {@link #postSetUp()} for convenience. 738 */ 739 protected void applyInlineDeployments() throws Exception { 740 runtime.getComponentManager().refresh(false); 741 runtime.getComponentManager().start(); // make sure components are started 742 postSetUp(); 743 } 744 745 /** 746 * Should be called by subclasses to remove any inline deployments made in the current test method. 747 * <p /> 748 * <b>Be Warned</b> that if you reference runtime services or components you should lookup them again after calling 749 * this method! 750 * <p /> 751 * This method also calls {@link #postSetUp()} for convenience. 752 */ 753 protected void removeInlineDeployments() throws Exception { 754 runtime.getComponentManager().reset(); 755 runtime.getComponentManager().start(); 756 postSetUp(); 757 } 758 759 /** 760 * Hot deploy the given components (identified by an URI). All the started components are stopped, the new ones are 761 * registered and then all components are started. You can undeploy these components by calling 762 * {@link #popInlineDeployments()} 763 * <p> 764 * A component URI is of the form: bundleSymbolicName:pathToComponentXmlInBundle 765 */ 766 public void pushInlineDeployments(String... deploymentUris) throws Exception { 767 deploymentStack.add(deploymentUris); 768 for (String uri : deploymentUris) { 769 deployContrib(uri); 770 } 771 applyInlineDeployments(); 772 } 773 774 /** 775 * Remove the latest deployed components using {@link #pushInlineDeployments(String...)}. 776 */ 777 public void popInlineDeployments() throws Exception { 778 if (deploymentStack.isEmpty()) { 779 throw new IllegalStateException("deployment stack is empty"); 780 } 781 popInlineDeployments(deploymentStack.size() - 1); 782 } 783 784 public void popInlineDeployments(int index) throws Exception { 785 if (index < 0 || index > deploymentStack.size() - 1) { 786 throw new IllegalStateException("deployment stack index is invalid: " + index); 787 } 788 deploymentStack.remove(index); 789 790 runtime.getComponentManager().reset(); 791 for (String[] ar : deploymentStack) { 792 for (String element : ar) { 793 deployContrib(element); 794 } 795 } 796 applyInlineDeployments(); 797 } 798 799}