Repository: incubator-brooklyn Updated Branches: refs/heads/master 3cfda7e8f -> dc53cec5d
support multiple instances of the same osgi symbolic-name:version Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/72b29b95 Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/72b29b95 Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/72b29b95 Branch: refs/heads/master Commit: 72b29b950b0ad2839dc0e3beafb814089a466472 Parents: 3cfda7e Author: Alex Heneveld <[email protected]> Authored: Thu Nov 6 17:41:33 2014 +0000 Committer: Alex Heneveld <[email protected]> Committed: Tue Nov 11 01:52:05 2014 +0000 ---------------------------------------------------------------------- .../brooklyn/management/ha/OsgiManager.java | 78 +++-- .../main/java/brooklyn/util/ResourceUtils.java | 2 +- .../src/main/java/brooklyn/util/osgi/Osgis.java | 300 +++++++++++++++---- .../osgi/more-entities-v2-evil-twin/pom.xml | 88 ++++++ .../brooklyn/osgi/tests/more/MoreEntity.java | 38 +++ .../osgi/tests/more/MoreEntityImpl.java | 47 +++ .../brooklyn/catalog/internal/CatalogItems.java | 8 + .../management/osgi/OsgiTestResources.java | 16 +- .../osgi/OsgiVersionMoreEntityTest.java | 116 +++++-- ...-test-osgi-more-entities_evil-twin_0.2.0.jar | Bin 0 -> 12757 bytes ...-test-osgi-more-entities_evil-twin_0.2.0.txt | 21 ++ .../util/exceptions/ReferenceWithError.java | 6 +- 12 files changed, 601 insertions(+), 119 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b29b95/core/src/main/java/brooklyn/management/ha/OsgiManager.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/management/ha/OsgiManager.java b/core/src/main/java/brooklyn/management/ha/OsgiManager.java index 3dea979..44b30af 100644 --- a/core/src/main/java/brooklyn/management/ha/OsgiManager.java +++ b/core/src/main/java/brooklyn/management/ha/OsgiManager.java @@ -21,6 +21,7 @@ package brooklyn.management.ha; import java.io.File; import java.net.URL; import java.util.Arrays; +import java.util.List; import java.util.Map; import org.osgi.framework.Bundle; @@ -51,7 +52,9 @@ public class OsgiManager { protected Framework framework; protected File osgiTempDir; - protected Map<String,String> bundleUrlToNameVersionString = MutableMap.of(); + + // we could manage without this map but it is useful to validate what is a user-supplied url + protected Map<String,String> urlToBundleIdentifier = MutableMap.of(); public void start() { try { @@ -87,19 +90,32 @@ public class OsgiManager { public void registerBundle(String bundleUrl) { try { - String nv = bundleUrlToNameVersionString.get(bundleUrl); + String nv = urlToBundleIdentifier.get(bundleUrl); if (nv!=null) { - if (Osgis.getBundle(framework, nv).isPresent()) { + if (Osgis.bundleFinder(framework).id(nv).requiringFromUrl(bundleUrl).find().isPresent()) { log.trace("Bundle from "+bundleUrl+" already installed as "+nv+"; not re-registering"); return; + } else { + log.debug("Bundle "+nv+" from "+bundleUrl+" is known in map but not installed; perhaps in the process of installing?"); } } + Bundle b = Osgis.install(framework, bundleUrl); nv = b.getSymbolicName()+":"+b.getVersion().toString(); - // TODO if there is another entry for name:version we should log a warning at the very least, - // or better provide a way to get back *this* bundle - bundleUrlToNameVersionString.put(bundleUrl, nv); - log.debug("Bundle from "+bundleUrl+" successfully installed as " + nv + " ("+b+")"); + + List<Bundle> matches = Osgis.bundleFinder(framework).id(nv).findAll(); + if (matches.isEmpty()) { + log.error("OSGi could not find bundle "+nv+" in search after installing it from "+bundleUrl); + } else if (matches.size()==1) { + log.debug("Bundle from "+bundleUrl+" successfully installed as " + nv + " ("+b+")"); + } else { + log.warn("OSGi has multiple bundles matching "+nv+", when just installed from "+bundleUrl+": "+matches+"; " + + "brooklyn will prefer the URL-based bundle for top-level references but any dependencies or " + + "import-packages will be at the mercy of OSGi. " + + "It is recommended to use distinct versions for different bundles, and the same URL for the same bundles."); + } + urlToBundleIdentifier.put(bundleUrl, nv); + } catch (BundleException e) { log.debug("Bundle from "+bundleUrl+" failed to install (rethrowing): "+e); throw Throwables.propagate(e); @@ -112,15 +128,9 @@ public class OsgiManager { public <T> Maybe<Class<T>> tryResolveClass(String type, Iterable<String> bundleUrlsOrNameVersionString) { Map<String,Throwable> bundleProblems = MutableMap.of(); for (String bundleUrlOrNameVersionString: bundleUrlsOrNameVersionString) { - boolean noVersionInstalled = false; try { - String bundleNameVersion = bundleUrlToNameVersionString.get(bundleUrlOrNameVersionString); - if (bundleNameVersion==null) { - noVersionInstalled = true; - bundleNameVersion = bundleUrlOrNameVersionString; - } - - Maybe<Bundle> bundle = Osgis.getBundle(framework, bundleNameVersion); + Maybe<Bundle> bundle = findBundle(bundleUrlOrNameVersionString); + if (bundle.isPresent()) { Bundle b = bundle.get(); Class<T> clazz; @@ -137,20 +147,13 @@ public class OsgiManager { } return Maybe.of(clazz); } else { - bundleProblems.put(bundleUrlOrNameVersionString, new IllegalStateException("Unable to find bundle "+bundleUrlOrNameVersionString)); + bundleProblems.put(bundleUrlOrNameVersionString, ((Maybe.Absent<?>)bundle).getException()); } + } catch (Exception e) { + // should come from classloading now; name formatting or missing bundle errors will be caught above Exceptions.propagateIfFatal(e); - if (noVersionInstalled) { - if (bundleUrlOrNameVersionString.contains("/")) { - // suppress misleading nested trace if the input string looked like a URL - bundleProblems.put(bundleUrlOrNameVersionString, new IllegalStateException("Bundle does not appear to be installed")); - } else { - bundleProblems.put(bundleUrlOrNameVersionString, new IllegalStateException("Bundle does not appear to be installed", e)); - } - } else { - bundleProblems.put(bundleUrlOrNameVersionString, e); - } + bundleProblems.put(bundleUrlOrNameVersionString, e); Throwable cause = e.getCause(); if (cause != null && cause.getMessage().contains("Unresolved constraint in bundle")) { @@ -170,15 +173,22 @@ public class OsgiManager { } } + /** finds an installed bundle with the given URL or OSGi identifier ("symbolicName:version" string) */ + public Maybe<Bundle> findBundle(String bundleUrlOrNameVersionString) { + String bundleNameVersion = urlToBundleIdentifier.get(bundleUrlOrNameVersionString); + if (bundleNameVersion==null) { + Maybe<String[]> nv = Osgis.parseOsgiIdentifier(bundleUrlOrNameVersionString); + if (nv.isPresent()) + bundleNameVersion = bundleUrlOrNameVersionString; + } + Maybe<Bundle> bundle = Osgis.bundleFinder(framework).id(bundleNameVersion).preferringFromUrl(bundleUrlOrNameVersionString).find(); + return bundle; + } + public URL getResource(String name, Iterable<String> bundleUrlsOrNameVersionString) { for (String bundleUrlOrNameVersionString: bundleUrlsOrNameVersionString) { try { - String bundleNameVersion = bundleUrlToNameVersionString.get(bundleUrlOrNameVersionString); - if (bundleNameVersion==null) { - bundleNameVersion = bundleUrlOrNameVersionString; - } - - Maybe<Bundle> bundle = Osgis.getBundle(framework, bundleNameVersion); + Maybe<Bundle> bundle = findBundle(bundleUrlOrNameVersionString); if (bundle.isPresent()) { URL result = bundle.get().getResource(name); if (result!=null) return result; @@ -190,4 +200,8 @@ public class OsgiManager { return null; } + public Framework getFramework() { + return framework; + } + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b29b95/core/src/main/java/brooklyn/util/ResourceUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/ResourceUtils.java b/core/src/main/java/brooklyn/util/ResourceUtils.java index 072789e..b40df0d 100644 --- a/core/src/main/java/brooklyn/util/ResourceUtils.java +++ b/core/src/main/java/brooklyn/util/ResourceUtils.java @@ -323,7 +323,7 @@ public class ResourceUtils { } catch (MalformedURLException e) { throw Exceptions.propagate(e); } - if (!urlOut.equals(in) && log.isDebugEnabled()) { + if (!urlOut.equals(url) && log.isDebugEnabled()) { log.debug("quietly changing " + url + " to " + urlOut); } return urlOut; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b29b95/core/src/main/java/brooklyn/util/osgi/Osgis.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/osgi/Osgis.java b/core/src/main/java/brooklyn/util/osgi/Osgis.java index d8f20ae..f7508f6 100644 --- a/core/src/main/java/brooklyn/util/osgi/Osgis.java +++ b/core/src/main/java/brooklyn/util/osgi/Osgis.java @@ -28,10 +28,14 @@ import java.io.InputStreamReader; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.jar.Attributes; import java.util.jar.JarInputStream; import java.util.jar.JarOutputStream; @@ -56,13 +60,16 @@ import org.slf4j.LoggerFactory; import brooklyn.util.ResourceUtils; import brooklyn.util.collections.MutableList; import brooklyn.util.collections.MutableMap; +import brooklyn.util.collections.MutableSet; import brooklyn.util.exceptions.Exceptions; import brooklyn.util.exceptions.ReferenceWithError; import brooklyn.util.guava.Maybe; import brooklyn.util.net.Urls; import brooklyn.util.os.Os; import brooklyn.util.stream.Streams; +import brooklyn.util.text.Strings; import brooklyn.util.time.Duration; +import brooklyn.util.time.Time; import com.google.common.annotations.Beta; import com.google.common.base.Joiner; @@ -82,54 +89,195 @@ public class Osgis { private static final String EXTENSION_PROTOCOL = "system"; private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF"; + private static final Set<String> SYSTEM_BUNDLES = MutableSet.of(); + + public static class BundleFinder { + protected final Framework framework; + protected String symbolicName; + protected String version; + protected String url; + protected boolean urlMandatory = false; + protected final List<Predicate<? super Bundle>> predicates = MutableList.of(); + + protected BundleFinder(Framework framework) { + this.framework = framework; + } - public static List<Bundle> getBundlesByName(Framework framework, String symbolicName, Predicate<Version> versionMatcher) { - List<Bundle> result = MutableList.of(); - for (Bundle b: framework.getBundleContext().getBundles()) { - if (symbolicName.equals(b.getSymbolicName()) && versionMatcher.apply(b.getVersion())) { + public BundleFinder symbolicName(String symbolicName) { + this.symbolicName = symbolicName; + return this; + } + + public BundleFinder version(String version) { + this.version = version; + return this; + } + + public BundleFinder id(String symbolicNameOptionallyWithVersion) { + if (Strings.isBlank(symbolicNameOptionallyWithVersion)) + return this; + + Maybe<String[]> partsM = parseOsgiIdentifier(symbolicNameOptionallyWithVersion); + if (partsM.isAbsent()) + throw new IllegalArgumentException("Cannot parse symbolic-name:version string '"+symbolicNameOptionallyWithVersion+"'"); + String[] parts = partsM.get(); + + symbolicName(parts[0]); + if (parts.length >= 2) version(parts[1]); + + return this; + } + + /** Looks for a bundle matching the given URL; + * unlike {@link #requiringFromUrl(String)} however, if the URL does not match any bundles + * it will return other matching bundles <i>if</if> a {@link #symbolicName(String)} is specified. + */ + public BundleFinder preferringFromUrl(String url) { + this.url = url; + urlMandatory = false; + return this; + } + + /** Requires the bundle to have the given URL set as its location. */ + public BundleFinder requiringFromUrl(String url) { + this.url = url; + urlMandatory = true; + return this; + } + + /** Finds the best matching bundle. */ + public Maybe<Bundle> find() { + return findOne(false); + } + + /** Finds the matching bundle, requiring it to be unique. */ + public Maybe<Bundle> findUnique() { + return findOne(true); + } + + protected Maybe<Bundle> findOne(boolean requireExactlyOne) { + if (symbolicName==null && url==null) + throw new IllegalStateException(this+" must be given either a symbolic name or a URL"); + + List<Bundle> result = findAll(); + if (result.isEmpty()) + return Maybe.absent("No bundle matching "+getConstraintsDescription()); + if (requireExactlyOne && result.size()>1) + return Maybe.absent("Multiple bundles ("+result.size()+") matching "+getConstraintsDescription()); + + return Maybe.of(result.get(0)); + } + + /** Finds all matching bundles, in decreasing version order. */ + public List<Bundle> findAll() { + boolean urlMatched = false; + List<Bundle> result = MutableList.of(); + for (Bundle b: framework.getBundleContext().getBundles()) { + if (symbolicName!=null && !symbolicName.equals(b.getSymbolicName())) continue; + if (version!=null && !Version.parseVersion(version).equals(b.getVersion())) continue; + for (Predicate<? super Bundle> predicate: predicates) { + if (!predicate.apply(b)) continue; + } + + // check url last, because if it isn't mandatory we should only clear if we find a url + // for which the other items also match + if (url!=null) { + boolean matches = url.equals(b.getLocation()); + if (urlMandatory) { + if (!matches) continue; + } else { + if (matches) { + if (!urlMatched) { + result.clear(); + urlMatched = true; + } + } else { + if (urlMatched) { + // can't use this bundle as we have previously found a preferred bundle, with a matching url + continue; + } + } + } + } + result.add(b); } + + if (symbolicName==null && url!=null && !urlMatched) { + // if we only "preferred" the url, and we did not match it, and we did not have a symbolic name, + // then clear the results list! + result.clear(); + } + + Collections.sort(result, new Comparator<Bundle>() { + @Override + public int compare(Bundle o1, Bundle o2) { + return o2.getVersion().compareTo(o1.getVersion()); + } + }); + + return result; + } + + public String getConstraintsDescription() { + List<String> parts = MutableList.of(); + if (symbolicName!=null) parts.add("symbolicName="+symbolicName); + if (version!=null) parts.add("version="+version); + if (url!=null) + parts.add("url["+(urlMandatory ? "required" : "preferred")+"]="+url); + if (!predicates.isEmpty()) + parts.add("predicates="+predicates); + return Joiner.on(";").join(parts); + } + + public String toString() { + return getClass().getCanonicalName()+"["+getConstraintsDescription()+"]"; + } + + public BundleFinder version(final Predicate<Version> versionPredicate) { + return satisfying(new Predicate<Bundle>() { + @Override + public boolean apply(Bundle input) { + return versionPredicate.apply(input.getVersion()); + } + }); + } + + public BundleFinder satisfying(Predicate<? super Bundle> predicate) { + predicates.add(predicate); + return this; } - return result; + } + + public static BundleFinder bundleFinder(Framework framework) { + return new BundleFinder(framework); + } + + /** @deprecated since 0.7.0 use {@link #bundleFinder(Framework)} */ @Deprecated + public static List<Bundle> getBundlesByName(Framework framework, String symbolicName, Predicate<Version> versionMatcher) { + return bundleFinder(framework).symbolicName(symbolicName).version(versionMatcher).findAll(); } + /** @deprecated since 0.7.0 use {@link #bundleFinder(Framework)} */ @Deprecated public static List<Bundle> getBundlesByName(Framework framework, String symbolicName) { - return getBundlesByName(framework, symbolicName, Predicates.<Version>alwaysTrue()); + return bundleFinder(framework).symbolicName(symbolicName).findAll(); } /** * Tries to find a bundle in the given framework with name matching either `name' or `name:version'. - */ + * @deprecated since 0.7.0 use {@link #bundleFinder(Framework)} */ @Deprecated public static Maybe<Bundle> getBundle(Framework framework, String symbolicNameOptionallyWithVersion) { - String[] parts = symbolicNameOptionallyWithVersion.split(":"); - Maybe<Bundle> result = Maybe.absent("No bundles matching "+symbolicNameOptionallyWithVersion); - if (parts.length == 2) { - result = getBundle(framework, parts[0], parts[1]); - } else if (parts.length == 1) { - // TODO: Select latest version rather than first result - List<Bundle> matches = getBundlesByName(framework, symbolicNameOptionallyWithVersion); - if (!matches.isEmpty()) { - result = Maybe.of(matches.iterator().next()); - } - } else { - throw new IllegalArgumentException("Cannot parse symbolic-name:version string '"+symbolicNameOptionallyWithVersion+"'"); - } - return result; + return bundleFinder(framework).id(symbolicNameOptionallyWithVersion).find(); } + /** @deprecated since 0.7.0 use {@link #bundleFinder(Framework)} */ @Deprecated public static Maybe<Bundle> getBundle(Framework framework, String symbolicName, String version) { - return getBundle(framework, symbolicName, Version.parseVersion(version)); + return bundleFinder(framework).symbolicName(symbolicName).version(version).find(); } + /** @deprecated since 0.7.0 use {@link #bundleFinder(Framework)} */ @Deprecated public static Maybe<Bundle> getBundle(Framework framework, String symbolicName, Version version) { - List<Bundle> matches = getBundlesByName(framework, symbolicName, Predicates.equalTo(version)); - if (matches.isEmpty()) { - return Maybe.absent("No bundles matching name=" + symbolicName + " version=" + version); - } else if (matches.size() > 1) { - LOG.warn("More than one bundle in framework={} matched name={}, version={}! Returning first of matches={}", - new Object[]{framework, symbolicName, version, Joiner.on(", ").join(matches)}); - } - return Maybe.of(matches.iterator().next()); + return bundleFinder(framework).symbolicName(symbolicName).version(Predicates.equalTo(version)).findUnique(); } // -------- creating @@ -168,6 +316,7 @@ public class Osgis { Map<Object,Object> cfg = MutableMap.copyOf(extraStartupConfig); if (clean) cfg.put(Constants.FRAMEWORK_STORAGE_CLEAN, "onFirstInit"); if (felixCacheDir!=null) cfg.put(Constants.FRAMEWORK_STORAGE, felixCacheDir); + cfg.put(Constants.FRAMEWORK_BSNVERSION, Constants.FRAMEWORK_BSNVERSION_MULTIPLE); FrameworkFactory factory = newFrameworkFactory(); Stopwatch timer = Stopwatch.createStarted(); @@ -180,12 +329,14 @@ public class Osgis { // framework bundle start exceptions are not interesting to caller... throw Exceptions.propagate(e); } + LOG.debug("System bundles are: "+SYSTEM_BUNDLES); LOG.debug("OSGi framework started in " + Duration.of(timer)); - return framework; } private static void installBootBundles(Framework framework) { + Stopwatch timer = Stopwatch.createStarted(); + LOG.debug("Installing OSGi boot bundles from "+Osgis.class.getClassLoader()+"..."); Enumeration<URL> resources; try { resources = Osgis.class.getClassLoader().getResources(MANIFEST_PATH); @@ -193,29 +344,32 @@ public class Osgis { throw Exceptions.propagate(e); } BundleContext bundleContext = framework.getBundleContext(); - Map<String, Bundle> installedBundles = getInstalledBundles(bundleContext); + Map<String, Bundle> installedBundles = getInstalledBundlesById(bundleContext); while(resources.hasMoreElements()) { URL url = resources.nextElement(); - ReferenceWithError<Boolean> installResult = installExtensionBundle(bundleContext, url, installedBundles, getVersionedId(framework)); - if (installResult.hasError()) { - if (installResult.getWithoutError()) { - // true return code means it was installed or trivially not installed - if (LOG.isTraceEnabled()) - LOG.trace(installResult.getError().getMessage()); - } else { - if (installResult.masksErrorIfPresent()) { - // if error is masked, then it's not so important (many of the bundles we are looking at won't have manifests) - LOG.debug(installResult.getError().getMessage()); + ReferenceWithError<?> installResult = installExtensionBundle(bundleContext, url, installedBundles, getVersionedId(framework)); + if (installResult.hasError() && !installResult.masksErrorIfPresent()) { + // it's reported as a critical error, so warn here + LOG.warn("Unable to install manifest from "+url+": "+installResult.getError(), installResult.getError()); + } else { + Object result = installResult.getWithoutError(); + if (result instanceof Bundle) { + String v = getVersionedId( (Bundle)result ); + SYSTEM_BUNDLES.add(v); + if (installResult.hasError()) { + LOG.debug(installResult.getError().getMessage()+(result!=null ? " ("+result+"/"+v+")" : "")); } else { - // it's reported as a critical error, so warn here - LOG.warn("Unable to install manifest from "+url+": "+installResult.getError(), installResult.getError()); + LOG.debug("Installed "+v+" from "+url); } + } else if (installResult.hasError()) { + LOG.debug(installResult.getError().getMessage()); } } } + LOG.debug("Installed OSGi boot bundles in "+Time.makeTimeStringRounded(timer)+": "+Arrays.asList(framework.getBundleContext().getBundles())); } - private static Map<String, Bundle> getInstalledBundles(BundleContext bundleContext) { + private static Map<String, Bundle> getInstalledBundlesById(BundleContext bundleContext) { Map<String, Bundle> installedBundles = new HashMap<String, Bundle>(); Bundle[] bundles = bundleContext.getBundles(); for (Bundle b : bundles) { @@ -224,15 +378,21 @@ public class Osgis { return installedBundles; } - private static ReferenceWithError<Boolean> installExtensionBundle(BundleContext bundleContext, URL manifestUrl, Map<String, Bundle> installedBundles, String frameworkVersionedId) { + /** Wraps the bundle if successful or already installed, wraps TRUE if it's the system entry, + * wraps null if the bundle is already installed from somewhere else; + * in all these cases <i>masking</i> an explanatory error if already installed or it's the system entry. + * <p> + * Returns an instance wrapping null and <i>throwing</i> an error if the bundle could not be installed. + */ + private static ReferenceWithError<?> installExtensionBundle(BundleContext bundleContext, URL manifestUrl, Map<String, Bundle> installedBundles, String frameworkVersionedId) { //ignore http://felix.extensions:9/ system entry if("felix.extensions".equals(manifestUrl.getHost())) - return ReferenceWithError.newInstanceMaskingError(true, new IllegalArgumentException("Skiping install of internal extension bundle from "+manifestUrl)); + return ReferenceWithError.newInstanceMaskingError(null, new IllegalArgumentException("Skipping install of internal extension bundle from "+manifestUrl)); try { Manifest manifest = readManifest(manifestUrl); if (!isValidBundle(manifest)) - return ReferenceWithError.newInstanceMaskingError(false, new IllegalArgumentException("Resource at "+manifestUrl+" is not an OSGi bundle: no valid manifest")); + return ReferenceWithError.newInstanceMaskingError(null, new IllegalArgumentException("Resource at "+manifestUrl+" is not an OSGi bundle: no valid manifest")); String versionedId = getVersionedId(manifest); URL bundleUrl = ResourceUtils.getContainerUrl(manifestUrl, MANIFEST_PATH); @@ -242,9 +402,9 @@ public class Osgis { if (!bundleUrl.equals(existingBundle.getLocation()) && //the framework bundle is always pre-installed, don't display duplicate info !versionedId.equals(frameworkVersionedId)) { - return ReferenceWithError.newInstanceMaskingError(false, new IllegalArgumentException("Bundle "+versionedId+" (from manifest " + manifestUrl + ") is already installed, from " + existingBundle.getLocation())); + return ReferenceWithError.newInstanceMaskingError(null, new IllegalArgumentException("Bundle "+versionedId+" (from manifest " + manifestUrl + ") is already installed, from " + existingBundle.getLocation())); } - return ReferenceWithError.newInstanceMaskingError(true, new IllegalArgumentException("Bundle "+versionedId+" from manifest " + manifestUrl + " is already installed")); + return ReferenceWithError.newInstanceMaskingError(existingBundle, new IllegalArgumentException("Bundle "+versionedId+" from manifest " + manifestUrl + " is already installed")); } byte[] jar = buildExtensionBundle(manifest); @@ -253,10 +413,10 @@ public class Osgis { //(since we cannot access BundleImpl.isExtension) Bundle newBundle = bundleContext.installBundle(EXTENSION_PROTOCOL + ":" + bundleUrl.toString(), new ByteArrayInputStream(jar)); installedBundles.put(versionedId, newBundle); - return ReferenceWithError.newInstanceWithoutError(true); + return ReferenceWithError.newInstanceWithoutError(newBundle); } catch (Exception e) { Exceptions.propagateIfFatal(e); - return ReferenceWithError.newInstanceThrowingError(false, + return ReferenceWithError.newInstanceThrowingError(null, new IllegalStateException("Problem installing extension bundle " + manifestUrl + ": "+e, e)); } } @@ -364,10 +524,11 @@ public class Osgis { return bundle; } - //Note that in OSGi 4.3+ it could be possible to have the same version installed - //multiple times in more advanced scenarios. In our case we don't support it. + // We now support same version installed multiple times (avail since OSGi 4.3+). + // However we do not support overriding *system* bundles, ie anything already on the classpath. + // If we wanted to disable multiple versions, see comments below, and reference to FRAMEWORK_BSNVERSION_MULTIPLE above. - //Felix already assumes the stream is pointing to a Jar + // Felix already assumes the stream is pointing to a JAR JarInputStream stream; try { stream = new JarInputStream(getUrlStream(url)); @@ -379,7 +540,15 @@ public class Osgis { String versionedId = getVersionedId(manifest); for (Bundle installedBundle : framework.getBundleContext().getBundles()) { if (versionedId.equals(getVersionedId(installedBundle))) { - return installedBundle; + if (SYSTEM_BUNDLES.contains(versionedId)) { + LOG.debug("Already have system bundle "+versionedId+" from "+installedBundle+"/"+installedBundle.getLocation()+" when requested "+url+"; not installing"); + // "System bundles" (ie things on the classpath) cannot be overridden + return installedBundle; + } else { + LOG.debug("Already have bundle "+versionedId+" from "+installedBundle+"/"+installedBundle.getLocation()+" when requested "+url+"; but it is not a system bundle so proceeding"); + // Other bundles can be installed multiple times. To ignore multiples and continue to use the old one, + // just return the installedBundle as done just above for system bundles. + } } } return null; @@ -395,6 +564,25 @@ public class Osgis { EXTENSION_PROTOCOL.equals(Urls.getProtocol(location)); } + /** Takes a string which might be of the form "symbolic-name" or "symbolic-name:version" (or something else entirely) + * and returns an array of 1 or 2 string items being the symbolic name or symbolic name and version if possible + * (or returning {@link Maybe#absent()} if not, with a suitable error message). */ + public static Maybe<String[]> parseOsgiIdentifier(String symbolicNameOptionalWithVersion) { + if (Strings.isBlank(symbolicNameOptionalWithVersion)) + return Maybe.absent("OSGi identifier is blank"); + + String[] parts = symbolicNameOptionalWithVersion.split(":"); + if (parts.length>2) + return Maybe.absent("OSGi identifier has too many parts; max one ':' symbol"); + + try { + Version.parseVersion(parts[1]); + } catch (IllegalArgumentException e) { + return Maybe.absent("OSGi identifier has invalid version string"); + } + + return Maybe.of(parts); + } /** * The class is not used, staying for future reference. http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b29b95/core/src/test/dependencies/osgi/more-entities-v2-evil-twin/pom.xml ---------------------------------------------------------------------- diff --git a/core/src/test/dependencies/osgi/more-entities-v2-evil-twin/pom.xml b/core/src/test/dependencies/osgi/more-entities-v2-evil-twin/pom.xml new file mode 100644 index 0000000..28655c2 --- /dev/null +++ b/core/src/test/dependencies/osgi/more-entities-v2-evil-twin/pom.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <packaging>jar</packaging> + + <groupId>org.apache.brooklyn.test.resources.osgi.evil_twin</groupId> + <artifactId>brooklyn-test-osgi-more-entities</artifactId> + <version>0.2.0</version> + + <name>OSGi bundled test entities</name> + + <description> + Simple entities for testing the OSGi functionality + </description> + + <parent> + <groupId>org.apache.brooklyn</groupId> + <artifactId>brooklyn-parent</artifactId> + <version>0.7.0-SNAPSHOT</version><!-- BROOKLYN_VERSION --> + <relativePath>../../../../../../pom.xml</relativePath> + </parent> + <dependencies> + <dependency> + <groupId>org.apache.brooklyn</groupId> + <artifactId>brooklyn-core</artifactId> + <version>${brooklyn.version}</version> + </dependency> + <dependency> + <groupId>org.apache.brooklyn</groupId> + <artifactId>brooklyn-api</artifactId> + <version>${brooklyn.version}</version> + </dependency> + <dependency> + <groupId>org.apache.brooklyn</groupId> + <artifactId>brooklyn-utils-common</artifactId> + <version>${brooklyn.version}</version> + </dependency> + <dependency> + <groupId>org.apache.brooklyn.test.resources.osgi</groupId> + <artifactId>brooklyn-test-osgi-entities</artifactId> + <version>0.1.0</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>2.3.2</version> + <configuration> + <outputDirectory>../../../resources/brooklyn/osgi</outputDirectory> + <finalName>brooklyn-test-osgi-more-entities_evil-twin_${version}</finalName> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <version>2.5.3</version> + <configuration> + <instructions> + <Bundle-SymbolicName>org.apache.brooklyn.test.resources.osgi.brooklyn-test-osgi-more-entities</Bundle-SymbolicName> + <Bundle-Version>${project.version}</Bundle-Version> + </instructions> + </configuration> + </plugin> + </plugins> + </build> +</project> http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b29b95/core/src/test/dependencies/osgi/more-entities-v2-evil-twin/src/main/java/brooklyn/osgi/tests/more/MoreEntity.java ---------------------------------------------------------------------- diff --git a/core/src/test/dependencies/osgi/more-entities-v2-evil-twin/src/main/java/brooklyn/osgi/tests/more/MoreEntity.java b/core/src/test/dependencies/osgi/more-entities-v2-evil-twin/src/main/java/brooklyn/osgi/tests/more/MoreEntity.java new file mode 100644 index 0000000..553cbc5 --- /dev/null +++ b/core/src/test/dependencies/osgi/more-entities-v2-evil-twin/src/main/java/brooklyn/osgi/tests/more/MoreEntity.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package brooklyn.osgi.tests.more; + + +import brooklyn.entity.Effector; +import brooklyn.entity.Entity; +import brooklyn.entity.effector.Effectors; +import brooklyn.entity.proxying.ImplementedBy; + +@ImplementedBy(MoreEntityImpl.class) +public interface MoreEntity extends Entity { + + public static final Effector<String> SAY_HI = Effectors.effector(String.class, "sayHI") + .description("says HO to an uppercased name") + .parameter(String.class, "name") + .buildAbstract(); + + /** Makes a string saying HO to the given name, in contrast to v1 and v2. */ + String sayHI(String name); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b29b95/core/src/test/dependencies/osgi/more-entities-v2-evil-twin/src/main/java/brooklyn/osgi/tests/more/MoreEntityImpl.java ---------------------------------------------------------------------- diff --git a/core/src/test/dependencies/osgi/more-entities-v2-evil-twin/src/main/java/brooklyn/osgi/tests/more/MoreEntityImpl.java b/core/src/test/dependencies/osgi/more-entities-v2-evil-twin/src/main/java/brooklyn/osgi/tests/more/MoreEntityImpl.java new file mode 100644 index 0000000..2788b8b --- /dev/null +++ b/core/src/test/dependencies/osgi/more-entities-v2-evil-twin/src/main/java/brooklyn/osgi/tests/more/MoreEntityImpl.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package brooklyn.osgi.tests.more; + +import brooklyn.entity.basic.AbstractEntity; +import brooklyn.entity.effector.EffectorBody; +import brooklyn.policy.PolicySpec; +import brooklyn.util.config.ConfigBag; + + +public class MoreEntityImpl extends AbstractEntity implements MoreEntity { + + /** Unlike v1, this declares an explicit dependency on SimplePolicy */ + @Override + public void init() { + super.init(); + getMutableEntityType().addEffector(SAY_HI, new EffectorBody<String>() { + @Override + public String call(ConfigBag parameters) { + return sayHI((String)parameters.getStringKey("name")); + } + }); + addPolicy(PolicySpec.create(brooklyn.osgi.tests.SimplePolicy.class)); + } + + /** Returns HO instead of HI (like v2 non-evil twin) or Hi (like v1) */ + public String sayHI(String name) { + return "HO "+name.toUpperCase(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b29b95/core/src/test/java/brooklyn/catalog/internal/CatalogItems.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/brooklyn/catalog/internal/CatalogItems.java b/core/src/test/java/brooklyn/catalog/internal/CatalogItems.java index dcefbcc..80b41d5 100644 --- a/core/src/test/java/brooklyn/catalog/internal/CatalogItems.java +++ b/core/src/test/java/brooklyn/catalog/internal/CatalogItems.java @@ -18,6 +18,8 @@ */ package brooklyn.catalog.internal; +import com.google.common.annotations.Beta; + import io.brooklyn.camp.spi.pdp.DeploymentPlan; /** Only for internal use / use in tests. */ @@ -41,6 +43,12 @@ public class CatalogItems { return target; } + // TODO just added this method to expose registeredTypeName for tests; but it should all go away; + // so long as tests pass no need to keep deprecation imho + @Beta + public static CatalogEntityItemDto newEntityFromJavaWithRegisteredTypeName(String registeredTypeName, String javaType) { + return set(new CatalogEntityItemDto(), registeredTypeName, javaType, registeredTypeName, null, null); + } public static CatalogEntityItemDto newEntityFromJava(String javaType, String name) { return newEntityFromJava(javaType, name, null, null); } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b29b95/core/src/test/java/brooklyn/management/osgi/OsgiTestResources.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/brooklyn/management/osgi/OsgiTestResources.java b/core/src/test/java/brooklyn/management/osgi/OsgiTestResources.java index de889be..48d01dc 100644 --- a/core/src/test/java/brooklyn/management/osgi/OsgiTestResources.java +++ b/core/src/test/java/brooklyn/management/osgi/OsgiTestResources.java @@ -28,6 +28,7 @@ package brooklyn.management.osgi; */ public class OsgiTestResources { + /** * brooklyn-osgi-test-a_0.1.0 - * defines TestA which has a "times" method and a static multiplier field; @@ -48,7 +49,10 @@ public class OsgiTestResources { * another bundle with a minimal sayHi effector, used to test versioning and dependencies * (this one has no dependencies, but see also {@value #BROOKLYN_TEST_MORE_ENTITIES_V2_PATH}) */ - public static final String BROOKLYN_TEST_MORE_ENTITIES_V1_PATH = "/brooklyn/osgi/brooklyn-test-osgi-more-entities_0.1.0.jar"; + public static final String BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FINAL_PART = "brooklyn-test-osgi-more-entities"; + public static final String BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL = + "org.apache.brooklyn.test.resources.osgi."+BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FINAL_PART; + public static final String BROOKLYN_TEST_MORE_ENTITIES_V1_PATH = "/brooklyn/osgi/" + BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FINAL_PART + "_0.1.0.jar"; public static final String BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY = "brooklyn.osgi.tests.more.MoreEntity"; /** @@ -56,6 +60,14 @@ public class OsgiTestResources { * similar to {@link #BROOKLYN_TEST_MORE_ENTITIES_V1_PATH} but saying "HI NAME" rather than "Hi NAME", * and declaring an explicit dependency on SimplePolicy from {@link #BROOKLYN_TEST_OSGI_ENTITIES_PATH} */ - public static final String BROOKLYN_TEST_MORE_ENTITIES_V2_PATH = "/brooklyn/osgi/brooklyn-test-osgi-more-entities_0.2.0.jar"; + public static final String BROOKLYN_TEST_MORE_ENTITIES_V2_PATH = "/brooklyn/osgi/" + BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FINAL_PART + "_0.2.0.jar"; + + /** + * bundle with identical metadata (same symbolic name and version -- hence being an evil twin) + * as {@link #BROOKLYN_TEST_MORE_ENTITIES_V2_PATH}, + * but slightly different behaviour -- saying "HO NAME" -- in order to make sure we can differentiate two two + * at runtime. + */ + public static final String BROOKLYN_TEST_MORE_ENTITIES_V2_EVIL_TWIN_PATH = "/brooklyn/osgi/" + BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FINAL_PART + "_evil-twin_0.2.0.jar"; } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b29b95/core/src/test/java/brooklyn/management/osgi/OsgiVersionMoreEntityTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/brooklyn/management/osgi/OsgiVersionMoreEntityTest.java b/core/src/test/java/brooklyn/management/osgi/OsgiVersionMoreEntityTest.java index c5df30d..4b653ab 100644 --- a/core/src/test/java/brooklyn/management/osgi/OsgiVersionMoreEntityTest.java +++ b/core/src/test/java/brooklyn/management/osgi/OsgiVersionMoreEntityTest.java @@ -21,6 +21,7 @@ package brooklyn.management.osgi; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; +import java.util.List; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; @@ -46,6 +47,7 @@ import brooklyn.management.internal.ManagementContextInternal; import brooklyn.policy.PolicySpec; import brooklyn.test.entity.LocalManagementContextForTests; import brooklyn.test.entity.TestApplication; +import brooklyn.util.guava.Maybe; import brooklyn.util.os.Os; import brooklyn.util.osgi.Osgis; @@ -66,6 +68,8 @@ public class OsgiVersionMoreEntityTest { public static final String BROOKLYN_TEST_MORE_ENTITIES_V1_URL = "classpath:"+BROOKLYN_TEST_MORE_ENTITIES_V1_PATH; public static final String BROOKLYN_TEST_MORE_ENTITIES_V2_PATH = OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH; public static final String BROOKLYN_TEST_MORE_ENTITIES_V2_URL = "classpath:"+BROOKLYN_TEST_MORE_ENTITIES_V2_PATH; + public static final String BROOKLYN_TEST_MORE_ENTITIES_V2_EVIL_TWIN_PATH = OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_EVIL_TWIN_PATH; + public static final String BROOKLYN_TEST_MORE_ENTITIES_V2_EVIL_TWIN_URL = "classpath:"+BROOKLYN_TEST_MORE_ENTITIES_V2_EVIL_TWIN_PATH; protected LocalManagementContext mgmt; protected TestApplication app; @@ -115,17 +119,23 @@ public class OsgiVersionMoreEntityTest { } } + protected CatalogItem<?, ?> addCatalogItemWithTypeAsName(String type, String ...libraries) { + return addCatalogItemWithNameAndType(type, type, libraries); + } @SuppressWarnings("deprecation") - protected CatalogItem<?, ?> addCatalogItem(String type, String ...libraries) { - CatalogEntityItemDto c1 = newCatalogItem(type, libraries); + protected CatalogItem<?, ?> addCatalogItemWithNameAndType(String symName, String type, String ...libraries) { + CatalogEntityItemDto c1 = newCatalogItemWithNameAndType(symName, type, libraries); mgmt.getCatalog().addItem(c1); CatalogItem<?, ?> c2 = mgmt.getCatalog().getCatalogItem(type); return c2; } - static CatalogEntityItemDto newCatalogItem(String type, String ...libraries) { - CatalogEntityItemDto c1 = CatalogItems.newEntityFromJava(type, type); - c1.setCatalogItemId(type); + static CatalogEntityItemDto newCatalogItemWithTypeAsName(String type, String ...libraries) { + return newCatalogItemWithNameAndType(type, type, libraries); + } + static CatalogEntityItemDto newCatalogItemWithNameAndType(String symName, String type, String ...libraries) { + CatalogEntityItemDto c1 = CatalogItems.newEntityFromJavaWithRegisteredTypeName(symName, type); + c1.setCatalogItemId(symName); for (String library: libraries) c1.getLibrariesDto().addBundle(library); return c1; @@ -149,6 +159,9 @@ public class OsgiVersionMoreEntityTest { public static void assertV2MethodCall(Entity me) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { Assert.assertEquals(doMethodCallBrooklyn(me), "HI BROOKLYN"); } + public static void assertV2EvilTwinMethodCall(Entity me) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { + Assert.assertEquals(doMethodCallBrooklyn(me), "HO BROOKLYN"); + } public static Object doMethodCallBrooklyn(Entity me) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { return me.getClass().getMethod("sayHI", String.class).invoke(me, "Brooklyn"); @@ -160,6 +173,9 @@ public class OsgiVersionMoreEntityTest { public static void assertV2EffectorCall(Entity me) { Assert.assertEquals(doEffectorCallBrooklyn(me), "HI BROOKLYN"); } + public static void assertV2EvilTwinEffectorCall(Entity me) { + Assert.assertEquals(doEffectorCallBrooklyn(me), "HO BROOKLYN"); + } public static String doEffectorCallBrooklyn(Entity me) { return me.invoke(Effectors.effector(String.class, "sayHI").buildAbstract(), ImmutableMap.of("name", "brooklyn")).getUnchecked(); @@ -167,7 +183,7 @@ public class OsgiVersionMoreEntityTest { @Test public void testMoreEntitiesV1() throws Exception { - CatalogItem<?, ?> c2 = addCatalogItem(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, BROOKLYN_TEST_MORE_ENTITIES_V1_URL); + CatalogItem<?, ?> c2 = addCatalogItemWithTypeAsName(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, BROOKLYN_TEST_MORE_ENTITIES_V1_URL); // test load and instantiate Entity me = addItemFromCatalog(c2); @@ -193,12 +209,12 @@ public class OsgiVersionMoreEntityTest { @Test public void testMoreEntitiesV1Policy() throws Exception { - CatalogItem<?, ?> c2 = addCatalogItem(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, BROOKLYN_TEST_MORE_ENTITIES_V1_URL); + CatalogItem<?, ?> c2 = addCatalogItemWithTypeAsName(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, BROOKLYN_TEST_MORE_ENTITIES_V1_URL); // test load and instantiate Entity me = addItemFromCatalog(c2); - CatalogItem<?, ?> cp = addCatalogItem(OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_SIMPLE_POLICY, + CatalogItem<?, ?> cp = addCatalogItemWithTypeAsName(OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_SIMPLE_POLICY, BROOKLYN_TEST_OSGI_ENTITIES_URL); me.addPolicy(getPolicySpec(cp)); @@ -213,7 +229,7 @@ public class OsgiVersionMoreEntityTest { @Test public void testMoreEntitiesV2FailsWithoutBasicTestOsgiEntitiesBundle() throws Exception { - CatalogItem<?, ?> c2 = addCatalogItem(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, + CatalogItem<?, ?> c2 = addCatalogItemWithTypeAsName(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, BROOKLYN_TEST_MORE_ENTITIES_V2_URL); // test load and instantiate @@ -231,7 +247,7 @@ public class OsgiVersionMoreEntityTest { // and has policy, with policy item catalog ID is reasonable @Test public void testMoreEntitiesV2() throws Exception { - CatalogItem<?, ?> c2 = addCatalogItem(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, + CatalogItem<?, ?> c2 = addCatalogItemWithTypeAsName(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, BROOKLYN_TEST_MORE_ENTITIES_V2_URL, BROOKLYN_TEST_OSGI_ENTITIES_URL); // test load and instantiate @@ -250,9 +266,9 @@ public class OsgiVersionMoreEntityTest { @Test public void testMoreEntitiesV1ThenV2GivesV2() throws Exception { - addCatalogItem(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, + addCatalogItemWithTypeAsName(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, BROOKLYN_TEST_MORE_ENTITIES_V1_URL); - addCatalogItem(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, + addCatalogItemWithTypeAsName(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, BROOKLYN_TEST_MORE_ENTITIES_V2_URL, BROOKLYN_TEST_OSGI_ENTITIES_URL); // test load and instantiate @@ -265,9 +281,9 @@ public class OsgiVersionMoreEntityTest { @Test public void testMoreEntitiesV2ThenV1GivesV1() throws Exception { - addCatalogItem(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, + addCatalogItemWithTypeAsName(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, BROOKLYN_TEST_MORE_ENTITIES_V2_URL, BROOKLYN_TEST_OSGI_ENTITIES_URL); - addCatalogItem(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, + addCatalogItemWithTypeAsName(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, BROOKLYN_TEST_MORE_ENTITIES_V1_URL); // test load and instantiate @@ -301,18 +317,64 @@ public class OsgiVersionMoreEntityTest { Assert.assertEquals(me.getPolicies().size(), 0, "Wrong number of policies: "+me.getPolicies()); } - // TODO test YAML for many of the above (in the camp project CAMP, using other code below) - + @Test + public void testUnfazedByMoreEntitiesV1AndV2AndV2EvilTwin() throws Exception { + addCatalogItemWithNameAndType("v1", OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, + BROOKLYN_TEST_MORE_ENTITIES_V1_URL); + addCatalogItemWithNameAndType("v2", OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, + BROOKLYN_TEST_MORE_ENTITIES_V2_URL, BROOKLYN_TEST_OSGI_ENTITIES_URL); + addCatalogItemWithNameAndType("v2-evil", OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, + BROOKLYN_TEST_MORE_ENTITIES_V2_EVIL_TWIN_URL, BROOKLYN_TEST_OSGI_ENTITIES_URL); + + // test osgi finding + + List<Bundle> bundles = Osgis.bundleFinder(mgmt.getOsgiManager().get().getFramework()) + .symbolicName(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL).findAll(); + Assert.assertEquals(bundles.size(), 3, "Wrong list of bundles: "+bundles); + + Maybe<Bundle> preferredVersion = Osgis.bundleFinder(mgmt.getOsgiManager().get().getFramework()) + .symbolicName(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL).find(); + Assert.assertTrue(preferredVersion.isPresent()); + Assert.assertEquals(preferredVersion.get().getVersion().toString(), "0.2.0"); + + Maybe<Bundle> evilVersion = Osgis.bundleFinder(mgmt.getOsgiManager().get().getFramework()). + preferringFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V2_EVIL_TWIN_URL).find(); + Assert.assertTrue(evilVersion.isPresent()); + Assert.assertEquals(evilVersion.get().getVersion().toString(), "0.2.0"); + + // test preferredUrl vs requiredUrl + + Maybe<Bundle> versionIgnoresInvalidPreferredUrl = Osgis.bundleFinder(mgmt.getOsgiManager().get().getFramework()) + .symbolicName(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL) + .version("0.1.0") + .preferringFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V2_EVIL_TWIN_URL).find(); + Assert.assertTrue(versionIgnoresInvalidPreferredUrl.isPresent()); + Assert.assertEquals(versionIgnoresInvalidPreferredUrl.get().getVersion().toString(), "0.1.0"); + + Maybe<Bundle> versionRespectsInvalidRequiredUrl = Osgis.bundleFinder(mgmt.getOsgiManager().get().getFramework()) + .symbolicName(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL) + .version("0.1.0") + .requiringFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V2_EVIL_TWIN_URL).find(); + Assert.assertFalse(versionRespectsInvalidRequiredUrl.isPresent()); + + // test entity resolution + + Entity v2 = addItemFromCatalog( mgmt.getCatalog().getCatalogItem("v2") ); + assertV2MethodCall(v2); + assertV2EffectorCall(v2); + Assert.assertEquals(v2.getPolicies().size(), 1, "Wrong number of policies: "+v2.getPolicies()); + + Entity v2_evil = addItemFromCatalog( mgmt.getCatalog().getCatalogItem("v2-evil") ); + assertV2EvilTwinMethodCall(v2_evil); + assertV2EvilTwinEffectorCall(v2_evil); + Assert.assertEquals(v2_evil.getPolicies().size(), 1, "Wrong number of policies: "+v2_evil.getPolicies()); + + Entity v1 = addItemFromCatalog( mgmt.getCatalog().getCatalogItem("v1") ); + assertV1MethodCall(v1); + assertV1EffectorCall(v1); + Assert.assertEquals(v1.getPolicies().size(), 0, "Wrong number of policies: "+v1.getPolicies()); + } + // TODO versioning (WIP until #92), install both V1 and V2 with version number, and test that both work - - // TODO other code which might be useful - but requires CAMP: -// mgmt.getCatalog().addItem(Strings.lines( -// "brooklyn.catalog:", -// " id: my-entity", -// "brooklyn.library:", -// "- url: "+BROOKLYN_TEST_MORE_ENTITIES_V1_URL, -// "services:", -// "- type: "+OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY -// )); - + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b29b95/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_evil-twin_0.2.0.jar ---------------------------------------------------------------------- diff --git a/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_evil-twin_0.2.0.jar b/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_evil-twin_0.2.0.jar new file mode 100644 index 0000000..2d52340 Binary files /dev/null and b/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_evil-twin_0.2.0.jar differ http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b29b95/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_evil-twin_0.2.0.txt ---------------------------------------------------------------------- diff --git a/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_evil-twin_0.2.0.txt b/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_evil-twin_0.2.0.txt new file mode 100644 index 0000000..9597d3b --- /dev/null +++ b/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_evil-twin_0.2.0.txt @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +The file brooklyn-test-osgi-entities.jar is for testing a deployment of +an OSGi bundle containing entities. + +The source is in core/src/test/dependencies/osgi/more-entities-v2-evil-twin http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b29b95/utils/common/src/main/java/brooklyn/util/exceptions/ReferenceWithError.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/brooklyn/util/exceptions/ReferenceWithError.java b/utils/common/src/main/java/brooklyn/util/exceptions/ReferenceWithError.java index 2ffe119..bd52b3f 100644 --- a/utils/common/src/main/java/brooklyn/util/exceptions/ReferenceWithError.java +++ b/utils/common/src/main/java/brooklyn/util/exceptions/ReferenceWithError.java @@ -93,5 +93,9 @@ public class ReferenceWithError<T> implements Supplier<T> { public boolean hasError() { return error!=null; } - + + @Override + public String toString() { + return getClass().getSimpleName()+"["+object+(error!=null?"/"+(maskError?"masking:":"throwing:")+error:"")+"]"; + } }
