1 package com.atlassian.plugin.osgi.container.felix;
2
3 import com.atlassian.plugin.ReferenceMode;
4 import com.atlassian.plugin.event.impl.DefaultPluginEventManager;
5 import com.atlassian.plugin.osgi.container.OsgiContainerException;
6 import com.atlassian.plugin.osgi.container.OsgiPersistentCache;
7 import com.atlassian.plugin.osgi.container.impl.DefaultOsgiPersistentCache;
8 import com.atlassian.plugin.osgi.container.impl.DefaultPackageScannerConfiguration;
9 import com.atlassian.plugin.test.PluginJarBuilder;
10 import com.atlassian.plugin.test.PluginTestUtils;
11 import com.google.common.base.Charsets;
12 import com.google.common.collect.ImmutableList;
13 import com.google.common.collect.ImmutableMap;
14 import com.google.common.hash.Hashing;
15 import org.apache.commons.io.FileUtils;
16 import org.apache.felix.framework.Logger;
17 import org.apache.felix.framework.cache.BundleArchive;
18 import org.hamcrest.FeatureMatcher;
19 import org.hamcrest.Matcher;
20 import org.junit.After;
21 import org.junit.Before;
22 import org.junit.Rule;
23 import org.junit.Test;
24 import org.junit.contrib.java.lang.system.RestoreSystemProperties;
25 import org.osgi.framework.Bundle;
26 import org.osgi.framework.BundleException;
27 import org.osgi.framework.Constants;
28 import org.osgi.framework.ServiceReference;
29 import org.osgi.service.packageadmin.PackageAdmin;
30 import org.osgi.util.tracker.ServiceTracker;
31 import org.osgi.util.tracker.ServiceTrackerCustomizer;
32
33 import java.io.File;
34 import java.io.FilenameFilter;
35 import java.io.IOException;
36 import java.net.URISyntaxException;
37 import java.net.URL;
38 import java.util.ArrayList;
39 import java.util.Collection;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.concurrent.CountDownLatch;
43 import java.util.concurrent.TimeUnit;
44
45 import static com.atlassian.plugin.ReferenceMode.FORBID_REFERENCE;
46 import static com.atlassian.plugin.ReferenceMode.PERMIT_REFERENCE;
47 import static com.atlassian.plugin.osgi.container.felix.FelixOsgiContainerManager.ATLASSIAN_BOOTDELEGATION;
48 import static com.atlassian.plugin.osgi.container.felix.FelixOsgiContainerManager.ATLASSIAN_BOOTDELEGATION_EXTRA;
49 import static com.atlassian.plugin.osgi.container.felix.FelixOsgiContainerManager.ATLASSIAN_DISABLE_REFERENCE_PROTOCOL;
50 import static com.atlassian.plugin.test.Matchers.fileNamed;
51 import static com.atlassian.plugin.util.PluginUtils.ATLASSIAN_PLUGINS_ENABLE_WAIT;
52 import static org.hamcrest.Matchers.allOf;
53 import static org.hamcrest.Matchers.arrayContaining;
54 import static org.hamcrest.Matchers.arrayWithSize;
55 import static org.hamcrest.Matchers.containsString;
56 import static org.hamcrest.Matchers.empty;
57 import static org.hamcrest.Matchers.emptyArray;
58 import static org.hamcrest.Matchers.equalTo;
59 import static org.hamcrest.Matchers.hasItemInArray;
60 import static org.hamcrest.Matchers.hasSize;
61 import static org.hamcrest.Matchers.is;
62 import static org.hamcrest.Matchers.not;
63 import static org.hamcrest.Matchers.notNullValue;
64 import static org.hamcrest.Matchers.nullValue;
65 import static org.hamcrest.Matchers.startsWith;
66 import static org.junit.Assert.assertThat;
67 import static org.junit.Assert.fail;
68 import static org.osgi.framework.Constants.BUNDLE_NAME;
69 import static org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME;
70
71 public class TestFelixOsgiContainerManager {
72 @Rule
73 public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
74
75 private File tmpdir;
76 private OsgiPersistentCache osgiPersistentCache;
77 private FelixOsgiContainerManager felix;
78 private URL frameworkBundlesUrl = getClass().getResource("/nothing.zip");
79
80 @Before
81 public void setUp() throws Exception {
82 tmpdir = PluginTestUtils.createTempDirectory(TestFelixOsgiContainerManager.class);
83 osgiPersistentCache = new DefaultOsgiPersistentCache(tmpdir);
84 felix = new FelixOsgiContainerManager(
85 frameworkBundlesUrl,
86 osgiPersistentCache,
87 new DefaultPackageScannerConfiguration(),
88 null,
89 new DefaultPluginEventManager());
90 }
91
92 @After
93 public void tearDown() {
94 if (felix != null && felix.isRunning()) {
95 for (final Bundle bundle : felix.getBundles()) {
96 try {
97 bundle.uninstall();
98 } catch (final BundleException ignored) {
99 }
100 }
101 }
102 if (felix != null) {
103 felix.stop();
104 felix.clearExportCache();
105 }
106 felix = null;
107 osgiPersistentCache = null;
108 tmpdir = null;
109 }
110
111 @Test
112 public void testDetectXercesOverride() {
113 felix.detectXercesOverride("foo.bar,baz.jim");
114 felix.detectXercesOverride("foo.bar,org.apache.xerces.util;version=\"1.0\",baz.jim");
115 felix.detectXercesOverride("foo.bar,org.apache.xerces.util;version=\"1.0\"");
116 felix.detectXercesOverride("foo.bar,repackaged.org.apache.xerces.util,bar.baz");
117
118
119 try {
120 felix.detectXercesOverride("foo.bar,org.apache.xerces.util");
121 fail("Should fail validation");
122 } catch (final OsgiContainerException ex) {
123
124 }
125
126 try {
127 felix.detectXercesOverride("org.apache.xerces.util");
128 fail("Should fail validation");
129 } catch (final OsgiContainerException ex) {
130
131 }
132
133 try {
134 felix.detectXercesOverride("org.apache.xerces.util,bar.baz");
135 fail("Should fail validation");
136 } catch (final OsgiContainerException ex) {
137
138 }
139
140 }
141
142 @Test
143 public void testStartStop() {
144 final FilenameFilter filter = (file, s) -> s.startsWith("felix");
145 final int filesNamedFelix = tmpdir.listFiles(filter).length;
146 felix.start();
147 assertThat(felix.isRunning(), is(true));
148 assertThat(felix.getBundles(), arrayWithSize(1));
149 felix.stop();
150 assertThat(tmpdir.listFiles(filter), arrayWithSize(filesNamedFelix));
151 }
152
153 @Test
154 public void testInstallBundle() throws URISyntaxException {
155 felix.start();
156 assertThat(felix.getBundles(), arrayWithSize(1));
157 final File jar = new File(getClass().getResource("/myapp-1.0.jar").toURI());
158 felix.installBundle(jar, FORBID_REFERENCE);
159 assertThat(felix.getBundles(), arrayWithSize(2));
160 assertThat(felix.getBundles()[1], not(bundleThatIsReference()));
161
162 final String[] justJar = {"jar"};
163 final Collection<File> cachedJars = FileUtils.listFiles(osgiPersistentCache.getOsgiBundleCache(), justJar, true);
164 assertThat(cachedJars, hasSize(1));
165 }
166
167 @Test
168 public void testInstallBundleAllowReference() throws URISyntaxException {
169 felix.start();
170 assertThat(felix.getBundles(), arrayWithSize(1));
171 final File jar = new File(getClass().getResource("/myapp-1.0.jar").toURI());
172 felix.installBundle(jar, PERMIT_REFERENCE);
173 assertThat(felix.getBundles(), arrayWithSize(2));
174 assertThat(felix.getBundles()[1], bundleThatIsReference());
175
176 final String[] justJar = {"jar"};
177 final Collection<File> cachedJars = FileUtils.listFiles(osgiPersistentCache.getOsgiBundleCache(), justJar, true);
178 assertThat(cachedJars, empty());
179 }
180
181 @Test
182 public void testInstallFileBundleWithReferenceProtocolDisabled() throws URISyntaxException {
183 felix.start();
184 assertThat(felix.getBundles(), arrayWithSize(1));
185 System.setProperty(ATLASSIAN_DISABLE_REFERENCE_PROTOCOL, "true");
186 final File jar = new File(getClass().getResource("/myapp-1.0.jar").toURI());
187 felix.installBundle(jar, ReferenceMode.PERMIT_REFERENCE);
188 assertThat(felix.getBundles(), arrayWithSize(2));
189 assertThat(felix.getBundles()[1], not(bundleThatIsReference()));
190 }
191
192 @Test
193 public void checkDefaultBootDelegation() throws Exception {
194 felix.start();
195 final Bundle clientBundle = installBootDelegationBundles();
196 try {
197
198 clientBundle.loadClass("my.client.ClientClass").newInstance();
199 fail("Expected exception: NoClassDefFoundError");
200 } catch (final NoClassDefFoundError expected) {
201 assertThat(expected.getMessage(), containsString("junit/framework/TestCase"));
202 }
203
204 clientBundle.loadClass("my.client.OtherClientClass").newInstance();
205 felix.stop();
206 }
207
208 @Test
209 public void checkReplacingBootDelegation() throws Exception {
210
211 System.setProperty(ATLASSIAN_BOOTDELEGATION, "junit.framework,junit.framework.*");
212 felix.start();
213
214 final Bundle clientBundle = installBootDelegationBundles();
215 clientBundle.loadClass("my.client.ClientClass").newInstance();
216
217 try {
218 clientBundle.loadClass("my.client.OtherClientClass").newInstance();
219 fail("Expected exception: NoClassDefFoundError");
220 } catch (final NoClassDefFoundError expected) {
221 assertThat(expected.getMessage(), containsString("com/sun/xml/bind/v2/ContextFactory"));
222 }
223 felix.stop();
224 }
225
226 @Test
227 public void checkExtraBootDelegation() throws Exception {
228
229 System.setProperty(ATLASSIAN_BOOTDELEGATION_EXTRA, "junit.framework,junit.framework.*");
230 felix.start();
231
232 final Bundle clientBundle = installBootDelegationBundles();
233 clientBundle.loadClass("my.client.ClientClass").newInstance();
234
235 clientBundle.loadClass("my.client.OtherClientClass").newInstance();
236 felix.stop();
237 }
238
239 private Bundle installBootDelegationBundles() throws Exception {
240
241 final File pluginServer = new PluginJarBuilder("plugin")
242 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
243 "Bundle-Version: 1.0\n" +
244 "Bundle-SymbolicName: my.server\n" +
245 "Bundle-ManifestVersion: 2\n" +
246 "Export-Package: my.server\n")
247 .addJava("my.server.ServerClass", "package my.server; public class ServerClass extends junit.framework.TestCase {}")
248 .addJava("my.server.OtherServerClass", "package my.server; public class OtherServerClass " +
249 "extends com.sun.xml.bind.v2.ContextFactory " +
250 "{}")
251 .build();
252
253
254
255
256 final File pluginClient = new PluginJarBuilder("plugin")
257 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
258 "Bundle-Version: 1.0\n" +
259 "Bundle-SymbolicName: my.client\n" +
260 "Bundle-ManifestVersion: 2\n" +
261 "Import-Package: my.server\n")
262 .addJava("my.client.ClientClass", "package my.client; public class ClientClass {" +
263 "public ClientClass() throws ClassNotFoundException {" +
264 "getClass().getClassLoader().loadClass(\"my.server.ServerClass\");" +
265 "}}")
266 .addJava("my.client.OtherClientClass", "package my.client; public class OtherClientClass {" +
267 "public OtherClientClass() throws ClassNotFoundException {" +
268 "getClass().getClassLoader().loadClass(\"my.server.OtherServerClass\");" +
269 "}}")
270 .build();
271
272 final Bundle serverBundle = felix.installBundle(pluginServer, FORBID_REFERENCE);
273 serverBundle.start();
274 final Bundle clientBundle = felix.installBundle(pluginClient, FORBID_REFERENCE);
275 clientBundle.start();
276 return clientBundle;
277 }
278
279
280 @Test
281 public void testInstallBundleTwice() throws IOException, BundleException {
282 final File plugin = new PluginJarBuilder("plugin")
283 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
284 "Import-Package: javax.swing\n" +
285 "Bundle-Version: 1.0\n" +
286 "Bundle-SymbolicName: my.foo.symbolicName\n" +
287 "Bundle-ManifestVersion: 2\n")
288 .addResource("foo.txt", "foo")
289 .build();
290
291 final File pluginUpdate = new PluginJarBuilder("plugin")
292 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
293 "Import-Package: javax.swing\n" +
294 "Bundle-Version: 1.0\n" +
295 "Bundle-SymbolicName: my.foo.symbolicName\n" +
296 "Bundle-ManifestVersion: 2\n")
297 .addResource("bar.txt", "bar")
298 .build();
299
300 felix.start();
301 assertThat(felix.getBundles(), arrayWithSize(1));
302 final Bundle bundle = felix.installBundle(plugin, FORBID_REFERENCE);
303 assertThat(felix.getBundles(), arrayWithSize(2));
304 assertThat(bundle, bundleWithSymbolicName("my.foo.symbolicName"));
305 assertThat(bundle.getHeaders().get(Constants.BUNDLE_VERSION), is((Object) "1.0"));
306 assertThat(bundle, bundleWithState(Bundle.INSTALLED));
307 assertThat(bundle.getResource("foo.txt"), notNullValue());
308 assertThat(bundle.getResource("bar.txt"), nullValue());
309 bundle.start();
310 assertThat(bundle, bundleWithState(Bundle.ACTIVE));
311 final Bundle bundleUpdate = felix.installBundle(pluginUpdate, FORBID_REFERENCE);
312 assertThat(felix.getBundles(), arrayWithSize(2));
313 assertThat(bundleUpdate, bundleWithState(Bundle.INSTALLED));
314 bundleUpdate.start();
315 assertThat(bundleUpdate, bundleWithState(Bundle.ACTIVE));
316 assertThat(bundleUpdate.getResource("foo.txt"), nullValue());
317 assertThat(bundleUpdate.getResource("bar.txt"), notNullValue());
318 }
319
320 @Test
321 public void testInstallBundleTwiceDifferentSymbolicNames() throws IOException, BundleException {
322 final File plugin = new PluginJarBuilder("plugin")
323 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
324 "Import-Package: javax.swing\n" +
325 "Bundle-Version: 1.0\n" +
326 "Bundle-SymbolicName: my.foo\n" +
327 "Atlassian-Plugin-Key: my.foo.symbolicName\n" +
328 "Bundle-ManifestVersion: 2\n")
329 .addResource("foo.txt", "foo")
330 .build();
331
332 final File pluginUpdate = new PluginJarBuilder("plugin")
333 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
334 "Import-Package: javax.swing\n" +
335 "Bundle-Version: 1.0\n" +
336 "Atlassian-Plugin-Key: my.foo.symbolicName\n" +
337 "Bundle-SymbolicName: my.bar\n" +
338 "Bundle-ManifestVersion: 2\n")
339 .addResource("bar.txt", "bar")
340 .build();
341
342 felix.start();
343 assertThat(felix.getBundles(), arrayWithSize(1));
344 final Bundle bundle = felix.installBundle(plugin, FORBID_REFERENCE);
345 assertThat(felix.getBundles(), arrayWithSize(2));
346 assertThat(bundle.getHeaders().get(Constants.BUNDLE_VERSION), is((Object) "1.0"));
347 assertThat(bundle, bundleWithState(Bundle.INSTALLED));
348 assertThat(bundle.getResource("foo.txt"), notNullValue());
349 assertThat(bundle.getResource("bar.txt"), nullValue());
350 bundle.start();
351 assertThat(bundle, bundleWithState(Bundle.ACTIVE));
352 final Bundle bundleUpdate = felix.installBundle(pluginUpdate, FORBID_REFERENCE);
353 assertThat(felix.getBundles(), arrayWithSize(2));
354 assertThat(bundleUpdate, bundleWithState(Bundle.INSTALLED));
355 bundleUpdate.start();
356 assertThat(bundleUpdate, bundleWithState(Bundle.ACTIVE));
357 assertThat(bundleUpdate.getResource("foo.txt"), nullValue());
358 assertThat(bundleUpdate.getResource("bar.txt"), notNullValue());
359 }
360
361 @Test
362 public void testInstallFailure() throws Exception {
363 final File plugin = new PluginJarBuilder("plugin")
364 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
365 "Bundle-Version: 1.0\n" +
366 "Import-Package: foo.missing.package\n" +
367 "Bundle-SymbolicName: my.foo.symbolicName\n" +
368 "Bundle-ManifestVersion: 2\n")
369 .build();
370 felix.start();
371
372 final Bundle bundle = felix.installBundle(plugin, FORBID_REFERENCE);
373 try {
374 bundle.loadClass("foo.bar");
375 fail("Should have thrown exception");
376 } catch (final ClassNotFoundException ex) {
377
378 }
379 }
380
381 @Test
382 public void testServiceTrackerIsClosed() {
383 felix.start();
384
385 final ServiceTracker tracker = felix.getServiceTracker(PackageAdmin.class.getCanonicalName());
386 Object[] trackedServices = tracker.getServices();
387 assertThat(trackedServices, notNullValue());
388
389 tracker.close();
390
391 trackedServices = tracker.getServices();
392 assertThat(trackedServices, nullValue());
393 }
394
395 @Test
396 public void testServiceTrackerActuallyTracksStuff() throws Exception {
397 startFelixWithListAsOSGiService();
398
399 final ServiceTracker tracker = felix.getServiceTracker(List.class.getName());
400 final Object[] trackedServices = tracker.getServices();
401
402
403 assertThat(trackedServices, arrayWithSize(1));
404 assertThat(trackedServices[0], is(ImmutableList.of("blah")));
405 }
406
407 @Test
408 public void testServiceTrackerCutomizerIsInPlace() throws Exception {
409 startFelixWithListAsOSGiService();
410 final CountDownLatch latch = new CountDownLatch(1);
411
412 felix.getServiceTracker(List.class.getName(), new ServiceTrackerCustomizer() {
413 public Object addingService(final ServiceReference reference) {
414 latch.countDown();
415 return reference;
416 }
417
418 public void modifiedService(final ServiceReference reference, final Object service) {
419 }
420
421 public void removedService(final ServiceReference reference, final Object service) {
422 }
423 });
424 assertThat(latch.await(10, TimeUnit.SECONDS), is(true));
425 }
426
427 private void startFelixWithListAsOSGiService() throws Exception {
428 final File plugin = new PluginJarBuilder("plugin")
429 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
430 "Bundle-Version: 1.0\n" +
431 "Atlassian-Plugin-Key: my.foo.symbolicName\n" +
432 "Bundle-Name: my.bar\n" +
433 "Bundle-SymbolicName: my.bar\n" +
434 "Bundle-ManifestVersion: 2\n" +
435 "Bundle-Activator: my.MyActivator\n" +
436 "Import-Package: org.osgi.framework\n")
437 .addJava("my.MyActivator", "package my;" +
438 "import org.osgi.framework.ServiceRegistration;\n" +
439 "import org.osgi.framework.BundleActivator;\n" +
440 "import org.osgi.framework.BundleContext;\n" +
441 "import java.util.*;\n" +
442 "public class MyActivator implements BundleActivator {\n" +
443 " private ServiceRegistration registration;\n" +
444 " public void start(BundleContext context) throws Exception\n" +
445 " {\n" +
446 " context.registerService(List.class.getName(), Arrays.asList(new Object[]{ \"blah\" }), new Properties());\n" +
447 " }\n" +
448 " public void stop(BundleContext context) throws Exception {}\n" +
449 "}")
450 .build();
451 felix.start();
452 final Bundle bundle = felix.installBundle(plugin, FORBID_REFERENCE);
453 bundle.start();
454 }
455
456 @Test
457 public void testRuntimeEnvironment() {
458 final int timeout = 3883;
459 setPluginTimeout(timeout);
460 final String expectedRuntimeEnvironment = String.format(
461 "java.version=%s,plugin.enable.timeout=%d",
462 System.getProperty("java.version"), timeout);
463
464 assertThat(felix.getRuntimeEnvironment(), is(expectedRuntimeEnvironment));
465 }
466
467 @Test
468 public void testRuntimeEnvironmentIntegration() throws Exception {
469 final int timeout = 678;
470 setPluginTimeout(timeout);
471
472 startFelixWithListAsOSGiService();
473
474 final File versionFile = new File(new File(tmpdir, "transformed-plugins"), "cache.key");
475 assertThat(versionFile.exists(), is(true));
476 final String txt = FileUtils.readFileToString(versionFile);
477
478 final ExportsBuilder eBuilder = new ExportsBuilder();
479 final String systemExports = eBuilder.getExports(new ArrayList<>(),
480 new DefaultPackageScannerConfiguration());
481
482
483 final String expectedKey = String.format(
484 "java.version=%s,plugin.enable.timeout=%d,%s",
485 System.getProperty("java.version"),
486 timeout,
487 systemExports);
488 assertThat(txt, is(Hashing.sha1().hashString(expectedKey, Charsets.UTF_8).toString()));
489 }
490
491 @Test
492 public void testFrameworkBundlesUnzipped() {
493
494
495 assertThat(osgiPersistentCache.getFrameworkBundleCache().listFiles(), emptyArray());
496 felix.start();
497 assertThat(osgiPersistentCache.getFrameworkBundleCache().listFiles(), arrayContaining(fileNamed("nothing.txt")));
498 }
499
500 @Test
501 public void testFrameworkBundleDirectorySkipsUnzip() throws Exception {
502 final File frameworkBundles = new File(tmpdir, "provided-framework-bundles");
503 FileUtils.forceMkdir(frameworkBundles);
504 final String symbolicName = "my.foo.frameworkbundle";
505 final Map<String, String> manifest = ImmutableMap.<String, String>builder()
506 .put("Manifest-Version", "1.0")
507 .put(BUNDLE_NAME, "Framework Bundle")
508 .put(BUNDLE_SYMBOLICNAME, symbolicName)
509 .build();
510 new PluginJarBuilder("framework-bundle")
511 .manifest(manifest)
512 .build(frameworkBundles);
513 felix = new FelixOsgiContainerManager(
514 frameworkBundles,
515 osgiPersistentCache,
516 new DefaultPackageScannerConfiguration(),
517 null,
518 new DefaultPluginEventManager());
519
520 felix.start();
521
522 assertThat(osgiPersistentCache.getFrameworkBundleCache().listFiles(), emptyArray());
523
524 assertThat(felix.getBundles(), hasItemInArray(bundleWithSymbolicName(symbolicName)));
525 }
526
527 @Test
528 public void testFrameworkBundleDirectoryDoesNotExistThrowsNiceException() {
529 final File frameworkBundles = new File(tmpdir, "framework-bundles-dir-does-not-exist");
530 felix = new FelixOsgiContainerManager(
531 frameworkBundles,
532 osgiPersistentCache,
533 new DefaultPackageScannerConfiguration(),
534 null,
535 new DefaultPluginEventManager());
536
537 TestFelixLogger logger = new TestFelixLogger();
538 logger.setLogLevel(Logger.LOG_WARNING);
539 felix.setFelixLogger(logger);
540
541 assertThat(frameworkBundles.exists(), is(false));
542 felix.start();
543
544 assertThat(logger.getExceptions(), hasSize(1));
545
546 Throwable exception = logger.getExceptions().get(0);
547 Matcher<String> matchesException = allOf(containsString("BundleException"), containsString(frameworkBundles.toString()));
548 assertThat(exception.toString(), matchesException);
549 }
550
551 private void setPluginTimeout(final int timeout) {
552 System.setProperty(ATLASSIAN_PLUGINS_ENABLE_WAIT, String.valueOf(timeout));
553 }
554
555
556
557
558
559
560 private static Matcher<Bundle> bundleThatIsReference() {
561 return bundleWithLocation(startsWith(BundleArchive.REFERENCE_PROTOCOL));
562 }
563
564
565
566
567
568
569 private static Matcher<Bundle> bundleWithLocation(final Matcher<String> locationMatcher) {
570 return new FeatureMatcher<Bundle, String>(locationMatcher, "bundle with location", "bundle location") {
571 @Override
572 protected String featureValueOf(final Bundle bundle) {
573 return bundle.getLocation();
574 }
575 };
576 }
577
578
579
580
581
582
583
584 private static Matcher<Bundle> bundleWithSymbolicName(final String symbolicName) {
585 return new FeatureMatcher<Bundle, String>(equalTo(symbolicName), "bundle with symbolic name", "symbolic name") {
586 @Override
587 protected String featureValueOf(final Bundle bundle) {
588 return bundle.getSymbolicName();
589 }
590 };
591 }
592
593
594
595
596
597
598
599 private static Matcher<Bundle> bundleWithState(final Integer bundleState) {
600 return new FeatureMatcher<Bundle, Integer>(equalTo(bundleState), "a bundle with state", "bundle state") {
601 @Override
602 protected Integer featureValueOf(final Bundle bundle) {
603 return bundle.getState();
604 }
605 };
606 }
607
608 private static class TestFelixLogger extends Logger {
609
610 private final List<Throwable> exceptions = new ArrayList<>();
611
612 @Override
613 protected void doLog(int level, String msg, Throwable throwable) {
614 super.doLog(level, msg, throwable);
615 exceptions.add(throwable);
616 }
617
618 List<Throwable> getExceptions() {
619 return exceptions;
620 }
621 }
622 }