From 9b337035992f77c7e156bba4d3b3ce35438ca180 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 7 Sep 2017 16:43:45 +0100 Subject: [PATCH 1/5] REST API for bundles, types, and subtypes also tweaks to other classes to make them more usable, eg VersionedName comparables and bundle removal giving an OsgiBundleInstallationResult test in BundleAndTypeAndSubtypeResourcesTest based on CatalogResourceTest, basically giving parity in terms of functionality and test coverage --- .../brooklyn/api/catalog/BrooklynCatalog.java | 1 - .../typereg/RegisteredTypeLoadingContext.java | 2 +- .../internal/BasicBrooklynCatalog.java | 106 +- .../core/mgmt/ha/OsgiArchiveInstaller.java | 18 +- .../mgmt/ha/OsgiBundleInstallationResult.java | 21 +- .../brooklyn/core/mgmt/ha/OsgiManager.java | 89 +- .../typereg/RegisteredTypePredicates.java | 54 +- .../core/typereg/RegisteredTypes.java | 43 +- .../apache/brooklyn/rest/api/BundleApi.java | 158 +++ .../apache/brooklyn/rest/api/CatalogApi.java | 3 + .../apache/brooklyn/rest/api/SubtypeApi.java | 96 ++ .../org/apache/brooklyn/rest/api/TypeApi.java | 94 ++ .../rest/domain/AdjunctConfigSummary.java | 85 ++ .../domain/BundleInstallationRestResult.java | 67 + .../brooklyn/rest/domain/BundleSummary.java | 99 ++ .../brooklyn/rest/domain/ConfigSummary.java | 17 +- .../rest/domain/EnricherConfigSummary.java | 54 +- .../rest/domain/PolicyConfigSummary.java | 53 +- .../brooklyn/rest/domain/TaskSummary.java | 9 +- .../brooklyn/rest/domain/TypeDetail.java | 86 ++ .../brooklyn/rest/domain/TypeSummary.java | 287 +++++ .../apache/brooklyn/rest/BrooklynRestApi.java | 8 +- .../rest/resources/BundleResource.java | 186 +++ .../rest/resources/CatalogResource.java | 40 +- .../rest/resources/SubtypeResource.java | 82 ++ .../brooklyn/rest/resources/TypeResource.java | 186 +++ .../rest/transform/EntityTransformer.java | 25 +- .../rest/transform/TypeTransformer.java | 194 +++ .../BundleAndTypeAndSubtypeResourcesTest.java | 1093 +++++++++++++++++ .../brooklyn/util/osgi/VersionedName.java | 24 +- 30 files changed, 3062 insertions(+), 218 deletions(-) create mode 100644 rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java create mode 100644 rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SubtypeApi.java create mode 100644 rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java create mode 100644 rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/AdjunctConfigSummary.java create mode 100644 rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleInstallationRestResult.java create mode 100644 rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleSummary.java create mode 100644 rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeDetail.java create mode 100644 rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeSummary.java create mode 100644 rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java create mode 100644 rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SubtypeResource.java create mode 100644 rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java create mode 100644 rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java create mode 100644 rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/BundleAndTypeAndSubtypeResourcesTest.java diff --git a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java index cbc5b2bf7d..9e19a81848 100644 --- a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java +++ b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java @@ -61,7 +61,6 @@ public interface BrooklynCatalog { Iterable> getCatalogItems(); /** convenience for filtering items in the catalog; see CatalogPredicates for useful filters */ -// XXX Iterable> getCatalogItems(Predicate> filter); /** persists the catalog item to the object store, if persistence is enabled */ diff --git a/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java b/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java index d37666e820..925b6b1a5d 100644 --- a/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java +++ b/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java @@ -44,7 +44,7 @@ public interface RegisteredTypeLoadingContext { * the instantiator can avoid recursive cycles */ @Nonnull public Set getAlreadyEncounteredTypes(); - /** A loader to use, supplying additional search paths */ + /** A loader to use, supplying preferred or additional bundles and search paths */ @Nullable public BrooklynClassLoadingContext getLoader(); } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index bfca5ca34f..0de825e0e4 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -1381,43 +1381,8 @@ public List> addItems(String yaml, boolean forceUpdat Maybe osgiManager = ((ManagementContextInternal)mgmt).getOsgiManager(); if (osgiManager.isPresent() && AUTO_WRAP_CATALOG_YAML_AS_BUNDLE) { // wrap in a bundle to be managed; need to get bundle and version from yaml - Map cm = BasicBrooklynCatalog.getCatalogMetadata(yaml); - VersionedName vn = BasicBrooklynCatalog.getVersionedName( cm, false ); - if (vn==null) { - // for better legacy compatibiity, if id specified at root use that - String id = (String) cm.get("id"); - if (Strings.isNonBlank(id)) { - vn = VersionedName.fromString(id); - } - vn = new VersionedName(vn!=null && Strings.isNonBlank(vn.getSymbolicName()) ? vn.getSymbolicName() : "brooklyn-catalog-bom-"+Identifiers.makeRandomId(8), - vn!=null && vn.getVersionString()!=null ? vn.getVersionString() : getFirstAs(cm, String.class, "version").or(NO_VERSION)); - } - log.debug("Wrapping supplied BOM as "+vn); - Manifest mf = new Manifest(); - mf.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, vn.getSymbolicName()); - mf.getMainAttributes().putValue(Constants.BUNDLE_VERSION, vn.getOsgiVersionString()); - mf.getMainAttributes().putValue(Constants.BUNDLE_MANIFESTVERSION, "2"); - mf.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), OSGI_MANIFEST_VERSION_VALUE); - mf.getMainAttributes().putValue(BROOKLYN_WRAPPED_BOM_BUNDLE, Boolean.TRUE.toString()); - - BundleMaker bm = new BundleMaker(mgmt); - File bf = bm.createTempBundle(vn.getSymbolicName(), mf, MutableMap.of( - new ZipEntry(CATALOG_BOM), (InputStream) new ByteArrayInputStream(yaml.getBytes())) ); - - OsgiBundleInstallationResult result = null; - try { - result = osgiManager.get().install(null, new FileInputStream(bf), true, true, forceUpdate).get(); - } catch (FileNotFoundException e) { - throw Exceptions.propagate(e); - } finally { - bf.delete(); - } - if (result.getCode().isError()) { - // rollback done by install call above - throw new IllegalStateException(result.getMessage()); - } - uninstallEmptyWrapperBundles(); - return toLegacyCatalogItems(result.getCatalogItemsInstalled()); + OsgiBundleInstallationResult result = addItemsOsgi(yaml, forceUpdate, osgiManager); + return toLegacyCatalogItems(result.getTypesInstalled()); // if all items pertaining to an older anonymous catalog.bom bundle have been overridden // we delete those later; see list of wrapper bundles kept in OsgiManager @@ -1426,10 +1391,73 @@ public List> addItems(String yaml, boolean forceUpdat return addItems(yaml, null, forceUpdate); } + /** Like {@link #addItems(String, boolean)} but returning the {@link OsgiBundleInstallationResult} for use from new environments. + * If not using OSGi the bundle/code/etc fields are null but the types will always be set. */ + @SuppressWarnings("deprecation") + public OsgiBundleInstallationResult addItemsBundleResult(String yaml, boolean forceUpdate) { + Maybe osgiManager = ((ManagementContextInternal)mgmt).getOsgiManager(); + if (osgiManager.isPresent() && AUTO_WRAP_CATALOG_YAML_AS_BUNDLE) { + // wrap in a bundle to be managed; need to get bundle and version from yaml + return addItemsOsgi(yaml, forceUpdate, osgiManager); + + // if all items pertaining to an older anonymous catalog.bom bundle have been overridden + // we delete those later; see list of wrapper bundles kept in OsgiManager + } + // fallback to non-OSGi for tests and other environments + List> items = addItems(yaml, null, forceUpdate); + OsgiBundleInstallationResult result = new OsgiBundleInstallationResult(); + for (CatalogItem ci: items) { + RegisteredType rt = mgmt.getTypeRegistry().get(ci.getId()); + result.getTypesInstalled().add(rt!=null ? rt : RegisteredTypes.of(ci)); + } + return result; + } + + protected OsgiBundleInstallationResult addItemsOsgi(String yaml, boolean forceUpdate, Maybe osgiManager) { + Map cm = BasicBrooklynCatalog.getCatalogMetadata(yaml); + VersionedName vn = BasicBrooklynCatalog.getVersionedName( cm, false ); + if (vn==null) { + // for better legacy compatibiity, if id specified at root use that + String id = (String) cm.get("id"); + if (Strings.isNonBlank(id)) { + vn = VersionedName.fromString(id); + } + vn = new VersionedName(vn!=null && Strings.isNonBlank(vn.getSymbolicName()) ? vn.getSymbolicName() : "brooklyn-catalog-bom-"+Identifiers.makeRandomId(8), + vn!=null && vn.getVersionString()!=null ? vn.getVersionString() : getFirstAs(cm, String.class, "version").or(NO_VERSION)); + } + log.debug("Wrapping supplied BOM as "+vn); + Manifest mf = new Manifest(); + mf.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, vn.getSymbolicName()); + mf.getMainAttributes().putValue(Constants.BUNDLE_VERSION, vn.getOsgiVersionString()); + mf.getMainAttributes().putValue(Constants.BUNDLE_MANIFESTVERSION, "2"); + mf.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), OSGI_MANIFEST_VERSION_VALUE); + mf.getMainAttributes().putValue(BROOKLYN_WRAPPED_BOM_BUNDLE, Boolean.TRUE.toString()); + + BundleMaker bm = new BundleMaker(mgmt); + File bf = bm.createTempBundle(vn.getSymbolicName(), mf, MutableMap.of( + new ZipEntry(CATALOG_BOM), (InputStream) new ByteArrayInputStream(yaml.getBytes())) ); + + OsgiBundleInstallationResult result = null; + try { + result = osgiManager.get().install(null, new FileInputStream(bf), true, true, forceUpdate).get(); + } catch (FileNotFoundException e) { + throw Exceptions.propagate(e); + } finally { + bf.delete(); + } + if (result.getCode().isError()) { + // rollback done by install call above + throw new IllegalStateException(result.getMessage()); + } + uninstallEmptyWrapperBundles(); + return result; + } + @SuppressWarnings("deprecation") - private List> toLegacyCatalogItems(Iterable itemIds) { + private List> toLegacyCatalogItems(Iterable list) { List> result = MutableList.of(); - for (String id: itemIds) { + for (RegisteredType t: list) { + String id = t.getId(); CatalogItem item = CatalogUtils.getCatalogItemOptionalVersion(mgmt, id); if (item==null) { // using new Type Registry (OSGi addition); diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java index 2b837ba007..15430e592d 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java @@ -60,6 +60,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; @@ -446,7 +447,7 @@ public void run() { Iterable items = mgmt().getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(result.getMetadata())); log.debug("Adding items from bundle "+result.getVersionedName()+": "+items); for (RegisteredType ci: items) { - result.catalogItemsInstalled.add(ci.getId()); + result.addType(ci); } } catch (Exception e) { // unable to install new items; rollback bundles @@ -494,15 +495,20 @@ public void run() { log.debug(result.message+" (Brooklyn load deferred)"); } else { startRunnable.run(); - if (!result.catalogItemsInstalled.isEmpty()) { + if (!result.typesInstalled.isEmpty()) { // show fewer info messages, only for 'interesting' and non-deferred installations // (rebind is deferred, as are tests, but REST is not) final int MAX_TO_LIST_EXPLICITLY = 5; - MutableList firstN = MutableList.copyOf(Iterables.limit(result.catalogItemsInstalled, MAX_TO_LIST_EXPLICITLY)); + Iterable firstN = Iterables.transform(MutableList.copyOf(Iterables.limit(result.typesInstalled, MAX_TO_LIST_EXPLICITLY)), + new Function() { + @Override public String apply(RegisteredType input) { + return input.getVersionedName().toString(); + } + }); log.info(result.message+", items: "+firstN+ - (result.catalogItemsInstalled.size() > MAX_TO_LIST_EXPLICITLY ? " (and others, "+result.catalogItemsInstalled.size()+" total)" : "") ); - if (log.isDebugEnabled() && result.catalogItemsInstalled.size()>MAX_TO_LIST_EXPLICITLY) { - log.debug(result.message+", all items: "+result.catalogItemsInstalled); + (result.typesInstalled.size() > MAX_TO_LIST_EXPLICITLY ? " (and others, "+result.typesInstalled.size()+" total)" : "") ); + if (log.isDebugEnabled() && result.typesInstalled.size()>MAX_TO_LIST_EXPLICITLY) { + log.debug(result.message+", all items: "+result.typesInstalled); } } else { log.debug(result.message+" (into Brooklyn), with no catalog items"); diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java index c611553241..d65f105145 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java @@ -21,6 +21,7 @@ import java.util.List; import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.osgi.VersionedName; import org.osgi.framework.Bundle; @@ -48,14 +49,21 @@ public enum ResultCode { /** bundle successfully installed to OSGi container but there was an error launching it, * either the OSGi bundle start, catalog items load, or (most commonly) validating the catalog items; * bundle may be installed (currently it is in most/all places, but behaviour TBC) so caller may have to uninstall it */ - ERROR_LAUNCHING_BUNDLE(true); + ERROR_LAUNCHING_BUNDLE(true), + // codes below used for deletion + BUNDLE_REMOVED(false), + ERROR_REMOVING_BUNDLE_IN_USE(true), + ERROR_REMOVING_BUNDLE_OTHER(true); final boolean isError; ResultCode(boolean isError) { this.isError = isError; } public boolean isError() { return isError; } } - final List catalogItemsInstalled = MutableList.of(); + final List typesInstalled = MutableList.of(); + /** @deprecated since 0.12.0 use {@link #typesInstalled} */ + @Deprecated + private final List catalogItemsInstalled = MutableList.of(); public String getMessage() { return message; @@ -69,6 +77,11 @@ public ManagedBundle getMetadata() { public ResultCode getCode() { return code; } + public List getTypesInstalled() { + return typesInstalled; + } + /** @deprecated since 0.12.0 use {@link #getTypesInstalled()} */ + @Deprecated public List getCatalogItemsInstalled() { return ImmutableList.copyOf(catalogItemsInstalled); } @@ -89,4 +102,8 @@ void setIgnoringAlreadyInstalled() { public String toString() { return OsgiBundleInstallationResult.class.getSimpleName()+"["+code+", "+metadata+", "+message+"]"; } + public void addType(RegisteredType ci) { + typesInstalled.add(ci); + catalogItemsInstalled.add(ci.getId()); + } } \ No newline at end of file diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java index d0c7800c66..603927a45d 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java @@ -46,6 +46,7 @@ import org.apache.brooklyn.core.BrooklynVersion; import org.apache.brooklyn.core.catalog.internal.CatalogBundleLoader; import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult.ResultCode; import org.apache.brooklyn.core.server.BrooklynServerConfig; import org.apache.brooklyn.core.server.BrooklynServerPaths; import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; @@ -373,6 +374,11 @@ public ReferenceWithError install(@Nullable Manage return installer.install(); } + /** Convenience for {@link #uninstallUploadedBundle(ManagedBundle, boolean)} without forcing, and throwing on error */ + public OsgiBundleInstallationResult uninstallUploadedBundle(ManagedBundle bundleMetadata) { + return uninstallUploadedBundle(bundleMetadata, false).get(); + } + /** * Removes this bundle from Brooklyn management, * removes all catalog items it defined, @@ -382,27 +388,78 @@ public ReferenceWithError install(@Nullable Manage * behaviour of such things is not guaranteed. They will work for many things * but attempts to load new classes may fail. *

- * Callers should typically fail if anything from this bundle is in use. + * Callers should typically fail prior to invoking if anything from this bundle is in use. + *

+ * This does not throw but returns a reference containing errors and result for caller to inspect and handle. */ - public void uninstallUploadedBundle(ManagedBundle bundleMetadata) { - uninstallCatalogItemsFromBundle( bundleMetadata.getVersionedName() ); + public ReferenceWithError uninstallUploadedBundle(ManagedBundle bundleMetadata, boolean force) { + OsgiBundleInstallationResult result = new OsgiBundleInstallationResult(); + result.metadata = bundleMetadata; + List errors = MutableList.of(); + boolean uninstalledItems = false; - if (!managedBundlesRecord.remove(bundleMetadata)) { - throw new IllegalStateException("No such bundle registered: "+bundleMetadata); + try { + try { + Iterable itemsRemoved = uninstallCatalogItemsFromBundle( bundleMetadata.getVersionedName() ); + for (RegisteredType t: itemsRemoved) result.addType(t); + uninstalledItems = true; + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + if (!force) Exceptions.propagate(e); + log.warn("Error uninstalling catalog items of "+bundleMetadata+": "+e); + errors.add(e); + } + + if (!managedBundlesRecord.remove(bundleMetadata)) { + Exception e = new IllegalStateException("No such bundle registered with Brooklyn when uninstalling: "+bundleMetadata); + if (!force) Exceptions.propagate(e); + log.warn(e.getMessage()); + errors.add(e); + } + try { + mgmt.getRebindManager().getChangeListener().onUnmanaged(bundleMetadata); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + if (!force) Exceptions.propagate(e); + log.warn("Error handling unmanagement of "+bundleMetadata+": "+e); + errors.add(e); + } + + Bundle bundle = framework.getBundleContext().getBundle(bundleMetadata.getOsgiUniqueUrl()); + result.bundle = bundle; + if (bundle==null) { + Exception e = new IllegalStateException("No such bundle installed in OSGi when uninstalling: "+bundleMetadata); + if (!force) Exceptions.propagate(e); + log.warn(e.getMessage()); + errors.add(e); + } else { + try { + bundle.stop(); + bundle.uninstall(); + } catch (BundleException e) { + Exceptions.propagateIfFatal(e); + if (!force) Exceptions.propagate(e); + log.warn("Error stopping and uninstalling "+bundleMetadata+": "+e); + errors.add(e); + } + } + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + if (!force) Exceptions.propagate(e); + log.warn("Error removing "+bundleMetadata+": "+e); + errors.add(e); } - mgmt.getRebindManager().getChangeListener().onUnmanaged(bundleMetadata); - - Bundle bundle = framework.getBundleContext().getBundle(bundleMetadata.getOsgiUniqueUrl()); - if (bundle==null) { - throw new IllegalStateException("No such bundle installed: "+bundleMetadata); - } - try { - bundle.stop(); - bundle.uninstall(); - } catch (BundleException e) { - throw Exceptions.propagate(e); + if (errors.isEmpty()) { + result.message = "Uninstalled "+bundleMetadata+" (type count "+result.typesInstalled.size()+", OSGi "+result.bundle+")"; + result.code = ResultCode.BUNDLE_REMOVED; + return ReferenceWithError.newInstanceWithoutError(result); } + + RuntimeException e = Exceptions.create("Error removing bundle "+bundleMetadata, errors); + result.message = Exceptions.collapseText(e); + result.code = uninstalledItems ? ResultCode.ERROR_REMOVING_BUNDLE_OTHER : ResultCode.ERROR_REMOVING_BUNDLE_IN_USE; + return ReferenceWithError.newInstanceThrowingError(result, e); } @Beta diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java index 0bf3490fb6..f0189e2a44 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java @@ -175,6 +175,31 @@ public boolean apply(@Nullable RegisteredType item) { } } + /** Filters for the symbolic name or alias matching the given typeName. */ + public static Predicate nameOrAlias(final String typeName) { + return nameOrAlias(Predicates.equalTo(typeName)); + } + public static Predicate nameOrAlias(final Predicate filter) { + return new NameOrAliasMatches(filter); + } + + private static class NameOrAliasMatches implements Predicate { + private final Predicate filter; + + public NameOrAliasMatches(Predicate filter) { + this.filter = filter; + } + @Override + public boolean apply(@Nullable RegisteredType item) { + if (item==null) return false; + if (filter.apply(item.getSymbolicName())) return true; + for (String alias: item.getAliases()) { + if (filter.apply(alias)) return true; + } + return false; + } + } + public static Predicate tag(final Object tag) { return tags(CollectionFunctionals.any(Predicates.equalTo(tag))); } @@ -194,27 +219,42 @@ public boolean apply(@Nullable RegisteredType item) { } } - public static Predicate anySuperType(final Predicate> filter) { + public static Predicate anySuperType(final Predicate filter) { return new AnySuperTypeMatches(filter); } - @SuppressWarnings({ "unchecked", "rawtypes" }) public static Predicate subtypeOf(final Class filter) { // the assignableFrom predicate checks if this class is assignable from the subsequent *input*. // in other words, we're checking if any input is a subtype of this class - return anySuperType((Predicate)Predicates.assignableFrom(filter)); + return anySuperType(new Predicate() { + @Override + public boolean apply(Object input) { + if (!(input instanceof Class)) return false; + return filter.isAssignableFrom((Class)input); + } + }); + } + public static Predicate subtypeOf(final String filter) { + // the assignableFrom predicate checks if this class is assignable from the subsequent *input*. + // in other words, we're checking if any input is a subtype of this class + return anySuperType(new Predicate() { + @Override + public boolean apply(Object input) { + if (input instanceof Class) input = ((Class)input).getName(); + return filter.equals(input); + } + }); } private static class AnySuperTypeMatches implements Predicate { - private final Predicate> filter; + private final Predicate filter; - @SuppressWarnings({ "rawtypes", "unchecked" }) - private AnySuperTypeMatches(Predicate filter) { + private AnySuperTypeMatches(Predicate filter) { this.filter = filter; } @Override public boolean apply(@Nullable RegisteredType item) { if (item==null) return false; - return RegisteredTypes.isAnyTypeOrSuperSatisfying(item.getSuperTypes(), filter); + return RegisteredTypes.isAnyTypeOrSuper(item.getSuperTypes(), filter); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java index 6ec5983f58..7ee137c3f9 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java @@ -65,7 +65,6 @@ import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Predicate; -import com.google.common.base.Predicates; import com.google.common.collect.ComparisonChain; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; @@ -406,12 +405,50 @@ public static boolean isSubtypeOf(RegisteredType type, Class superType) { * to see whether any inherit from the given {@link Class} */ public static boolean isAnyTypeSubtypeOf(Set candidateTypes, Class superType) { if (superType == Object.class) return true; - return isAnyTypeOrSuperSatisfying(candidateTypes, Predicates.assignableFrom(superType)); + return isAnyTypeOrSuper(candidateTypes, new Predicate() { + @Override + public boolean apply(Object input) { + return input instanceof Class && superType.isAssignableFrom( (Class)input ); + } + }); + } + + /** + * Queries recursively the given types (either {@link Class} or {@link RegisteredType}) + * to see whether any inherit from the given type either in the registry or a java class */ + public static boolean isAnyTypeSubtypeOf(Set candidateTypes, String superType) { + if (Object.class.getName().equals(superType)) return true; + return isAnyTypeOrSuper(candidateTypes, new Predicate() { + @Override + public boolean apply(Object input) { + if (input instanceof Class) input = ((Class)input).getName(); + return superType.equals(input); + } + }); + } + + /** + * Queries recursively the given types (either {@link Class} or {@link RegisteredType}) + * to see whether any superclasses satisfy the given {@link Predicate} comparing as string or class */ + public static boolean isAnyTypeOrSuper(Set candidateTypes, Predicate filter) { + for (Object st: candidateTypes) { + if (filter.apply(st)) return true; + } + for (Object st: candidateTypes) { + if (st instanceof RegisteredType) { + if (isAnyTypeOrSuper(((RegisteredType)st).getSuperTypes(), filter)) return true; + } + } + return false; } /** * Queries recursively the given types (either {@link Class} or {@link RegisteredType}) - * to see whether any java superclasses satisfy the given {@link Predicate} */ + * to see whether any java superclasses satisfy the given {@link Predicate} on the {@link Class} + * @deprecated since 0.12.0 use {@link #isAnyTypeOrSuper(Set, Predicate)} accepting any object in the predicate, + * typically allowing string equivalence although it is valid to restrict to {@link Class} comparison + * (might be stricter in some OSGi cases) */ + @Deprecated public static boolean isAnyTypeOrSuperSatisfying(Set candidateTypes, Predicate> filter) { for (Object st: candidateTypes) { if (st instanceof Class) { diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java new file mode 100644 index 0000000000..2c2eeae401 --- /dev/null +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java @@ -0,0 +1,158 @@ +/* + * 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 org.apache.brooklyn.rest.api; + +import java.util.List; + +import javax.validation.Valid; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.brooklyn.rest.domain.BundleInstallationRestResult; +import org.apache.brooklyn.rest.domain.BundleSummary; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; + +@Path("/bundles") +@Api("Bundles") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public interface BundleApi { + + @GET + @ApiOperation(value = "List bundles registered in the system including their types", + response = BundleSummary.class, + responseContainer = "List") + public List list( + @ApiParam(name = "versions", value = "Whether to list 'latest' for each symbolic-name or 'all' versions", + required = false, defaultValue = "latest") + @QueryParam("versions") + String versions); + + @Path("/{symbolicName}") + @GET + @ApiOperation(value = "Get summaries for all versions of the given bundle, with more recent ones first (preferring non-SNAPSHOTs)", + response = BundleSummary.class, + responseContainer = "List") + public List listVersions( + @ApiParam(name = "symbolicName", value = "Bundle name to query", required = true) + @PathParam("symbolicName") + String symbolicName); + + @Path("/{symbolicName}/{version}") + @GET + @ApiOperation(value = "Get detail on a specific bundle given its symbolic name and version", + response = BundleSummary.class) + public BundleSummary detail( + @ApiParam(name = "symbolicName", value = "Bundle name to query", required = true) + @PathParam("symbolicName") + String symbolicName, + @ApiParam(name = "version", value = "Version to query", required = true) + @PathParam("version") + String version); + + + @Path("/{symbolicName}/{version}") + @DELETE + @ApiOperation(value = "Removes a bundle, unregistering all the types it declares", + response = BundleInstallationRestResult.class) + public BundleInstallationRestResult remove( + @ApiParam(name = "symbolicName", value = "Bundle name to query", required = true) + @PathParam("symbolicName") + String symbolicName, + @ApiParam(name = "version", value = "Version to query", required = true) + @PathParam("version") + String version, + @ApiParam(name = "force", value = "Whether to forcibly remove it, even if in use and/or errors", required = false, defaultValue = "false") + @QueryParam("force") @DefaultValue("false") + Boolean force); + + @POST + @Consumes({MediaType.APPLICATION_JSON, "application/x-yaml", + // see http://stackoverflow.com/questions/332129/yaml-mime-type + "text/yaml", "text/x-yaml", "application/yaml"}) + @ApiOperation( + value = "Adds types to the registry from a given BOM YAML/JSON descriptor (creating a bundle with just this file in it)", + response = BundleInstallationRestResult.class + ) + @ApiResponses(value = { + @ApiResponse(code = 400, message = "Error processing the given YAML"), + @ApiResponse(code = 201, message = "Items added successfully") + }) + public Response createFromYaml( + @ApiParam(name = "yaml", value = "BOM YAML declaring the types to be installed", required = true) + @Valid String yaml, + @ApiParam(name="force", value="Force installation including replacing any different bundle of the same name and version") + @QueryParam("force") @DefaultValue("false") + Boolean forceUpdate); + + @POST + @Consumes({"application/x-zip", "application/x-jar"}) + @ApiOperation( + value = "Adds types to the registry from a given JAR or ZIP", + notes = "Accepts either an OSGi bundle JAR, or ZIP which will be turned into bundle JAR. Either format must " + + "contain a catalog.bom at the root of the archive, which must contain the bundle and version key.", + response = BundleInstallationRestResult.class) + @ApiResponses(value = { + @ApiResponse(code = 400, message = "Error processing the given archive, or the catalog.bom is invalid"), + @ApiResponse(code = 201, message = "Catalog items added successfully") + }) + public Response createFromArchive( + @ApiParam( + name = "archive", + value = "Bundle to install, in ZIP or JAR format, requiring catalog.bom containing bundle name and version", + required = true) + byte[] archive, + @ApiParam(name = "force", value = "Whether to forcibly remove it, even if in use and/or errors", required = false, defaultValue = "false") + @QueryParam("force") @DefaultValue("false") + Boolean force); + + @POST + @Consumes // anything (if doesn't match other methods with specific content types + @ApiOperation( + value = "Adds types to the registry from the given item, autodetecting type as ZIP/JAR or BOM YAML", + response = BundleInstallationRestResult.class + ) + @ApiResponses(value = { + @ApiResponse(code = 400, message = "Error processing the given archive, or the catalog.bom is invalid"), + @ApiResponse(code = 201, message = "Catalog items added successfully") + }) + public Response createAutodetecting( + @ApiParam( + name = "item", + value = "Item to install, as JAR/ZIP or Catalog YAML (autodetected)", + required = true) + byte[] item, + @ApiParam(name = "force", value = "Whether to forcibly remove it, even if in use and/or errors", required = false, defaultValue = "false") + @QueryParam("force") @DefaultValue("false") + Boolean force); +} diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java index 698f97fb56..3cbae5baa2 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java @@ -47,6 +47,9 @@ import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; +/** @deprecated since 0.12.0 use /bundle, /type, and /subtype */ +// but we will probably keep this around for a while as many places use it +@Deprecated @Path("/catalog") @Api("Catalog") @Consumes(MediaType.APPLICATION_JSON) diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SubtypeApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SubtypeApi.java new file mode 100644 index 0000000000..01890c56c7 --- /dev/null +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SubtypeApi.java @@ -0,0 +1,96 @@ +/* + * 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 org.apache.brooklyn.rest.api; + +import java.util.List; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +import org.apache.brooklyn.rest.domain.TypeSummary; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; + +@Path("/subtypes") +@Api("Subtypes") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public interface SubtypeApi { + + @Path("/{supertype}") + @GET + @ApiOperation(value = "Get all known types which declare the given argument as a supertype", + response = TypeSummary.class, responseContainer = "List") + public List list( + @ApiParam(name = "supertype", value = "Supertype to query", required = true) + @PathParam("supertype") + String supertype, + @ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") + @QueryParam("versions") + String versions, + @ApiParam(name = "regex", value = "Regular expression to search for") + @QueryParam("regex") @DefaultValue("") String regex, + @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") + @QueryParam("fragment") @DefaultValue("") String fragment); + + // conveniences for common items where internally it uses java class name + // caller can of course use /subtypes/org.apache.brooklyn.api.Entity + + @GET @Path("/application") + @ApiOperation(value = "Get all applications", response = TypeSummary.class, responseContainer = "List") + public List listApplications(@ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") @QueryParam("versions") String versions, + @ApiParam(name = "regex", value = "Regular expression to search for") @QueryParam("regex") @DefaultValue("") String regex, + @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") @QueryParam("fragment") @DefaultValue("") String fragment); + + @GET @Path("/entity") + @ApiOperation(value = "Get all entities", response = TypeSummary.class, responseContainer = "List") + public List listEntities(@ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") @QueryParam("versions") String versions, + @ApiParam(name = "regex", value = "Regular expression to search for") @QueryParam("regex") @DefaultValue("") String regex, + @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") @QueryParam("fragment") @DefaultValue("") String fragment); + + @GET @Path("/policy") + @ApiOperation(value = "Get all policies", response = TypeSummary.class, responseContainer = "List") + public List listPolicies(@ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") @QueryParam("versions") String versions, + @ApiParam(name = "regex", value = "Regular expression to search for") @QueryParam("regex") @DefaultValue("") String regex, + @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") @QueryParam("fragment") @DefaultValue("") String fragment); + + @GET @Path("/enricher") + @ApiOperation(value = "Get all enrichers", response = TypeSummary.class, responseContainer = "List") + public List listEnrichers(@ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") @QueryParam("versions") String versions, + @ApiParam(name = "regex", value = "Regular expression to search for") @QueryParam("regex") @DefaultValue("") String regex, + @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") @QueryParam("fragment") @DefaultValue("") String fragment); + + @GET @Path("/location") + // note some (deprecated) locations are only available on location manager + @ApiOperation(value = "Get all locations stored in the registry", response = TypeSummary.class, responseContainer = "List") + public List listLocations(@ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") @QueryParam("versions") String versions, + @ApiParam(name = "regex", value = "Regular expression to search for") @QueryParam("regex") @DefaultValue("") String regex, + @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") @QueryParam("fragment") @DefaultValue("") String fragment); + + // in future could have others, eg: tasks, feeds, etc + +} diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java new file mode 100644 index 0000000000..c39d5eff81 --- /dev/null +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java @@ -0,0 +1,94 @@ +/* + * 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 org.apache.brooklyn.rest.api; + +import java.util.List; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.brooklyn.rest.domain.TypeDetail; +import org.apache.brooklyn.rest.domain.TypeSummary; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; + +@Path("/types") +@Api("Types") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public interface TypeApi { + + @GET + @ApiOperation(value = "List types registered in the system", + response = TypeSummary.class, + responseContainer = "List") + public List list( + @ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", + required = false, defaultValue = "latest") + @QueryParam("versions") + String versions, + @ApiParam(name = "regex", value = "Regular expression to search for") + @QueryParam("regex") @DefaultValue("") String regex, + @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") + @QueryParam("fragment") @DefaultValue("") String fragment); + + @Path("/{nameOrAlias}") + @GET + @ApiOperation(value = "Get summaries for all versions and instances of a given type or alias, with best match first", + response = TypeSummary.class, + responseContainer = "List") + public List listVersions( + @ApiParam(name = "nameOrAlias", value = "Type name to query", required = true) + @PathParam("nameOrAlias") + String nameOrAlias); + + @Path("/{symbolicName}/{version}") + @GET + @ApiOperation(value = "Get detail on a given type and version, allowing 'latest' to match the most recent version (preferring non-SNAPSHOTs)", + response = TypeDetail.class) + public TypeDetail detail( + @ApiParam(name = "symbolicName", value = "Type name to query", required = true) + @PathParam("symbolicName") + String symbolicName, + @ApiParam(name = "version", value = "Version to query", required = true) + @PathParam("version") + String version); + + @Path("/{symbolicName}/{version}/icon") + @GET + @ApiOperation(value = "Returns the icon image registered for this item") + @Produces("application/image") + public Response icon( + @ApiParam(name = "symbolicName", value = "Type name to query", required = true) + @PathParam("symbolicName") + String symbolicName, + @ApiParam(name = "version", value = "Version to query", required = true) + @PathParam("version") + String version); + +} diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/AdjunctConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/AdjunctConfigSummary.java new file mode 100644 index 0000000000..4bba4b20fa --- /dev/null +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/AdjunctConfigSummary.java @@ -0,0 +1,85 @@ +/* + * 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 org.apache.brooklyn.rest.domain; + +import java.net.URI; +import java.util.Map; +import java.util.Objects; + +import org.apache.brooklyn.config.ConfigKey; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.collect.ImmutableMap; + +public class AdjunctConfigSummary extends ConfigSummary { + + private static final long serialVersionUID = 4339330833863794513L; + + @JsonInclude(Include.NON_NULL) + private final Map links; + + // json deserialization + AdjunctConfigSummary() { + links = null; + } + + public AdjunctConfigSummary( + @JsonProperty("name") String name, + @JsonProperty("type") String type, + @JsonProperty("description") String description, + @JsonProperty("defaultValue") Object defaultValue, + @JsonProperty("reconfigurable") boolean reconfigurable, + @JsonProperty("links") Map links) { + super(name, type, description, defaultValue, reconfigurable, null, null, null); + this.links = (links == null) ? ImmutableMap.of() : ImmutableMap.copyOf(links); + } + + public AdjunctConfigSummary(ConfigKey config, String label, Double priority, Map links) { + super(config, label, priority); + this.links = links != null ? ImmutableMap.copyOf(links) : null; + } + + @Override + public Map getLinks() { + return links; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AdjunctConfigSummary)) return false; + if (!super.equals(o)) return false; + AdjunctConfigSummary that = (AdjunctConfigSummary) o; + return Objects.equals(links, that.links); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), links); + } + + @Override + public String toString() { + return "EnricherConfigSummary{" + + "links=" + links + + '}'; + } +} diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleInstallationRestResult.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleInstallationRestResult.java new file mode 100644 index 0000000000..48f15ee782 --- /dev/null +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleInstallationRestResult.java @@ -0,0 +1,67 @@ +/* + * 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 org.apache.brooklyn.rest.domain; + +import java.util.Map; + +import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; +import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult.ResultCode; +import org.apache.brooklyn.util.collections.MutableMap; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +public class BundleInstallationRestResult { + // as Osgi result, but without bundle, and with maps of catalog items installed + + private final String message; + private final String bundle; + private final OsgiBundleInstallationResult.ResultCode code; + @JsonInclude(Include.NON_EMPTY) + private Map types = MutableMap.of(); + + /** json internal only */ + @SuppressWarnings("unused") + private BundleInstallationRestResult() { + this.message = null; + this.bundle = null; + this.code = null; + } + + public BundleInstallationRestResult(String message, String bundle, ResultCode code) { + this.message = message; + this.bundle = bundle; + this.code = code; + } + + public String getMessage() { + return message; + } + public String getBundle() { + return bundle; + } + public OsgiBundleInstallationResult.ResultCode getCode() { + return code; + } + + public Map getTypes() { + return types; + } + +} \ No newline at end of file diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleSummary.java new file mode 100644 index 0000000000..f14ee0721d --- /dev/null +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleSummary.java @@ -0,0 +1,99 @@ +/* + * 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 org.apache.brooklyn.rest.domain; + +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.javalang.JavaClassNames; +import org.apache.brooklyn.util.text.NaturalOrderComparator; +import org.apache.brooklyn.util.text.VersionComparator; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.google.common.collect.ComparisonChain; + +public class BundleSummary implements Comparable { + + private final String symbolicName; + private final String version; + + @JsonInclude(value=Include.ALWAYS) + private final List types = MutableList.of(); + + // not exported directly, but used to provide other top-level json fields + // for specific types + @JsonIgnore + private final Map others = MutableMap.of(); + + /** for json deserialization */ + BundleSummary() { + symbolicName = null; + version = null; + } + + public BundleSummary(OsgiBundleWithUrl bundle) { + symbolicName = bundle.getSymbolicName(); + version = bundle.getSuppliedVersionString(); + } + + /** Mutable map of other top-level metadata included on this DTO (eg listing config keys or effectors) */ + @JsonAnyGetter + public Map getExtraFields() { + return others; + } + @JsonAnySetter + public void setExtraField(String name, Object value) { + others.put(name, value); + } + + public void addType(TypeSummary type) { types.add(type); } + + @Override + public int compareTo(BundleSummary o2) { + BundleSummary o1 = this; + return ComparisonChain.start() + .compare(o1.symbolicName, o2.symbolicName, NaturalOrderComparator.INSTANCE) + .compare(o2.version, o1.version, VersionComparator.INSTANCE) + .result(); + } + + public String getSymbolicName() { + return symbolicName; + } + + public String getVersion() { + return version; + } + + public List getTypes() { + return types; + } + + @Override + public String toString() { + return JavaClassNames.cleanSimpleClassName(this)+"["+symbolicName+":"+version+"]"; + } +} diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java index 22e88d6d36..6d9ceb33d9 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java @@ -30,6 +30,8 @@ import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.util.collections.Jsonya; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.base.Function; @@ -42,19 +44,24 @@ public abstract class ConfigSummary implements HasName, Serializable { private final String name; private final String type; - @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) + @JsonInclude(Include.NON_NULL) private final Object defaultValue; - @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) + @JsonInclude(Include.NON_NULL) private final String description; @JsonSerialize private final boolean reconfigurable; - @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) + @JsonInclude(Include.NON_NULL) private final String label; - @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) + @JsonInclude(Include.NON_NULL) private final Double priority; - @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) + @JsonInclude(Include.NON_NULL) private final List> possibleValues; + // json deserialization + ConfigSummary() { + this(null, null, null, null, false, null, null, null); + } + protected ConfigSummary( @JsonProperty("name") String name, @JsonProperty("type") String type, diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EnricherConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EnricherConfigSummary.java index 276cd6b354..c868fb82c0 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EnricherConfigSummary.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EnricherConfigSummary.java @@ -20,60 +20,24 @@ import java.net.URI; import java.util.Map; -import java.util.Objects; import org.apache.brooklyn.config.ConfigKey; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.google.common.collect.ImmutableMap; - -public class EnricherConfigSummary extends ConfigSummary { +// TODO remove? this class has no value over its super +public class EnricherConfigSummary extends AdjunctConfigSummary { private static final long serialVersionUID = 4339330833863794513L; - @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) - private final Map links; - - public EnricherConfigSummary( - @JsonProperty("name") String name, - @JsonProperty("type") String type, - @JsonProperty("description") String description, - @JsonProperty("defaultValue") Object defaultValue, - @JsonProperty("reconfigurable") boolean reconfigurable, - @JsonProperty("links") Map links) { - super(name, type, description, defaultValue, reconfigurable, null, null, null); - this.links = (links == null) ? ImmutableMap.of() : ImmutableMap.copyOf(links); - } - + @SuppressWarnings("unused") // json deserialization + private EnricherConfigSummary() {} + public EnricherConfigSummary(ConfigKey config, String label, Double priority, Map links) { - super(config, label, priority); - this.links = links != null ? ImmutableMap.copyOf(links) : null; + super(config, label, priority, links); } - @Override - public Map getLinks() { - return links; + public EnricherConfigSummary(String name, String type, String description, Object defaultValue, boolean reconfigurable, + Map links) { + super(name, type, description, defaultValue, reconfigurable, links); } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof EnricherConfigSummary)) return false; - if (!super.equals(o)) return false; - EnricherConfigSummary that = (EnricherConfigSummary) o; - return Objects.equals(links, that.links); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), links); - } - - @Override - public String toString() { - return "EnricherConfigSummary{" + - "links=" + links + - '}'; - } } diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java index 8115ab97a5..5dfb898bc4 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java @@ -20,59 +20,24 @@ import java.net.URI; import java.util.Map; -import java.util.Objects; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.google.common.collect.ImmutableMap; import org.apache.brooklyn.config.ConfigKey; -public class PolicyConfigSummary extends ConfigSummary { +//TODO remove? this class has no value over its super +public class PolicyConfigSummary extends AdjunctConfigSummary { private static final long serialVersionUID = 4339330833863794513L; - @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) - private final Map links; - - public PolicyConfigSummary( - @JsonProperty("name") String name, - @JsonProperty("type") String type, - @JsonProperty("description") String description, - @JsonProperty("defaultValue") Object defaultValue, - @JsonProperty("reconfigurable") boolean reconfigurable, - @JsonProperty("links") Map links) { - super(name, type, description, defaultValue, reconfigurable, null, null, null); - this.links = (links == null) ? ImmutableMap.of() : ImmutableMap.copyOf(links); - } - + @SuppressWarnings("unused") // json deserialization + private PolicyConfigSummary() {} + public PolicyConfigSummary(ConfigKey config, String label, Double priority, Map links) { - super(config, label, priority); - this.links = links != null ? ImmutableMap.copyOf(links) : null; - } - - @Override - public Map getLinks() { - return links; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof PolicyConfigSummary)) return false; - if (!super.equals(o)) return false; - PolicyConfigSummary that = (PolicyConfigSummary) o; - return Objects.equals(links, that.links); + super(config, label, priority, links); } - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), links); + public PolicyConfigSummary(String name, String type, String description, Object defaultValue, boolean reconfigurable, + Map links) { + super(name, type, description, defaultValue, reconfigurable, links); } - @Override - public String toString() { - return "PolicyConfigSummary{" + - "links=" + links + - '}'; - } } diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java index eef57e6516..e3b0e9509a 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java @@ -30,8 +30,9 @@ import org.apache.brooklyn.util.collections.Jsonya; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -58,14 +59,14 @@ public class TaskSummary implements HasId, Serializable { private final List children; private final LinkWithMetadata submittedByTask; - @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) + @JsonInclude(Include.NON_NULL) private final LinkWithMetadata blockingTask; - @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) + @JsonInclude(Include.NON_NULL) private final String blockingDetails; private final String detailedStatus; - @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) + @JsonInclude(Include.NON_NULL) private final Map streams; private final Map links; diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeDetail.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeDetail.java new file mode 100644 index 0000000000..2f9e32fe91 --- /dev/null +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeDetail.java @@ -0,0 +1,86 @@ +/* + * 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 org.apache.brooklyn.rest.domain; + +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.api.typereg.RegisteredType.TypeImplementationPlan; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +public class TypeDetail extends TypeSummary { + + public static class TypeImplementationPlanSummary { + @JsonInclude(value=Include.NON_EMPTY) + private String format; + private Object data; + private TypeImplementationPlanSummary() {} + private TypeImplementationPlanSummary(TypeImplementationPlan p) { + format = p.getPlanFormat(); + data = p.getPlanData(); + } + public String getFormat() { + return format; + } + public Object getData() { + return data; + } + } + private final TypeImplementationPlanSummary plan; + + /** Constructor for JSON deserialization use only. */ + TypeDetail() { + plan = null; + } + + public TypeDetail(RegisteredType t) { + super(t); + plan = new TypeImplementationPlanSummary(t.getPlan()); + } + + public TypeImplementationPlanSummary getPlan() { + return plan; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((plan == null) ? 0 : plan.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + TypeDetail other = (TypeDetail) obj; + if (plan == null) { + if (other.plan != null) + return false; + } else if (!plan.equals(other.plan)) + return false; + return true; + } + +} diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeSummary.java new file mode 100644 index 0000000000..0a045d4df0 --- /dev/null +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeSummary.java @@ -0,0 +1,287 @@ +/* + * 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 org.apache.brooklyn.rest.domain; + +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.javalang.JavaClassNames; +import org.apache.brooklyn.util.text.NaturalOrderComparator; +import org.apache.brooklyn.util.text.VersionComparator; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.google.common.collect.ComparisonChain; + +public class TypeSummary implements Comparable { + + private final String symbolicName; + private final String version; + private final String containingBundle; + private final RegisteredTypeKind kind; + + @JsonInclude(value=Include.NON_EMPTY) + private final String displayName; + @JsonInclude(value=Include.NON_EMPTY) + private final String description; + @JsonInclude(value=Include.NON_EMPTY) + private String iconUrl; + + @JsonInclude(value=Include.NON_EMPTY) + private Set aliases; + @JsonInclude(value=Include.NON_EMPTY) + private Set supertypes; + @JsonInclude(value=Include.NON_EMPTY) + private Set tags; + + @JsonInclude(value=Include.NON_DEFAULT) + private boolean disabled = false; + @JsonInclude(value=Include.NON_DEFAULT) + private boolean deprecated = false; + + // not exported directly, but used to provide other top-level json fields + // for specific types + @JsonIgnore + private final Map others = MutableMap.of(); + + /** Constructor for JSON deserialization use only. */ + TypeSummary() { + symbolicName = null; + version = null; + containingBundle = null; + kind = null; + + displayName = null; + description = null; + } + + public TypeSummary(RegisteredType t) { + symbolicName = t.getSymbolicName(); + version = t.getVersion(); + containingBundle = t.getContainingBundle(); + kind = t.getKind(); + + displayName = t.getDisplayName(); + description = t.getDescription(); + iconUrl = t.getIconUrl(); + + aliases = t.getAliases(); + supertypes = t.getSuperTypes(); + tags = t.getTags(); + + deprecated = t.isDeprecated(); + disabled = t.isDisabled(); + } + + public TypeSummary(TypeSummary t) { + symbolicName = t.getSymbolicName(); + version = t.getVersion(); + containingBundle = t.getContainingBundle(); + kind = t.getKind(); + + displayName = t.getDisplayName(); + description = t.getDescription(); + iconUrl = t.getIconUrl(); + + aliases = t.getAliases(); + supertypes = t.getSupertypes(); + tags = t.getTags(); + + deprecated = t.isDeprecated(); + disabled = t.isDisabled(); + + others.putAll(t.getExtraFields()); + } + + /** Mutable map of other top-level metadata included on this DTO (eg listing config keys or effectors) */ + @JsonAnyGetter + public Map getExtraFields() { + return others; + } + @JsonAnySetter + public void setExtraField(String name, Object value) { + others.put(name, value); + } + + public void setIconUrl(String iconUrl) { + this.iconUrl = iconUrl; + } + + @Override + public int compareTo(TypeSummary o2) { + TypeSummary o1 = this; + return ComparisonChain.start() + .compare(o1.symbolicName, o2.symbolicName, NaturalOrderComparator.INSTANCE) + .compareFalseFirst(o1.disabled, o2.disabled) + .compareFalseFirst(o1.deprecated, o2.deprecated) + .compare(o2.version, o1.version, VersionComparator.INSTANCE) + .result(); + } + + public String getSymbolicName() { + return symbolicName; + } + + public String getVersion() { + return version; + } + + public String getContainingBundle() { + return containingBundle; + } + + public RegisteredTypeKind getKind() { + return kind; + } + + public String getDisplayName() { + return displayName; + } + + public String getDescription() { + return description; + } + + public String getIconUrl() { + return iconUrl; + } + + public Set getAliases() { + return aliases; + } + + public Set getSupertypes() { + return supertypes; + } + + public Set getTags() { + return tags; + } + + public boolean isDisabled() { + return disabled; + } + + public boolean isDeprecated() { + return deprecated; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((aliases == null) ? 0 : aliases.hashCode()); + result = prime * result + ((containingBundle == null) ? 0 : containingBundle.hashCode()); + result = prime * result + (deprecated ? 1231 : 1237); + result = prime * result + ((description == null) ? 0 : description.hashCode()); + result = prime * result + (disabled ? 1231 : 1237); + result = prime * result + ((displayName == null) ? 0 : displayName.hashCode()); + result = prime * result + ((iconUrl == null) ? 0 : iconUrl.hashCode()); + result = prime * result + ((kind == null) ? 0 : kind.hashCode()); + // don't use 'others' - see equals comment + // result = prime * result + ((others == null) ? 0 : others.hashCode()); + result = prime * result + ((supertypes == null) ? 0 : supertypes.hashCode()); + result = prime * result + ((symbolicName == null) ? 0 : symbolicName.hashCode()); + result = prime * result + ((tags == null) ? 0 : tags.hashCode()); + result = prime * result + ((version == null) ? 0 : version.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TypeSummary other = (TypeSummary) obj; + if (aliases == null) { + if (other.aliases != null) + return false; + } else if (!aliases.equals(other.aliases)) + return false; + if (containingBundle == null) { + if (other.containingBundle != null) + return false; + } else if (!containingBundle.equals(other.containingBundle)) + return false; + if (deprecated != other.deprecated) + return false; + if (description == null) { + if (other.description != null) + return false; + } else if (!description.equals(other.description)) + return false; + if (disabled != other.disabled) + return false; + if (displayName == null) { + if (other.displayName != null) + return false; + } else if (!displayName.equals(other.displayName)) + return false; + if (iconUrl == null) { + if (other.iconUrl != null) + return false; + } else if (!iconUrl.equals(other.iconUrl)) + return false; + if (kind != other.kind) + return false; + // don't compare "others" -- eg if "links" are set, we don't care +// if (others == null) { +// if (other.others != null) +// return false; +// } else if (!others.equals(other.others)) +// return false; + if (supertypes == null) { + if (other.supertypes != null) + return false; + } else if (!supertypes.equals(other.supertypes)) + return false; + if (symbolicName == null) { + if (other.symbolicName != null) + return false; + } else if (!symbolicName.equals(other.symbolicName)) + return false; + if (tags == null) { + if (other.tags != null) + return false; + } else if (!tags.equals(other.tags)) + return false; + if (version == null) { + if (other.version != null) + return false; + } else if (!version.equals(other.version)) + return false; + return true; + } + + @Override + public String toString() { + return JavaClassNames.cleanSimpleClassName(this)+"["+symbolicName+":"+version+ + ", containingBundle=" + containingBundle + ", kind=" + kind + ", displayName=" + displayName + "]"; + } + +} diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java index 01e8ad5d4d..b3eb4e48cd 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java @@ -26,6 +26,7 @@ import org.apache.brooklyn.rest.resources.ActivityResource; import org.apache.brooklyn.rest.resources.ApidocResource; import org.apache.brooklyn.rest.resources.ApplicationResource; +import org.apache.brooklyn.rest.resources.BundleResource; import org.apache.brooklyn.rest.resources.CatalogResource; import org.apache.brooklyn.rest.resources.EffectorResource; import org.apache.brooklyn.rest.resources.EntityConfigResource; @@ -37,6 +38,8 @@ import org.apache.brooklyn.rest.resources.ScriptResource; import org.apache.brooklyn.rest.resources.SensorResource; import org.apache.brooklyn.rest.resources.ServerResource; +import org.apache.brooklyn.rest.resources.SubtypeResource; +import org.apache.brooklyn.rest.resources.TypeResource; import org.apache.brooklyn.rest.resources.UsageResource; import org.apache.brooklyn.rest.util.DefaultExceptionMapper; import org.apache.brooklyn.rest.util.FormMapProvider; @@ -46,14 +49,15 @@ import io.swagger.jaxrs.listing.SwaggerSerializers; - -@SuppressWarnings("deprecation") public class BrooklynRestApi { public static Iterable getBrooklynRestResources() { List resources = new ArrayList<>(); resources.add(new LocationResource()); resources.add(new CatalogResource()); + resources.add(new TypeResource()); + resources.add(new SubtypeResource()); + resources.add(new BundleResource()); resources.add(new ApplicationResource()); resources.add(new EntityResource()); resources.add(new EntityConfigResource()); diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java new file mode 100644 index 0000000000..a2a6532fdf --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java @@ -0,0 +1,186 @@ +/* + * 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 org.apache.brooklyn.rest.resources; + +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; +import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.rest.api.BundleApi; +import org.apache.brooklyn.rest.domain.ApiError; +import org.apache.brooklyn.rest.domain.BundleInstallationRestResult; +import org.apache.brooklyn.rest.domain.BundleSummary; +import org.apache.brooklyn.rest.filter.HaHotStateRequired; +import org.apache.brooklyn.rest.transform.TypeTransformer; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.exceptions.ReferenceWithError; +import org.apache.brooklyn.util.osgi.VersionedName; +import org.apache.brooklyn.util.osgi.VersionedName.VersionedNameComparator; +import org.apache.brooklyn.util.yaml.Yamls; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; + +@HaHotStateRequired +public class BundleResource extends AbstractBrooklynRestResource implements BundleApi { + + private static final Logger log = LoggerFactory.getLogger(BundleResource.class); + private static final String LATEST = "latest"; + + @Override + public List list(String versions) { + return list(TypeResource.isLatestOnly(versions, true), Predicates.alwaysTrue()); + } + + private List list(boolean onlyLatest, Predicate symbolicNameFilter) { + + Map bundles = new TreeMap<>(VersionedNameComparator.INSTANCE); + for (ManagedBundle b: ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles().values()) { + if (symbolicNameFilter.apply(b.getSymbolicName())) { + // TODO entitlements for bundles + VersionedName key = onlyLatest ? new VersionedName(b.getSymbolicName(), LATEST) : b.getVersionedName(); + ManagedBundle oldBundle = bundles.get(key); + if (oldBundle==null || oldBundle.getVersionedName().compareTo(b.getVersionedName()) > 0) { + bundles.put(key, b); + } + } + } + return toBundleSummary(bundles.values()); + } + + private List toBundleSummary(Iterable sortedItems) { + List result = MutableList.of(); + for (ManagedBundle t: sortedItems) { + result.add(TypeTransformer.bundleSummary(brooklyn(), t, ui.getBaseUriBuilder(), mgmt())); + } + return result; + } + + @Override + public List listVersions(String symbolicName) { + return list(false, Predicates.equalTo(symbolicName)); + } + + @Override + public BundleSummary detail(String symbolicName, String version) { + ManagedBundle b = lookup(symbolicName, version); + return TypeTransformer.bundleDetails(brooklyn(), b, ui.getBaseUriBuilder(), mgmt()); + } + + protected ManagedBundle lookup(String symbolicName, String version) { + // TODO entitlements for bundles + + ManagedBundle b = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundle(new VersionedName(symbolicName, version)); + if (b==null) { + throw WebResourceUtils.notFound("Bundle with id '%s:%s' not found", symbolicName, version); + } + return b; + } + + @Override + public BundleInstallationRestResult remove(String symbolicName, String version, Boolean force) { + ManagedBundle b = lookup(symbolicName, version); + log.info("REST removing "+symbolicName+":"+version); + if (force==null) force = false; + ReferenceWithError r = ((ManagementContextInternal)mgmt()).getOsgiManager().get().uninstallUploadedBundle(b, force); + return TypeTransformer.bundleInstallationResult(r.getWithoutError(), mgmt(), brooklyn(), ui); + } + + + @Override + public Response createFromYaml(String yaml, Boolean force) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ADD_CATALOG_ITEM, yaml)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to add catalog items", + Entitlements.getEntitlementContext().user()); + } + if (force==null) force = false; + + try { + return Response.status(Status.CREATED).entity( + TypeTransformer.bundleInstallationResult( + ((BasicBrooklynCatalog)brooklyn().getCatalog()).addItemsBundleResult(yaml, force), mgmt(), brooklyn(), ui)).build(); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + return badRequest(e); + } + } + + @Override + public Response createFromArchive(byte[] zipInput, Boolean force) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ROOT, null)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to add catalog items", + Entitlements.getEntitlementContext().user()); + } + if (force==null) force = false; + + ReferenceWithError result = ((ManagementContextInternal)mgmt()).getOsgiManager().get() + .install(null, new ByteArrayInputStream(zipInput), true, true, force); + + if (OsgiBundleInstallationResult.ResultCode.IGNORING_BUNDLE_AREADY_INSTALLED.equals(result.getWithoutError().getCode())) { + result = ReferenceWithError.newInstanceThrowingError(result.getWithoutError(), new IllegalStateException( + "Cannot add bundle" + result.getWithoutError().getMetadata().getVersionedName() + + "; different bundle with same name already installed")); + } + + if (result.hasError()) { + // (rollback already done as part of install, if necessary) + if (log.isTraceEnabled()) { + log.trace("Unable to create from archive, returning 400: "+result.getError().getMessage(), result.getError()); + } + return ApiError.builder().errorCode(Status.BAD_REQUEST).message(result.getWithoutError().getMessage()) + .data(TypeTransformer.bundleInstallationResult(result.getWithoutError(), mgmt(), brooklyn(), ui)).build().asJsonResponse(); + } + + BundleInstallationRestResult resultR = TypeTransformer.bundleInstallationResult(result.get(), mgmt(), brooklyn(), ui); + return Response.status(Status.CREATED).entity( resultR ).build(); + } + + @Override + public Response createAutodetecting(byte[] item, Boolean force) { + Throwable yamlException = null; + try { + MutableList.copyOf( Yamls.parseAll(new InputStreamReader(new ByteArrayInputStream(item))) ); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + yamlException = e; + } + + if (yamlException==null) { + // treat as yaml if it parsed + return createFromYaml(new String(item), force); + } + + return createFromArchive(item, force); + } +} + diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java index d327e4046b..65e695675c 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java @@ -35,7 +35,6 @@ import javax.ws.rs.core.UriInfo; import org.apache.brooklyn.api.catalog.CatalogItem; -import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; @@ -46,6 +45,7 @@ import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.rest.api.CatalogApi; import org.apache.brooklyn.rest.domain.ApiError; +import org.apache.brooklyn.rest.domain.BundleInstallationRestResult; import org.apache.brooklyn.rest.domain.CatalogEnricherSummary; import org.apache.brooklyn.rest.domain.CatalogEntitySummary; import org.apache.brooklyn.rest.domain.CatalogItemSummary; @@ -53,7 +53,7 @@ import org.apache.brooklyn.rest.domain.CatalogPolicySummary; import org.apache.brooklyn.rest.filter.HaHotStateRequired; import org.apache.brooklyn.rest.transform.CatalogTransformer; -import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils; +import org.apache.brooklyn.rest.transform.TypeTransformer; import org.apache.brooklyn.rest.util.WebResourceUtils; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; @@ -149,36 +149,6 @@ public Response createFromYaml(String yaml, boolean forceUpdate) { } } - public static class BundleInstallationRestResult { - // as Osgi result, but without bundle, and with maps of catalog items installed - - String message; - String bundle; - OsgiBundleInstallationResult.ResultCode code; - - Map types; - - public String getMessage() { - return message; - } - - public static BundleInstallationRestResult of(OsgiBundleInstallationResult in, ManagementContext mgmt, BrooklynRestResourceUtils brooklynU, UriInfo ui) { - BundleInstallationRestResult result = new BundleInstallationRestResult(); - result.message = in.getMessage(); - result.bundle = in.getVersionedName() != null ? in.getVersionedName().toString() : ""; - result.code = in.getCode(); - if (in.getCatalogItemsInstalled()!=null) { - result.types = MutableMap.of(); - for (String id: in.getCatalogItemsInstalled()) { - RegisteredType ci = mgmt.getTypeRegistry().get(id); - CatalogItemSummary summary = CatalogTransformer.catalogItemSummary(brooklynU, ci, ui.getBaseUriBuilder()); - result.types.put(id, summary); - } - } - return result; - } - } - @Override @Beta public Response createFromArchive(byte[] zipInput, boolean detail, boolean forceUpdate) { @@ -202,11 +172,11 @@ public Response createFromArchive(byte[] zipInput, boolean detail, boolean force log.trace("Unable to create from archive, returning 400: "+result.getError().getMessage(), result.getError()); } return ApiError.builder().errorCode(Status.BAD_REQUEST).message(result.getWithoutError().getMessage()) - .data(BundleInstallationRestResult.of(result.getWithoutError(), mgmt(), brooklyn(), ui)).build().asJsonResponse(); + .data(TypeTransformer.bundleInstallationResult(result.getWithoutError(), mgmt(), brooklyn(), ui)).build().asJsonResponse(); } - BundleInstallationRestResult resultR = BundleInstallationRestResult.of(result.get(), mgmt(), brooklyn(), ui); - return Response.status(Status.CREATED).entity( detail ? resultR : resultR.types ).build(); + BundleInstallationRestResult resultR = TypeTransformer.bundleInstallationResult(result.get(), mgmt(), brooklyn(), ui); + return Response.status(Status.CREATED).entity( detail ? resultR : resultR.getTypes() ).build(); } private Response buildCreateResponse(Iterable catalogItems) { diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SubtypeResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SubtypeResource.java new file mode 100644 index 0000000000..12716b36ee --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SubtypeResource.java @@ -0,0 +1,82 @@ +/* + * 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 org.apache.brooklyn.rest.resources; + +import java.util.List; + +import org.apache.brooklyn.api.entity.Application; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.policy.Policy; +import org.apache.brooklyn.api.sensor.Enricher; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; +import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.rest.api.SubtypeApi; +import org.apache.brooklyn.rest.domain.TypeSummary; +import org.apache.brooklyn.rest.filter.HaHotStateRequired; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.text.StringPredicates; +import org.apache.brooklyn.util.text.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; + +@HaHotStateRequired +public class SubtypeResource extends AbstractBrooklynRestResource implements SubtypeApi { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(SubtypeResource.class); + + @Override + public List list(String supertype, String versions, String regex, String fragment) { + List> filters = MutableList.>of() + .append(RegisteredTypePredicates.entitledToSee(mgmt())) + .append(RegisteredTypePredicates.subtypeOf(supertype)); + if (TypeResource.isLatestOnly(versions, true)) { + // TODO inefficient - does n^2 comparisons where n is sufficient + // create RegisteredTypes.filterBestVersions to do a list after the initial parse + // (and javadoc in predicate method below) + filters.add(RegisteredTypePredicates.isBestVersion(mgmt())); + } + if (Strings.isNonEmpty(regex)) { + filters.add(RegisteredTypePredicates.nameOrAlias(StringPredicates.containsRegex(regex))); + } + if (Strings.isNonEmpty(fragment)) { + filters.add(RegisteredTypePredicates.nameOrAlias(StringPredicates.containsLiteralIgnoreCase(fragment))); + } + Predicate filter = Predicates.and(filters); + ImmutableList sortedItems = + FluentIterable.from(brooklyn().getTypeRegistry().getMatching(filter)) + .toSortedList(RegisteredTypes.RegisteredTypeNameThenBestFirstComparator.INSTANCE); + return TypeResource.toTypeSummary(brooklyn(), sortedItems, ui.getBaseUriBuilder()); + } + + @Override public List listApplications(String versions, String regex, String fragment) { return list(Application.class.getName(), versions, regex, fragment); } + @Override public List listEntities(String versions, String regex, String fragment) { return list(Entity.class.getName(), versions, regex, fragment); } + @Override public List listPolicies(String versions, String regex, String fragment) { return list(Policy.class.getName(), versions, regex, fragment); } + @Override public List listEnrichers(String versions, String regex, String fragment) { return list(Enricher.class.getName(), versions, regex, fragment); } + @Override public List listLocations(String versions, String regex, String fragment) { return list(Location.class.getName(), versions, regex, fragment); } + +} + diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java new file mode 100644 index 0000000000..86de57d62f --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java @@ -0,0 +1,186 @@ +/* + * 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 org.apache.brooklyn.rest.resources; + +import java.net.URI; +import java.util.List; +import java.util.Set; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriBuilder; + +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.core.catalog.internal.CatalogUtils; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; +import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.rest.api.TypeApi; +import org.apache.brooklyn.rest.domain.TypeDetail; +import org.apache.brooklyn.rest.domain.TypeSummary; +import org.apache.brooklyn.rest.filter.HaHotStateRequired; +import org.apache.brooklyn.rest.transform.TypeTransformer; +import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.StringPredicates; +import org.apache.brooklyn.util.text.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.io.Files; + +@HaHotStateRequired +public class TypeResource extends AbstractBrooklynRestResource implements TypeApi { + + private static final Logger log = LoggerFactory.getLogger(TypeResource.class); + private static final String LATEST = "latest"; + private static final String ALL = "all"; + + private static Set missingIcons = MutableSet.of(); + + static boolean isLatestOnly(String versions, boolean defaultValue) { + if (ALL.equalsIgnoreCase(versions)) return false; + if (LATEST.equalsIgnoreCase(versions)) return true; + if (Strings.isNonBlank(versions)) { + log.warn("Invalid 'versions' argument '"+versions+"' when listing types; should be 'all' or 'latest'"); + } + return defaultValue; + } + + @Override + public List list(String versions, String regex, String fragment) { + List> filters = MutableList.>of() + .append(RegisteredTypePredicates.entitledToSee(mgmt())); + if (TypeResource.isLatestOnly(versions, true)) { + // TODO inefficient - does n^2 comparisons where n is sufficient + // create RegisteredTypes.filterBestVersions to do a list after the initial parse + // (and javadoc in predicate method below) + filters.add(RegisteredTypePredicates.isBestVersion(mgmt())); + } + if (Strings.isNonEmpty(regex)) { + filters.add(RegisteredTypePredicates.nameOrAlias(StringPredicates.containsRegex(regex))); + } + if (Strings.isNonEmpty(fragment)) { + filters.add(RegisteredTypePredicates.nameOrAlias(StringPredicates.containsLiteralIgnoreCase(fragment))); + } + Predicate filter = Predicates.and(filters); + + ImmutableList sortedItems = + FluentIterable.from(brooklyn().getTypeRegistry().getMatching(filter)) + .toSortedList(RegisteredTypes.RegisteredTypeNameThenBestFirstComparator.INSTANCE); + return toTypeSummary(brooklyn(), sortedItems, ui.getBaseUriBuilder()); + } + + static List toTypeSummary(BrooklynRestResourceUtils brooklyn, Iterable sortedItems, UriBuilder uriBuilder) { + List result = MutableList.of(); + for (RegisteredType t: sortedItems) { + result.add(TypeTransformer.summary(brooklyn, t, uriBuilder)); + } + return result; + } + + @Override + public List listVersions(String nameOrAlias) { + Predicate filter = Predicates.and(RegisteredTypePredicates.entitledToSee(mgmt()), + RegisteredTypePredicates.nameOrAlias(nameOrAlias)); + ImmutableList sortedItems = + FluentIterable.from(brooklyn().getTypeRegistry().getMatching(filter)) + .toSortedList(RegisteredTypes.RegisteredTypeNameThenBestFirstComparator.INSTANCE); + return toTypeSummary(brooklyn(), sortedItems, ui.getBaseUriBuilder()); + } + + @Override + public TypeDetail detail(String symbolicName, String version) { + RegisteredType item = lookup(symbolicName, version); + return TypeTransformer.detail(brooklyn(), item, ui.getBaseUriBuilder()); + } + + protected RegisteredType lookup(String symbolicName, String version) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, symbolicName+":"+version)) { + // TODO best to default to "not found" - unless maybe they have permission to "see null" + throw WebResourceUtils.forbidden("User '%s' not permitted to see info on this type (including whether or not installed)", + Entitlements.getEntitlementContext().user()); + } + RegisteredType item; + if (LATEST.equalsIgnoreCase(version)) { + item = brooklyn().getTypeRegistry().get(symbolicName); + } else { + item = brooklyn().getTypeRegistry().get(symbolicName, version); + } + if (item==null) { + throw WebResourceUtils.notFound("Entity with id '%s:%s' not found", symbolicName, version); + } + return item; + } + + @Override + public Response icon(String symbolicName, String version) { + RegisteredType item = lookup(symbolicName, version); + return produceIcon(item); + } + + private Response produceIcon(RegisteredType result) { + String url = result.getIconUrl(); + if (url==null) { + log.debug("No icon available for "+result+"; returning "+Status.NO_CONTENT); + return Response.status(Status.NO_CONTENT).build(); + } + + if (brooklyn().isUrlServerSideAndSafe(url)) { + // classpath URL's we will serve IF they end with a recognised image format; + // paths (ie non-protocol) and + // NB, for security, file URL's are NOT served + log.debug("Loading and returning "+url+" as icon for "+result); + + MediaType mime = WebResourceUtils.getImageMediaTypeFromExtension(Files.getFileExtension(url)); + try { + Object content = ResourceUtils.create(CatalogUtils.newClassLoadingContext(mgmt(), result)).getResourceFromUrl(url); + return Response.ok(content, mime).build(); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + synchronized (missingIcons) { + if (missingIcons.add(url)) { + // note: this can be quite common when running from an IDE, as resources may not be copied; + // a mvn build should sort it out (the IDE will then find the resources, until you clean or maybe refresh...) + log.warn("Missing icon data for "+result.getId()+", expected at: "+url+" (subsequent messages will log debug only)"); + log.debug("Trace for missing icon data at "+url+": "+e, e); + } else { + log.debug("Missing icon data for "+result.getId()+", expected at: "+url+" (already logged WARN and error details)"); + } + } + throw WebResourceUtils.notFound("Icon unavailable for %s", result.getId()); + } + } + + log.debug("Returning redirect to "+url+" as icon for "+result); + + // for anything else we do a redirect (e.g. http / https; perhaps ftp) + return Response.temporaryRedirect(URI.create(url)).build(); + } + +} diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java index 8a15020d36..6bf3f19aa0 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.rest.transform; import static com.google.common.collect.Iterables.transform; +import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder; import java.lang.reflect.Field; import java.net.URI; @@ -26,13 +27,21 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import javax.ws.rs.core.UriBuilder; + import org.apache.brooklyn.api.catalog.CatalogConfig; import org.apache.brooklyn.api.entity.Application; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.objs.SpecParameter; import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.config.render.RendererHints; import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.rest.api.ApplicationApi; +import org.apache.brooklyn.rest.api.CatalogApi; +import org.apache.brooklyn.rest.api.EntityApi; +import org.apache.brooklyn.rest.api.EntityConfigApi; +import org.apache.brooklyn.rest.domain.AdjunctConfigSummary; import org.apache.brooklyn.rest.domain.EnricherConfigSummary; import org.apache.brooklyn.rest.domain.EntityConfigSummary; import org.apache.brooklyn.rest.domain.EntitySummary; @@ -43,13 +52,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; -import javax.ws.rs.core.UriBuilder; -import org.apache.brooklyn.core.catalog.internal.CatalogUtils; -import org.apache.brooklyn.rest.api.ApplicationApi; -import org.apache.brooklyn.rest.api.CatalogApi; -import org.apache.brooklyn.rest.api.EntityApi; -import org.apache.brooklyn.rest.api.EntityConfigApi; -import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder; /** * @author Adam Lowe @@ -123,6 +125,10 @@ public static EntityConfigSummary entityConfigSummary(ConfigKey config, Strin return new EntityConfigSummary(config, label, priority, pinned, mapOfLinks); } + public static AdjunctConfigSummary adjunctConfigSummary(ConfigKey config, String label, Double priority, Map links) { + return new AdjunctConfigSummary(config, label, priority, links); + } + public static PolicyConfigSummary policyConfigSummary(ConfigKey config, String label, Double priority, Map links) { return new PolicyConfigSummary(config, label, priority, links); } @@ -192,6 +198,11 @@ public static EntityConfigSummary entityConfigSummary(SpecParameter input, At return entityConfigSummary(input.getConfigKey(), input.getLabel(), priority, input.isPinned(), null); } + public static AdjunctConfigSummary adjunctConfigSummary(SpecParameter input) { + Double priority = input.isPinned() ? Double.valueOf(1d) : null; + return policyConfigSummary(input.getConfigKey(), input.getLabel(), priority, null); + } + public static PolicyConfigSummary policyConfigSummary(SpecParameter input) { Double priority = input.isPinned() ? Double.valueOf(1d) : null; return policyConfigSummary(input.getConfigKey(), input.getLabel(), priority, null); diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java new file mode 100644 index 0000000000..fb3fdd3388 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java @@ -0,0 +1,194 @@ +/* + * 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 org.apache.brooklyn.rest.transform; + +import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder; + +import java.net.URI; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.entity.EntityType; +import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.objs.EntityAdjunct; +import org.apache.brooklyn.api.objs.SpecParameter; +import org.apache.brooklyn.api.policy.Policy; +import org.apache.brooklyn.api.sensor.Enricher; +import org.apache.brooklyn.api.sensor.Feed; +import org.apache.brooklyn.api.sensor.Sensor; +import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.core.entity.EntityDynamicType; +import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; +import org.apache.brooklyn.core.objs.BrooklynTypes; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; +import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.rest.api.TypeApi; +import org.apache.brooklyn.rest.domain.AdjunctConfigSummary; +import org.apache.brooklyn.rest.domain.BundleInstallationRestResult; +import org.apache.brooklyn.rest.domain.BundleSummary; +import org.apache.brooklyn.rest.domain.EffectorSummary; +import org.apache.brooklyn.rest.domain.EntityConfigSummary; +import org.apache.brooklyn.rest.domain.SensorSummary; +import org.apache.brooklyn.rest.domain.SummaryComparators; +import org.apache.brooklyn.rest.domain.TypeDetail; +import org.apache.brooklyn.rest.domain.TypeSummary; +import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Sets; + +public class TypeTransformer { + + private static final org.slf4j.Logger log = LoggerFactory.getLogger(TypeTransformer.class); + + public static TypeSummary summary(BrooklynRestResourceUtils b, RegisteredType item, UriBuilder ub) { + return embellish(new TypeSummary(item), item, false, b, ub); + } + + public static TypeDetail detail(BrooklynRestResourceUtils b, RegisteredType item, UriBuilder ub) { + return embellish(new TypeDetail(item), item, true, b, ub); + } + + private static T embellish(T result, RegisteredType item, boolean detail, BrooklynRestResourceUtils b, UriBuilder ub) { + result.getExtraFields().put("links", makeLinks(item, ub)); + + if (RegisteredTypes.isTemplate(item)) { + result.getExtraFields().put("template", true); + } + if (item.getIconUrl()!=null) { + result.setIconUrl(tidyIconLink(b, item, item.getIconUrl(), ub)); + } + + if (detail) { + if (RegisteredTypes.isSubtypeOf(item, Entity.class)) { + embellishEntity(result, item, b); + } else if (RegisteredTypes.isSubtypeOf(item, EntityAdjunct.class) || + // when implied supertypes are used we won't need the code below + RegisteredTypes.isSubtypeOf(item, Policy.class) || RegisteredTypes.isSubtypeOf(item, Enricher.class) || RegisteredTypes.isSubtypeOf(item, Feed.class) + ) { + try { + Set config = Sets.newLinkedHashSet(); + + AbstractBrooklynObjectSpec spec = b.getTypeRegistry().createSpec(item, null, null); + for (final SpecParameter input : spec.getParameters()){ + config.add(EntityTransformer.adjunctConfigSummary(input)); + } + + result.getExtraFields().put("config", config); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.trace("Unable to create spec for "+item+": "+e, e); + } + + } else if (RegisteredTypes.isSubtypeOf(item, Location.class)) { + // TODO include config on location specs? (wasn't done previously so not needed, but good for completeness) + result.getExtraFields().put("config", Collections.emptyMap()); + } + } + return result; + } + + protected static void embellishEntity(T result, RegisteredType item, BrooklynRestResourceUtils b) { + try { + Set config = Sets.newLinkedHashSet(); + Set sensors = Sets.newTreeSet(SummaryComparators.nameComparator()); + Set effectors = Sets.newTreeSet(SummaryComparators.nameComparator()); + + EntitySpec spec = b.getTypeRegistry().createSpec(item, null, EntitySpec.class); + EntityDynamicType typeMap = BrooklynTypes.getDefinedEntityType(spec.getType()); + EntityType type = typeMap.getSnapshot(); + + AtomicInteger paramPriorityCnt = new AtomicInteger(); + for (SpecParameter input: spec.getParameters()) + config.add(EntityTransformer.entityConfigSummary(input, paramPriorityCnt)); + for (Sensor x: type.getSensors()) + sensors.add(SensorTransformer.sensorSummaryForCatalog(x)); + for (Effector x: type.getEffectors()) + effectors.add(EffectorTransformer.effectorSummaryForCatalog(x)); + + result.getExtraFields().put("config", config); + result.getExtraFields().put("sensors", sensors); + result.getExtraFields().put("effectors", effectors); + + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + + // templates with multiple entities can't have spec created in the manner above; just ignore + if (item.getSuperTypes().contains(Entity.class)) { + log.warn("Unable to create spec for "+item+": "+e, e); + } + if (log.isTraceEnabled()) { + log.trace("Unable to create spec for "+item+": "+e, e); + } + } + } + + public static BundleSummary bundleSummary(BrooklynRestResourceUtils brooklyn, ManagedBundle b, UriBuilder baseUriBuilder, ManagementContext mgmt) { + BundleSummary result = new BundleSummary(b); + for (RegisteredType t: mgmt.getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(b))) { + result.addType(summary(brooklyn, t, baseUriBuilder)); + } + return result; + } + + public static BundleSummary bundleDetails(BrooklynRestResourceUtils brooklyn, ManagedBundle b, UriBuilder baseUriBuilder, ManagementContext mgmt) { + BundleSummary result = bundleSummary(brooklyn, b, baseUriBuilder, mgmt); + result.getExtraFields().put("osgiVersion", b.getOsgiVersionString()); + result.getExtraFields().put("checksum", b.getChecksum()); + return result; + } + + public static BundleInstallationRestResult bundleInstallationResult(OsgiBundleInstallationResult in, ManagementContext mgmt, BrooklynRestResourceUtils brooklynU, UriInfo ui) { + BundleInstallationRestResult result = new BundleInstallationRestResult( + in.getMessage(), in.getVersionedName() != null ? in.getVersionedName().toString() : "", in.getCode()); + for (RegisteredType t: in.getTypesInstalled()) { + TypeSummary summary = TypeTransformer.summary(brooklynU, t, ui.getBaseUriBuilder()); + result.getTypes().put(t.getId(), summary); + } + return result; + } + + protected static Map makeLinks(RegisteredType item, UriBuilder ub) { + return MutableMap.of().addIfNotNull("self", getSelfLink(item, ub)); + } + + private static URI getSelfLink(RegisteredType item, UriBuilder ub) { + return serviceUriBuilder(ub, TypeApi.class, "detail").build(item.getSymbolicName(), item.getVersion()); + } + private static String tidyIconLink(BrooklynRestResourceUtils b, RegisteredType item, String iconUrl, UriBuilder ub) { + if (b.isUrlServerSideAndSafe(iconUrl)) { + return serviceUriBuilder(ub, TypeApi.class, "icon").build(item.getSymbolicName(), item.getVersion()).toString(); + } + return iconUrl; + } + +} diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/BundleAndTypeAndSubtypeResourcesTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/BundleAndTypeAndSubtypeResourcesTest.java new file mode 100644 index 0000000000..03578f1794 --- /dev/null +++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/BundleAndTypeAndSubtypeResourcesTest.java @@ -0,0 +1,1093 @@ +/* + * 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 org.apache.brooklyn.rest.resources; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.awt.Image; +import java.awt.Toolkit; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.objs.BrooklynObject; +import org.apache.brooklyn.api.objs.Configurable; +import org.apache.brooklyn.api.objs.Identifiable; +import org.apache.brooklyn.api.policy.Policy; +import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.core.entity.EntityPredicates; +import org.apache.brooklyn.core.mgmt.ha.OsgiManager; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.enricher.stock.Aggregator; +import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy; +import org.apache.brooklyn.rest.domain.BundleInstallationRestResult; +import org.apache.brooklyn.rest.domain.BundleSummary; +import org.apache.brooklyn.rest.domain.TypeDetail; +import org.apache.brooklyn.rest.domain.TypeSummary; +import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.test.support.TestResourceUnavailableException; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.core.osgi.BundleMaker; +import org.apache.brooklyn.util.javalang.JavaClassNames; +import org.apache.brooklyn.util.javalang.Reflections; +import org.apache.brooklyn.util.os.Os; +import org.apache.brooklyn.util.osgi.OsgiTestResources; +import org.apache.brooklyn.util.stream.Streams; +import org.apache.http.HttpHeaders; +import org.eclipse.jetty.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.Test; +import org.testng.reporters.Files; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +public class BundleAndTypeAndSubtypeResourcesTest extends BrooklynRestResourceTest { + + private static final Logger log = LoggerFactory.getLogger(BundleAndTypeAndSubtypeResourcesTest.class); + + private static String TEST_VERSION = "0.1.2"; + private static String TEST_LASTEST_VERSION = "0.1.3"; + + private Collection initialBundles; + + @Override + protected boolean useLocalScannedCatalog() { + return true; + } + + @Override + protected void initClass() throws Exception { + super.initClass(); + // cache initially installed bundles + OsgiManager osgi = ((ManagementContextInternal)getManagementContext()).getOsgiManager().get(); + initialBundles = osgi.getManagedBundles().values(); + } + + protected void initMethod() throws Exception { + super.initMethod(); + + // and reset OSGi container + OsgiManager osgi = ((ManagementContextInternal)getManagementContext()).getOsgiManager().get(); + for (ManagedBundle b: osgi.getManagedBundles().values()) { + if (!initialBundles.contains(b)) { + osgi.uninstallUploadedBundle(b); + } + } + } + + @Test + /** based on CampYamlLiteTest */ + public void testRegisterCustomEntityTopLevelSyntaxWithBundleWhereEntityIsFromCoreAndIconFromBundle() { + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH); + + String symbolicName = "my.catalog.entity.id"; + String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL; + String yaml = Joiner.on("\n").join( + "brooklyn.catalog:", + " id: " + symbolicName, + " version: " + TEST_VERSION, + " itemType: entity", + " name: My Catalog App", + " description: My description", + " icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif", + " libraries:", + " - url: " + bundleUrl, + " item:", + " type: org.apache.brooklyn.core.test.entity.TestEntity"); + + Response response = client().path("/bundles") + .post(yaml); + + assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode()); + BundleInstallationRestResult installed = response.readEntity(BundleInstallationRestResult.class); + Asserts.assertSize(installed.getTypes().values(), 1); + TypeSummary installedItem = installed.getTypes().get(symbolicName+":"+TEST_VERSION); + Assert.assertNotNull(installedItem, ""+installed.getTypes()); + + TypeDetail entityItem = client().path("/types/"+symbolicName + "/" + TEST_VERSION) + .get(TypeDetail.class); + + Assert.assertEquals(new TypeSummary(entityItem), installedItem); + Assert.assertNotNull(entityItem.getPlan()); + Assert.assertTrue(((String)entityItem.getPlan().getData()).contains("org.apache.brooklyn.core.test.entity.TestEntity")); + + assertEquals(entityItem.getSymbolicName(), symbolicName); + assertEquals(entityItem.getVersion(), TEST_VERSION); + + // also check it's included in various lists + List list1 = client().path("/types/"+symbolicName).get(new GenericType>() {}); + assertEquals(list1, MutableList.of(installedItem)); + List list2 = client().path("/types").get(new GenericType>() {}); + Assert.assertTrue(list2.contains(installedItem), ""+list2); + List list3 = client().path("/subtypes/entity").get(new GenericType>() {}); + Assert.assertTrue(list3.contains(installedItem), ""+list3); + + // and internally let's check we have libraries + RegisteredType item = getManagementContext().getTypeRegistry().get(symbolicName, TEST_VERSION); + Assert.assertNotNull(item); + Collection libs = item.getLibraries(); + assertEquals(libs.size(), 1); + assertEquals(Iterables.getOnlyElement(libs).getUrl(), bundleUrl); + + // now let's check other things on the item + URI expectedIconUrl = URI.create(getEndpointAddress() + "/types/" + symbolicName + "/" + entityItem.getVersion()+"/icon").normalize(); + assertEquals(entityItem.getDisplayName(), "My Catalog App"); + assertEquals(entityItem.getDescription(), "My description"); + assertEquals(entityItem.getIconUrl(), expectedIconUrl.getPath()); + assertEquals(item.getIconUrl(), "classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif"); + + // an InterfacesTag should be created for every catalog item + if (checkTraits(false)) { + @SuppressWarnings("unchecked") + Map> traitsMapTag = Iterables.getOnlyElement(Iterables.filter(entityItem.getTags(), Map.class)); + List actualInterfaces = traitsMapTag.get("traits"); + List> expectedInterfaces = Reflections.getAllInterfaces(TestEntity.class); + assertEquals(actualInterfaces.size(), expectedInterfaces.size()); + for (Class expectedInterface : expectedInterfaces) { + assertTrue(actualInterfaces.contains(expectedInterface.getName())); + } + } + + byte[] iconData = client().path("/types/" + symbolicName + "/" + TEST_VERSION+"/icon").get(byte[].class); + assertEquals(iconData.length, 43); + } + + @Test + public void testRegisterOsgiPolicyTopLevelSyntax() { + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH); + + String symbolicName = "my.catalog.entity.id."+JavaClassNames.niceClassAndMethod(); + String policyType = "org.apache.brooklyn.test.osgi.entities.SimplePolicy"; + String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL; + + String yaml = Joiner.on("\n").join( + "brooklyn.catalog:", + " id: " + symbolicName, + " version: " + TEST_VERSION, + " itemType: policy", + " name: My Catalog App", + " description: My description", + " libraries:", + " - url: " + bundleUrl, + " item:", + " type: " + policyType); + + TypeSummary installedItem = Iterables.getOnlyElement( client().path("/bundles") + .post(yaml, BundleInstallationRestResult.class).getTypes().values() ); + + assertEquals(installedItem.getSymbolicName(), symbolicName); + assertEquals(installedItem.getVersion(), TEST_VERSION); + Assert.assertTrue(installedItem.getSupertypes().contains(Policy.class.getName()), ""+installedItem.getSupertypes()); + } + + @Test + public void testFilterListOfEntitiesByName() { + List entities = client().path("/types") + .query("fragment", "vaNIllasOFTWAREpROCESS").get(new GenericType>() {}); + log.info("Matching entities: " + entities); + assertEquals(entities.size(), 1); + + entities = client().path("/subtypes/entity") + .query("fragment", "vaNIllasOFTWAREpROCESS").get(new GenericType>() {}); + log.info("Matching entities: " + entities); + assertEquals(entities.size(), 1); + + List entities2 = client().path("/types") + .query("regex", "[Vv]an.[alS]+oftware\\w+").get(new GenericType>() {}); + assertEquals(entities2.size(), 1); + + assertEquals(entities, entities2); + + entities = client().path("/subtypes/entity") + .query("fragment", "bweqQzZ").get(new GenericType>() {}); + Asserts.assertSize(entities, 0); + + entities = client().path("/subtypes/entity") + .query("regex", "bweq+z+").get(new GenericType>() {}); + Asserts.assertSize(entities, 0); + } + + @Test + public void testGetCatalogEntityIconDetails() throws IOException { + String catalogItemId = "testGetCatalogEntityIconDetails"; + addTestCatalogItemAsEntity(catalogItemId); + Response response = client().path(URI.create("/types/" + catalogItemId + "/" + TEST_VERSION + "/icon")) + .get(); + response.bufferEntity(); + Assert.assertEquals(response.getStatus(), 200); + Assert.assertEquals(response.getMediaType(), MediaType.valueOf("image/png")); + Image image = Toolkit.getDefaultToolkit().createImage(Files.readFile(response.readEntity(InputStream.class))); + Assert.assertNotNull(image); + } + + private void addTestCatalogItemAsEntity(String catalogItemId) { + addTestCatalogItem(catalogItemId, "entity", TEST_VERSION, "org.apache.brooklyn.rest.resources.DummyIconEntity"); + } + + private void addTestCatalogItem(String catalogItemId, String itemType, String version, String service) { + String yaml = Joiner.on("\n").join( + "brooklyn.catalog:", + " id: " + catalogItemId, + " version: " + TEST_VERSION, + " itemType: " + checkNotNull(itemType), + " name: My Catalog App", + " description: My description", + " icon_url: classpath:///bridge-small.png", + " version: " + version, + " item:", + " type: " + service); + + client().path("/bundles").post(yaml); + } + + @Test + public void testListPolicies() { + Set policies = client().path("/subtypes/policy") + .get(new GenericType>() {}); + + assertTrue(policies.size() > 0); + TypeSummary asp = null; + for (TypeSummary p : policies) { + if (AutoScalerPolicy.class.getName().equals(p.getSymbolicName())) + asp = p; + } + Assert.assertNotNull(asp, "didn't find AutoScalerPolicy"); + } + + @Test + public void testLocationAddGetAndRemove() { + String symbolicName = "my.catalog.location.id"; + String locationType = "localhost"; + String yaml = Joiner.on("\n").join( + "brooklyn.catalog:", + " id: " + symbolicName, + " version: " + TEST_VERSION, + " itemType: location", + " name: My Catalog Location", + " description: My description", + " item:", + " type: " + locationType); + + // Create location item + Map items = client().path("/bundles") + .post(yaml, BundleInstallationRestResult.class).getTypes(); + TypeSummary locationItem = Iterables.getOnlyElement(items.values()); + + assertEquals(locationItem.getSymbolicName(), symbolicName); + assertEquals(locationItem.getVersion(), TEST_VERSION); + + // Retrieve location item + TypeDetail location = client().path("/types/"+symbolicName+"/"+TEST_VERSION).get(TypeDetail.class); + assertEquals(location.getSymbolicName(), symbolicName); + + // Retrieve all locations + Set locations = client().path("/subtypes/location") + .get(new GenericType>() {}); + boolean found = false; + for (TypeSummary contender : locations) { + if (contender.getSymbolicName().equals(symbolicName)) { + found = true; + break; + } + } + Assert.assertTrue(found, "contenders="+locations); + + // Delete + Response deleteResponse = client().path("/bundles/"+locationItem.getContainingBundle().replaceAll(":", "/")) + .delete(); + assertEquals(deleteResponse.getStatus(), Response.Status.OK.getStatusCode()); + BundleInstallationRestResult deletionResponse = deleteResponse.readEntity(BundleInstallationRestResult.class); + Assert.assertEquals(deletionResponse.getBundle(), symbolicName+":"+TEST_VERSION); + Assert.assertEquals(deletionResponse.getTypes().keySet(), MutableSet.of(symbolicName+":"+TEST_VERSION)); + + Response getPostDeleteResponse = client().path("/types/"+symbolicName+"/"+TEST_VERSION) + .get(); + assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode()); + } + + @Test + public void testListEnrichers() { + Set enrichers = client().path("/subtypes/enricher") + .get(new GenericType>() {}); + + assertTrue(enrichers.size() > 0); + TypeSummary asp = null; + for (TypeSummary p : enrichers) { + if (Aggregator.class.getName().equals(p.getSymbolicName())) + asp = p; + } + Assert.assertNotNull(asp, "didn't find Aggregator"); + } + + @Test + public void testEnricherAddGet() { + String symbolicName = "my.catalog.enricher.id"; + String enricherType = "org.apache.brooklyn.enricher.stock.Aggregator"; + String yaml = Joiner.on("\n").join( + "brooklyn.catalog:", + " id: " + symbolicName, + " version: " + TEST_VERSION, + " itemType: enricher", + " name: My Catalog Enricher", + " description: My description", + " item:", + " type: " + enricherType); + + // Create location item + Map items = client().path("/bundles") + .post(yaml, BundleInstallationRestResult.class).getTypes(); + TypeSummary enricherItem = Iterables.getOnlyElement(items.values()); + + assertEquals(enricherItem.getSymbolicName(), symbolicName); + assertEquals(enricherItem.getVersion(), TEST_VERSION); + + // Retrieve location item + TypeSummary enricher = client().path("/types/"+symbolicName+"/"+TEST_VERSION) + .get(TypeSummary.class); + assertEquals(enricher.getSymbolicName(), symbolicName); + + // Retrieve all locations + Set enrichers = client().path("/subtypes/enricher") + .get(new GenericType>() {}); + boolean found = false; + for (TypeSummary contender : enrichers) { + if (contender.getSymbolicName().equals(symbolicName)) { + found = true; + break; + } + } + Assert.assertTrue(found, "contenders="+enrichers); + } + + @Test + // osgi may fail in IDE, typically works on mvn CLI though + public void testRegisterOsgiEnricherTopLevelSyntax() { + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH); + + String symbolicName = "my.catalog.enricher.id"; + String enricherType = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_SIMPLE_ENRICHER; + String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL; + + String yaml = Joiner.on("\n").join( + "brooklyn.catalog:", + " id: " + symbolicName, + " version: " + TEST_VERSION, + " itemType: enricher", + " name: My Catalog Enricher", + " description: My description", + " libraries:", + " - url: " + bundleUrl, + " item:", + " type: " + enricherType); + + TypeSummary installedItem = Iterables.getOnlyElement( client().path("/bundles") + .post(yaml, BundleInstallationRestResult.class).getTypes().values() ); + + assertEquals(installedItem.getSymbolicName(), symbolicName); + assertEquals(installedItem.getVersion(), TEST_VERSION); + } + + @Test + public void testDeleteCustomEntityFromCatalog() { + String symbolicName = "my.catalog.app.id.to.subsequently.delete"; + String yaml = Joiner.on("\n").join( + "brooklyn.catalog:", + " id: " + symbolicName, + " version: " + TEST_VERSION, + " itemType: entity", + " name: My Catalog App To Be Deleted", + " description: My description", + " item:", + " type: org.apache.brooklyn.core.test.entity.TestEntity"); + + client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/yaml") + .post(yaml); + + BundleSummary getInstalledBundle = client().path("/bundles/"+symbolicName+"/"+TEST_VERSION) + .get(BundleSummary.class); + assertEquals(getInstalledBundle.getSymbolicName(), symbolicName); + assertEquals(getInstalledBundle.getVersion(), TEST_VERSION); + Asserts.assertNotNull(getInstalledBundle.getTypes(), "expected 'types' in: "+getInstalledBundle.getExtraFields()); + Asserts.assertStringContains(""+getInstalledBundle.getTypes(), "My Catalog App"); + + Response deleteResponse = client().path("/bundles/"+symbolicName+"/"+TEST_VERSION) + .delete(); + + assertEquals(deleteResponse.getStatus(), Response.Status.OK.getStatusCode()); + // contents of delete tested in delete location method + + Response getPostDeleteResponse = client().path("/bundles/"+symbolicName+"/"+TEST_VERSION) + .get(); + assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode()); + } + + private void addCatalogItemWithInvalidBundleUrl(String bundleUrl) { + String symbolicName = "my.catalog.entity.id"; + String yaml = Joiner.on("\n").join( + "brooklyn.catalog:", + " id: " + symbolicName, + " version: " + TEST_VERSION, + " itemType: entity", + " name: My Catalog App", + " description: My description", + " icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif", + " libraries:", + " - url: " + bundleUrl, + " item:", + " type: org.apache.brooklyn.core.test.entity.TestEntity"); + + Response response = client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-yaml") + .post(yaml); + + assertEquals(response.getStatus(), HttpStatus.BAD_REQUEST_400); + } + + @Test + public void testAddUnreachableItem() { + addCatalogItemWithInvalidBundleUrl("http://0.0.0.0/can-not-connect"); + } + + @Test + public void testAddInvalidItem() { + //equivalent to HTTP response 200 text/html + addCatalogItemWithInvalidBundleUrl("classpath://not-a-jar-file.txt"); + } + + @Test + public void testAddMissingItem() { + //equivalent to HTTP response 404 text/html + addCatalogItemWithInvalidBundleUrl("classpath://missing-jar-file.txt"); + } + + @Test + public void testInvalidArchive() throws Exception { + File f = Os.newTempFile("osgi", "zip"); + + Response response = client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") + .post(Streams.readFully(new FileInputStream(f))); + + assertEquals(response.getStatus(), Response.Status.BAD_REQUEST.getStatusCode()); + Asserts.assertStringContainsIgnoreCase(response.readEntity(String.class), "zip file is empty"); + } + + @Test + public void testArchiveWithoutBom() throws Exception { + File f = createZip(ImmutableMap.of()); + + Response response = client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") + .post(Streams.readFully(new FileInputStream(f))); + + assertEquals(response.getStatus(), Response.Status.BAD_REQUEST.getStatusCode()); + Asserts.assertStringContainsIgnoreCase(response.readEntity(String.class), "Missing bundle symbolic name in BOM or MANIFEST"); + } + + @Test + public void testArchiveWithoutBundleAndVersion() throws Exception { + File f = createZip(ImmutableMap.of("catalog.bom", Joiner.on("\n").join( + "brooklyn.catalog:", + " itemType: entity", + " name: My Catalog App", + " description: My description", + " icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif", + " item:", + " type: org.apache.brooklyn.core.test.entity.TestEntity"))); + + Response response = client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") + .post(Streams.readFully(new FileInputStream(f))); + + assertEquals(response.getStatus(), Response.Status.BAD_REQUEST.getStatusCode()); + Asserts.assertStringContainsIgnoreCase(response.readEntity(String.class), "Missing bundle symbolic name in BOM or MANIFEST"); + } + + @Test + public void testArchiveWithoutBundle() throws Exception { + File f = createZip(ImmutableMap.of("catalog.bom", Joiner.on("\n").join( + "brooklyn.catalog:", + " version: 0.1.0", + " itemType: entity", + " name: My Catalog App", + " description: My description", + " icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif", + " item:", + " type: org.apache.brooklyn.core.test.entity.TestEntity"))); + + Response response = client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") + .post(Streams.readFully(new FileInputStream(f))); + + assertEquals(response.getStatus(), Response.Status.BAD_REQUEST.getStatusCode()); + Asserts.assertStringContainsIgnoreCase(response.readEntity(String.class), + "Missing bundle symbolic name in BOM or MANIFEST"); + } + + @Test + public void testArchiveWithoutVersion() throws Exception { + File f = createZip(ImmutableMap.of("catalog.bom", Joiner.on("\n").join( + "brooklyn.catalog:", + " bundle: org.apache.brooklyn.test", + " itemType: entity", + " name: My Catalog App", + " description: My description", + " icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif", + " item:", + " type: org.apache.brooklyn.core.test.entity.TestEntity"))); + + Response response = client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") + .post(Streams.readFully(new FileInputStream(f))); + + assertEquals(response.getStatus(), Response.Status.BAD_REQUEST.getStatusCode()); + Asserts.assertStringContainsIgnoreCase(response.readEntity(String.class), "Catalog BOM must define version"); + } + + @Test + public void testJarWithoutMatchingBundle() throws Exception { + String name = "My Catalog App"; + String bundle = "org.apache.brooklyn.test"; + String version = "0.1.0"; + String wrongBundleName = "org.apache.brooklyn.test2"; + File f = createJar(ImmutableMap.of( + "catalog.bom", Joiner.on("\n").join( + "brooklyn.catalog:", + " bundle: " + bundle, + " version: " + version, + " itemType: entity", + " name: " + name, + " description: My description", + " icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif", + " item:", + " type: org.apache.brooklyn.core.test.entity.TestEntity"), + "META-INF/MANIFEST.MF", Joiner.on("\n").join( + "Manifest-Version: 1.0", + "Bundle-Name: " + name, + "Bundle-SymbolicName: "+wrongBundleName, + "Bundle-Version: " + version, + "Bundle-ManifestVersion: " + version))); + + Response response = client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-jar") + .post(Streams.readFully(new FileInputStream(f))); + + assertEquals(response.getStatus(), Response.Status.BAD_REQUEST.getStatusCode()); + Asserts.assertStringContainsIgnoreCase(response.readEntity(String.class), + "symbolic name mismatch", + wrongBundleName, bundle); + } + + @Test + public void testJarWithoutMatchingVersion() throws Exception { + String name = "My Catalog App"; + String bundle = "org.apache.brooklyn.test"; + String version = "0.1.0"; + String wrongVersion = "0.3.0"; + File f = createJar(ImmutableMap.of( + "catalog.bom", Joiner.on("\n").join( + "brooklyn.catalog:", + " bundle: " + bundle, + " version: " + version, + " itemType: entity", + " name: " + name, + " description: My description", + " icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif", + " item:", + " type: org.apache.brooklyn.core.test.entity.TestEntity"), + "META-INF/MANIFEST.MF", Joiner.on("\n").join( + "Manifest-Version: 1.0", + "Bundle-Name: " + name, + "Bundle-SymbolicName: " + bundle, + "Bundle-Version: " + wrongVersion, + "Bundle-ManifestVersion: " + wrongVersion))); + + Response response = client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-jar") + .post(Streams.readFully(new FileInputStream(f))); + + assertEquals(response.getStatus(), Response.Status.BAD_REQUEST.getStatusCode()); + Asserts.assertStringContainsIgnoreCase(response.readEntity(String.class), + "version mismatch", + wrongVersion, version); + } + + @Test + public void testOsgiBundleWithBom() throws Exception { + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH); + final String symbolicName = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_SYMBOLIC_NAME_FULL; + final String version = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_VERSION; + final String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL; + BundleMaker bm = new BundleMaker(manager); + File f = Os.newTempFile("osgi", "jar"); + Files.copyFile(ResourceUtils.create(this).getResourceFromUrl(bundleUrl), f); + + String bom = Joiner.on("\n").join( + "brooklyn.catalog:", + " bundle: " + symbolicName, + " version: " + version, + " id: " + symbolicName, + " itemType: entity", + " name: My Catalog App", + " description: My description", + " icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif", + " item:", + " type: org.apache.brooklyn.core.test.entity.TestEntity"); + + f = bm.copyAdding(f, MutableMap.of(new ZipEntry("catalog.bom"), (InputStream) new ByteArrayInputStream(bom.getBytes()))); + + Response response = client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-jar") + .post(Streams.readFully(new FileInputStream(f))); + + assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode()); + + TypeSummary entityItem = client().path("/types/"+symbolicName + "/" + version) + .get(TypeSummary.class); + + assertEquals(entityItem.getSymbolicName(), symbolicName); + assertEquals(entityItem.getVersion(), version); + + // and internally let's check we have libraries + RegisteredType item = getManagementContext().getTypeRegistry().get(symbolicName, version); + Assert.assertNotNull(item); + Collection libs = item.getLibraries(); + assertEquals(libs.size(), 1); + OsgiBundleWithUrl lib = Iterables.getOnlyElement(libs); + Assert.assertNull(lib.getUrl()); + + assertEquals(lib.getSymbolicName(), "org.apache.brooklyn.test.resources.osgi.brooklyn-test-osgi-entities"); + assertEquals(lib.getSuppliedVersionString(), version); + + // now let's check other things on the item + URI expectedIconUrl = URI.create(getEndpointAddress() + "/types/" + symbolicName + "/" + entityItem.getVersion()+"/icon").normalize(); + assertEquals(entityItem.getDisplayName(), "My Catalog App"); + assertEquals(entityItem.getDescription(), "My description"); + assertEquals(entityItem.getIconUrl(), expectedIconUrl.getPath()); + assertEquals(item.getIconUrl(), "classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif"); + + if (checkTraits(false)) { + // an InterfacesTag should be created for every catalog item + @SuppressWarnings("unchecked") + Map> traitsMapTag = Iterables.getOnlyElement(Iterables.filter(entityItem.getTags(), Map.class)); + List actualInterfaces = traitsMapTag.get("traits"); + List> expectedInterfaces = Reflections.getAllInterfaces(TestEntity.class); + assertEquals(actualInterfaces.size(), expectedInterfaces.size()); + for (Class expectedInterface : expectedInterfaces) { + assertTrue(actualInterfaces.contains(expectedInterface.getName())); + } + } + + byte[] iconData = client().path("/types/" + symbolicName + "/" + version + "/icon").get(byte[].class); + assertEquals(iconData.length, 43); + } + + @Test + public void testOsgiBundleWithBomNotInBrooklynNamespace() throws Exception { + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_COM_EXAMPLE_PATH); + final String symbolicName = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_COM_EXAMPLE_SYMBOLIC_NAME_FULL; + final String version = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_COM_EXAMPLE_VERSION; + final String bundleUrl = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_COM_EXAMPLE_URL; + final String entityType = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_COM_EXAMPLE_ENTITY; + final String iconPath = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_COM_EXAMPLE_ICON_PATH; + BundleMaker bm = new BundleMaker(manager); + File f = Os.newTempFile("osgi", "jar"); + Files.copyFile(ResourceUtils.create(this).getResourceFromUrl(bundleUrl), f); + + String bom = Joiner.on("\n").join( + "brooklyn.catalog:", + " bundle: " + symbolicName, + " version: " + version, + " id: " + symbolicName, + " itemType: entity", + " name: My Catalog App", + " description: My description", + " icon_url: classpath:" + iconPath, + " item:", + " type: " + entityType); + + f = bm.copyAdding(f, MutableMap.of(new ZipEntry("catalog.bom"), (InputStream) new ByteArrayInputStream(bom.getBytes()))); + + Response response = client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") + .post(Streams.readFully(new FileInputStream(f))); + + + assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode()); + + TypeDetail entityItem = client().path("/types/"+symbolicName + "/" + version) + .get(TypeDetail.class); + + Assert.assertNotNull(entityItem.getPlan().getData()); + Assert.assertTrue(entityItem.getPlan().getData().toString().contains(entityType)); + + assertEquals(entityItem.getSymbolicName(), symbolicName); + assertEquals(entityItem.getVersion(), version); + + // and internally let's check we have libraries + RegisteredType item = getManagementContext().getTypeRegistry().get(symbolicName, version); + Assert.assertNotNull(item); + Collection libs = item.getLibraries(); + assertEquals(libs.size(), 1); + OsgiBundleWithUrl lib = Iterables.getOnlyElement(libs); + Assert.assertNull(lib.getUrl()); + + assertEquals(lib.getSymbolicName(), symbolicName); + assertEquals(lib.getSuppliedVersionString(), version); + + // now let's check other things on the item + assertEquals(entityItem.getDescription(), "My description"); + URI expectedIconUrl = URI.create(getEndpointAddress() + "/types/" + symbolicName + "/" + entityItem.getVersion() + "/icon").normalize(); + assertEquals(entityItem.getIconUrl(), expectedIconUrl.getPath()); + assertEquals(item.getIconUrl(), "classpath:" + iconPath); + + if (checkTraits(false)) { + // an InterfacesTag should be created for every catalog item + @SuppressWarnings("unchecked") + Map> traitsMapTag = Iterables.getOnlyElement(Iterables.filter(entityItem.getTags(), Map.class)); + List actualInterfaces = traitsMapTag.get("traits"); + List expectedInterfaces = ImmutableList.of(Entity.class.getName(), BrooklynObject.class.getName(), Identifiable.class.getName(), Configurable.class.getName()); + assertTrue(actualInterfaces.containsAll(expectedInterfaces), "actual="+actualInterfaces); + } + + byte[] iconData = client().path("/types/" + symbolicName + "/" + version + "/icon").get(byte[].class); + assertEquals(iconData.length, 43); + + // Check that the catalog item is useable (i.e. can deploy the entity) + String appYaml = Joiner.on("\n").join( + "services:", + "- type: " + symbolicName + ":" + version, + " name: myEntityName"); + + Response appResponse = client().path("/applications") + .header(HttpHeaders.CONTENT_TYPE, "application/x-yaml") + .post(appYaml); + + assertEquals(appResponse.getStatus(), Response.Status.CREATED.getStatusCode()); + + Entity entity = Iterables.tryFind(getManagementContext().getEntityManager().getEntities(), EntityPredicates.displayNameEqualTo("myEntityName")).get(); + assertEquals(entity.getEntityType().getName(), entityType); + } + + private static File createZip(Map files) throws Exception { + File f = Os.newTempFile("osgi", "zip"); + + ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(f)); + + for (Map.Entry entry : files.entrySet()) { + ZipEntry ze = new ZipEntry(entry.getKey()); + zip.putNextEntry(ze); + zip.write(entry.getValue().getBytes()); + } + + zip.closeEntry(); + zip.flush(); + zip.close(); + + return f; + } + + private static File createJar(Map files) throws Exception { + File f = Os.newTempFile("osgi", "jar"); + + JarOutputStream zip = new JarOutputStream(new FileOutputStream(f)); + + for (Map.Entry entry : files.entrySet()) { + JarEntry ze = new JarEntry(entry.getKey()); + zip.putNextEntry(ze); + zip.write(entry.getValue().getBytes()); + } + + zip.closeEntry(); + zip.flush(); + zip.close(); + + return f; + } + + @Test + public void testGetOnlyLatestApplication() { + String symbolicName = "latest.catalog.application.id"; + String itemType = "template"; + String serviceType = "org.apache.brooklyn.core.test.entity.TestEntity"; + + addTestCatalogItem(symbolicName, itemType, TEST_VERSION, serviceType); + addTestCatalogItem(symbolicName, itemType, TEST_LASTEST_VERSION, serviceType); + + TypeSummary application = client().path("/types/" + symbolicName + "/latest") + .get(TypeSummary.class); + assertEquals(application.getVersion(), TEST_LASTEST_VERSION); + } + + @Test + public void testGetOnlyLatestDifferentCases() { + // depends on installation of this + testGetOnlyLatestApplication(); + + String symbolicName = "latest.catalog.application.id"; + + TypeSummary application = client().path("/types/" + symbolicName + "/LaTeSt") + .get(TypeSummary.class); + assertEquals(application.getVersion(), TEST_LASTEST_VERSION); + + application = client().path("/types/" + symbolicName + "/LATEST") + .get(TypeSummary.class); + assertEquals(application.getVersion(), TEST_LASTEST_VERSION); + } + + @Test + public void testGetOnlyLatestEntity() { + String symbolicName = "latest.catalog.entity.id"; + String itemType = "entity"; + String serviceType = "org.apache.brooklyn.core.test.entity.TestEntity"; + + addTestCatalogItem(symbolicName, itemType, TEST_VERSION, serviceType); + addTestCatalogItem(symbolicName, itemType, TEST_LASTEST_VERSION, serviceType); + + TypeSummary application = client().path("/types/" + symbolicName + "/latest") + .get(TypeSummary.class); + assertEquals(application.getVersion(), TEST_LASTEST_VERSION); + } + + @Test + public void testGetOnlyLatestLocation() { + String symbolicName = "latest.catalog.location.id"; + String itemType = "location"; + String serviceType = "localhost"; + + addTestCatalogItem(symbolicName, itemType, TEST_VERSION, serviceType); + addTestCatalogItem(symbolicName, itemType, TEST_LASTEST_VERSION, serviceType); + + TypeSummary application = client().path("/types/" + symbolicName + "/latest") + .get(TypeSummary.class); + assertEquals(application.getVersion(), TEST_LASTEST_VERSION); + } + + + @Test + public void testForceUpdateForYAML() { + String symbolicName = "force.update.catalog.application.id"; + String itemType = "template"; + String initialName = "My Catalog App"; + String initialDescription = "My description"; + String updatedName = initialName + " 2"; + String updatedDescription = initialDescription + " 2"; + + String initialYaml = Joiner.on("\n").join( + "brooklyn.catalog:", + " id: " + symbolicName, + " version: " + TEST_VERSION, + " itemType: " + itemType, + " name: " + initialName, + " description: " + initialDescription, + " icon_url: classpath:///bridge-small.png", + " version: " + TEST_VERSION, + " item:", + " type: org.apache.brooklyn.core.test.entity.TestEntity"); + String updatedYaml = Joiner.on("\n").join( + "brooklyn.catalog:", + " id: " + symbolicName, + " version: " + TEST_VERSION, + " itemType: " + itemType, + " name: " + updatedName, + " description: " + updatedDescription, + " icon_url: classpath:///bridge-small.png", + " version: " + TEST_VERSION, + " item:", + " type: org.apache.brooklyn.core.test.entity.TestEntity"); + + client().path("/bundles").post(initialYaml); + + TypeDetail initialApplication = client().path("/types/" + symbolicName + "/" + TEST_VERSION) + .get(TypeDetail.class); + assertEquals(initialApplication.getDisplayName(), initialName); + assertEquals(initialApplication.getDescription(), initialDescription); + + Response invalidResponse = client().path("/bundles").post(updatedYaml); + + assertEquals(invalidResponse.getStatus(), Response.Status.BAD_REQUEST.getStatusCode()); + + Response validResponse = client().path("/bundles").query("force", true).post(updatedYaml); + + assertEquals(validResponse.getStatus(), Response.Status.CREATED.getStatusCode()); + + TypeSummary application = client().path("/types/" + symbolicName + "/" + TEST_VERSION) + .get(TypeSummary.class); + assertEquals(application.getDisplayName(), updatedName); + assertEquals(application.getDescription(), updatedDescription); + } + + @Test + public void testForceUpdateForZip() throws Exception { + final String symbolicName = "force.update.zip.catalog.application.id"; + final String initialName = "My Catalog App"; + final String initialDescription = "My Description"; + final String updatedName = initialName + " 2"; + final String updatedDescription = initialDescription +" 2"; + + File initialZip = createZip(ImmutableMap.of("catalog.bom", Joiner.on("\n").join( + "brooklyn.catalog:", + " bundle: " + symbolicName, + " version: " + TEST_VERSION, + " id: " + symbolicName, + " itemType: entity", + " name: " + initialName, + " description: " + initialDescription, + " icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif", + " item:", + " type: org.apache.brooklyn.core.test.entity.TestEntity"))); + File updatedZip = createZip(ImmutableMap.of("catalog.bom", Joiner.on("\n").join( + "brooklyn.catalog:", + " bundle: " + symbolicName, + " version: " + TEST_VERSION, + " id: " + symbolicName, + " itemType: entity", + " name: " + updatedName, + " description: " + updatedDescription, + " icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif", + " item:", + " type: org.apache.brooklyn.core.test.entity.TestEntity"))); + + client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") + .post(Streams.readFully(new FileInputStream(initialZip))); + + TypeSummary initialEntity = client().path("/types/" + symbolicName + "/" + TEST_VERSION) + .get(TypeSummary.class); + assertEquals(initialEntity.getDisplayName(), initialName); + assertEquals(initialEntity.getDescription(), initialDescription); + + Response invalidResponse = client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") + .post(Streams.readFully(new FileInputStream(updatedZip))); + + assertEquals(invalidResponse.getStatus(), Response.Status.BAD_REQUEST.getStatusCode()); + + Response validResponse = client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") + .query("force", true) + .post(Streams.readFully(new FileInputStream(updatedZip))); + + assertEquals(validResponse.getStatus(), Response.Status.CREATED.getStatusCode()); + + TypeSummary entity = client().path("/types/" + symbolicName + "/" + TEST_VERSION) + .get(TypeSummary.class); + assertEquals(entity.getDisplayName(), updatedName); + assertEquals(entity.getDescription(), updatedDescription); + } + + @Test + public void testForceUpdateForJar() throws Exception { + final String symbolicName = "force.update.jar.catalog.application.id"; + final String initialName = "My Catalog App"; + final String initialDescription = "My Description"; + final String updatedName = initialName + " 2"; + final String updatedDescription = initialDescription +" 2"; + + File initialJar = createJar(ImmutableMap.of("catalog.bom", Joiner.on("\n").join( + "brooklyn.catalog:", + " bundle: " + symbolicName, + " version: " + TEST_VERSION, + " id: " + symbolicName, + " itemType: entity", + " name: " + initialName, + " description: " + initialDescription, + " icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif", + " item:", + " type: org.apache.brooklyn.core.test.entity.TestEntity"))); + File updatedJar = createJar(ImmutableMap.of("catalog.bom", Joiner.on("\n").join( + "brooklyn.catalog:", + " bundle: " + symbolicName, + " version: " + TEST_VERSION, + " id: " + symbolicName, + " itemType: entity", + " name: " + updatedName, + " description: " + updatedDescription, + " icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif", + " item:", + " type: org.apache.brooklyn.core.test.entity.TestEntity"))); + + client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-jar") + .post(Streams.readFully(new FileInputStream(initialJar))); + + TypeSummary initialEntity = client().path("/types/" + symbolicName + "/" + TEST_VERSION) + .get(TypeSummary.class); + assertEquals(initialEntity.getDisplayName(), initialName); + assertEquals(initialEntity.getDescription(), initialDescription); + + Response invalidResponse = client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-jar") + .post(Streams.readFully(new FileInputStream(updatedJar))); + + assertEquals(invalidResponse.getStatus(), Response.Status.BAD_REQUEST.getStatusCode()); + + Response validResponse = client().path("/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/x-jar") + .query("force", true) + .post(Streams.readFully(new FileInputStream(updatedJar))); + + assertEquals(validResponse.getStatus(), Response.Status.CREATED.getStatusCode()); + + TypeSummary entity = client().path("/types/" + symbolicName + "/" + TEST_VERSION) + .get(TypeSummary.class); + assertEquals(entity.getDisplayName(), updatedName); + assertEquals(entity.getDescription(), updatedDescription); + } + + // TODO traits no longer always set - we have supertypes so not needed, we should investigate when they are and when they aren't + // and switch those to setting and using the supertypes + private boolean checkTraits(boolean currentExpectedToBeWorking) { + return currentExpectedToBeWorking; + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java b/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java index 9fec03c03c..9734153068 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java @@ -17,19 +17,24 @@ import static com.google.common.base.Preconditions.checkNotNull; +import java.util.Comparator; + import javax.annotation.Nullable; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.BrooklynVersionSyntax; +import org.apache.brooklyn.util.text.NaturalOrderComparator; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.text.VersionComparator; import org.osgi.framework.Bundle; import org.osgi.framework.Version; import com.google.common.base.Objects; +import com.google.common.collect.ComparisonChain; /** Records a name (string) and version (string), * with conveniences for pretty-printing and converting to OSGi format. */ -public class VersionedName { +public class VersionedName implements Comparable { private final String name; private final String v; @@ -172,4 +177,21 @@ public static Maybe parseMaybe(String symbolicNameWithVersion, bo return Maybe.of(new VersionedName(parts[0], parts.length == 2 ? parts[1] : null)); } + @Override + public int compareTo(VersionedName other) { + return VersionedNameComparator.INSTANCE.compare(this, other); + } + + public static class VersionedNameComparator implements Comparator { + public static final VersionedNameComparator INSTANCE = new VersionedNameComparator(); + + @Override + public int compare(VersionedName o1, VersionedName o2) { + return ComparisonChain.start() + .compare(o1.getSymbolicName(), o2.getSymbolicName(), NaturalOrderComparator.INSTANCE) + .compare(o2.getOsgiVersionString(), o1.getOsgiVersionString(), VersionComparator.INSTANCE) + .compare(o2.getVersionString(), o1.getVersionString(), VersionComparator.INSTANCE) + .result(); + } + } } From 206f744a84e93b364fbd912927ca6849a29873a3 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 22 Sep 2017 11:42:02 +0100 Subject: [PATCH 2/5] address most PR comments still to do: supertypes as query param, and restructure to hang both of catalog (remove POST /catalog/bundles) --- .../core/mgmt/entitlement/Entitlements.java | 1 + .../apache/brooklyn/rest/api/BundleApi.java | 14 +++++++-- .../org/apache/brooklyn/rest/api/TypeApi.java | 7 +++-- .../brooklyn/rest/domain/BundleSummary.java | 7 +++++ .../brooklyn/rest/domain/TypeDetail.java | 3 +- .../brooklyn/rest/domain/TypeSummary.java | 12 +++++-- .../rest/resources/BundleResource.java | 25 ++++++++------- .../brooklyn/rest/resources/TypeResource.java | 2 ++ .../rest/transform/TypeTransformer.java | 31 ++++++++++--------- 9 files changed, 68 insertions(+), 34 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/Entitlements.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/Entitlements.java index 60d998e01f..f7b5f724cc 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/Entitlements.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/Entitlements.java @@ -60,6 +60,7 @@ public class Entitlements { // ------------------- individual permissions + // TODO applies to bundles and registered types; should pass object or probably better add more entitlements? public static EntitlementClass SEE_CATALOG_ITEM = new BasicEntitlementClassDefinition("catalog.see", String.class); public static EntitlementClass ADD_CATALOG_ITEM = new BasicEntitlementClassDefinition("catalog.add", Object.class); public static EntitlementClass MODIFY_CATALOG_ITEM = new BasicEntitlementClassDefinition("catalog.modify", StringAndArgument.class); diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java index 2c2eeae401..6bf46eaa68 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java @@ -54,9 +54,13 @@ public interface BundleApi { responseContainer = "List") public List list( @ApiParam(name = "versions", value = "Whether to list 'latest' for each symbolic-name or 'all' versions", - required = false, defaultValue = "latest") + required = false, defaultValue = "latest") @QueryParam("versions") - String versions); + String versions, + @ApiParam(name = "detail", value = "Whether to include types and other detail info, default 'false'", + required = false, defaultValue = "false") + @QueryParam("detail") + boolean detail); @Path("/{symbolicName}") @GET @@ -66,7 +70,11 @@ public List list( public List listVersions( @ApiParam(name = "symbolicName", value = "Bundle name to query", required = true) @PathParam("symbolicName") - String symbolicName); + String symbolicName, + @ApiParam(name = "detail", value = "Whether to include types and other detail info, default 'false'", + required = false, defaultValue = "false") + @QueryParam("detail") + boolean detail); @Path("/{symbolicName}/{version}") @GET diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java index c39d5eff81..f8167b50f6 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java @@ -33,6 +33,8 @@ import org.apache.brooklyn.rest.domain.TypeDetail; import org.apache.brooklyn.rest.domain.TypeSummary; +import com.google.common.annotations.Beta; + import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -41,6 +43,7 @@ @Api("Types") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) +@Beta public interface TypeApi { @GET @@ -52,9 +55,9 @@ public List list( required = false, defaultValue = "latest") @QueryParam("versions") String versions, - @ApiParam(name = "regex", value = "Regular expression to search for") + @ApiParam(name = "regex", value = "Regular expression to search for (in name and description)") @QueryParam("regex") @DefaultValue("") String regex, - @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") + @ApiParam(name = "fragment", value = "Substring case-insensitive to search for (in name and description)") @QueryParam("fragment") @DefaultValue("") String fragment); @Path("/{nameOrAlias}") diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleSummary.java index f14ee0721d..c8f442be0a 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleSummary.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleSummary.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; +import org.apache.brooklyn.api.typereg.ManagedBundle; import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; @@ -35,6 +36,12 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.google.common.collect.ComparisonChain; +/** Summary info of {@link ManagedBundle} bundles in the catalog providing types, + * essentially the symbolic name and version. + * Extra fields listing the types may be added. + *

+ * These are comparable in alpha-then-version order with most recent preferring non-snapshot versions first, + * as per {@link VersionComparator}. */ public class BundleSummary implements Comparable { private final String symbolicName; diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeDetail.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeDetail.java index 2f9e32fe91..0fafcbeb03 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeDetail.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeDetail.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +/** As {@link TypeSummary} but including plan information. */ public class TypeDetail extends TypeSummary { public static class TypeImplementationPlanSummary { @@ -42,7 +43,7 @@ public Object getData() { return data; } } - private final TypeImplementationPlanSummary plan; + private TypeImplementationPlanSummary plan; /** Constructor for JSON deserialization use only. */ TypeDetail() { diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeSummary.java index 0a045d4df0..70942cfb57 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeSummary.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeSummary.java @@ -34,7 +34,10 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.google.common.collect.ComparisonChain; +import com.google.common.collect.ImmutableSet; +/** Summary info of {@link RegisteredType} items in the catalog. + * See {@link TypeDetail} for further information. */ public class TypeSummary implements Comparable { private final String symbolicName; @@ -52,7 +55,7 @@ public class TypeSummary implements Comparable { @JsonInclude(value=Include.NON_EMPTY) private Set aliases; @JsonInclude(value=Include.NON_EMPTY) - private Set supertypes; + private Set supertypes; @JsonInclude(value=Include.NON_EMPTY) private Set tags; @@ -88,7 +91,10 @@ public TypeSummary(RegisteredType t) { iconUrl = t.getIconUrl(); aliases = t.getAliases(); - supertypes = t.getSuperTypes(); + supertypes = ImmutableSet.copyOf(t.getSuperTypes().stream().map(s -> + s instanceof Class ? ((Class)s).getName() : + s instanceof RegisteredType ? ((RegisteredType)s).getId() : + s.toString()).iterator()); tags = t.getTags(); deprecated = t.isDeprecated(); @@ -172,7 +178,7 @@ public Set getAliases() { return aliases; } - public Set getSupertypes() { + public Set getSupertypes() { return supertypes; } diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java index a2a6532fdf..b4664ea960 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java @@ -58,16 +58,18 @@ public class BundleResource extends AbstractBrooklynRestResource implements Bund private static final String LATEST = "latest"; @Override - public List list(String versions) { - return list(TypeResource.isLatestOnly(versions, true), Predicates.alwaysTrue()); + public List list(String versions, boolean detail) { + return list(Predicates.alwaysTrue(), TypeResource.isLatestOnly(versions, true), detail); } - private List list(boolean onlyLatest, Predicate symbolicNameFilter) { + private List list(Predicate symbolicNameFilter, boolean onlyLatest, boolean detail) { Map bundles = new TreeMap<>(VersionedNameComparator.INSTANCE); for (ManagedBundle b: ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles().values()) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, b.getId())) { + continue; + } if (symbolicNameFilter.apply(b.getSymbolicName())) { - // TODO entitlements for bundles VersionedName key = onlyLatest ? new VersionedName(b.getSymbolicName(), LATEST) : b.getVersionedName(); ManagedBundle oldBundle = bundles.get(key); if (oldBundle==null || oldBundle.getVersionedName().compareTo(b.getVersionedName()) > 0) { @@ -75,20 +77,20 @@ private List list(boolean onlyLatest, Predicate symbolicN } } } - return toBundleSummary(bundles.values()); + return toBundleSummary(bundles.values(), detail); } - private List toBundleSummary(Iterable sortedItems) { + private List toBundleSummary(Iterable sortedItems, boolean detail) { List result = MutableList.of(); for (ManagedBundle t: sortedItems) { - result.add(TypeTransformer.bundleSummary(brooklyn(), t, ui.getBaseUriBuilder(), mgmt())); + result.add(TypeTransformer.bundleSummary(brooklyn(), t, ui.getBaseUriBuilder(), mgmt(), detail)); } return result; } @Override - public List listVersions(String symbolicName) { - return list(false, Predicates.equalTo(symbolicName)); + public List listVersions(String symbolicName, boolean detail) { + return list(Predicates.equalTo(symbolicName), false, detail); } @Override @@ -98,12 +100,13 @@ public BundleSummary detail(String symbolicName, String version) { } protected ManagedBundle lookup(String symbolicName, String version) { - // TODO entitlements for bundles - ManagedBundle b = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundle(new VersionedName(symbolicName, version)); if (b==null) { throw WebResourceUtils.notFound("Bundle with id '%s:%s' not found", symbolicName, version); } + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, b.getId())) { + throw WebResourceUtils.notFound("Bundle with id '%s:%s' not found", symbolicName, version); + } return b; } diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java index 86de57d62f..52627e3066 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java @@ -48,6 +48,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.annotations.Beta; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.FluentIterable; @@ -55,6 +56,7 @@ import com.google.common.io.Files; @HaHotStateRequired +@Beta public class TypeResource extends AbstractBrooklynRestResource implements TypeApi { private static final Logger log = LoggerFactory.getLogger(TypeResource.class); diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java index fb3fdd3388..4c9ccb23e7 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java @@ -79,10 +79,10 @@ public static TypeDetail detail(BrooklynRestResourceUtils b, RegisteredType item } private static T embellish(T result, RegisteredType item, boolean detail, BrooklynRestResourceUtils b, UriBuilder ub) { - result.getExtraFields().put("links", makeLinks(item, ub)); + result.setExtraField("links", makeLinks(item, ub)); if (RegisteredTypes.isTemplate(item)) { - result.getExtraFields().put("template", true); + result.setExtraField("template", true); } if (item.getIconUrl()!=null) { result.setIconUrl(tidyIconLink(b, item, item.getIconUrl(), ub)); @@ -103,7 +103,7 @@ private static T embellish(T result, RegisteredType item config.add(EntityTransformer.adjunctConfigSummary(input)); } - result.getExtraFields().put("config", config); + result.setExtraField("config", config); } catch (Exception e) { Exceptions.propagateIfFatal(e); log.trace("Unable to create spec for "+item+": "+e, e); @@ -111,7 +111,7 @@ private static T embellish(T result, RegisteredType item } else if (RegisteredTypes.isSubtypeOf(item, Location.class)) { // TODO include config on location specs? (wasn't done previously so not needed, but good for completeness) - result.getExtraFields().put("config", Collections.emptyMap()); + result.setExtraField("config", Collections.emptyMap()); } } return result; @@ -135,9 +135,9 @@ protected static void embellishEntity(T result, Register for (Effector x: type.getEffectors()) effectors.add(EffectorTransformer.effectorSummaryForCatalog(x)); - result.getExtraFields().put("config", config); - result.getExtraFields().put("sensors", sensors); - result.getExtraFields().put("effectors", effectors); + result.setExtraField("config", config); + result.setExtraField("sensors", sensors); + result.setExtraField("effectors", effectors); } catch (Exception e) { Exceptions.propagateIfFatal(e); @@ -152,19 +152,22 @@ protected static void embellishEntity(T result, Register } } - public static BundleSummary bundleSummary(BrooklynRestResourceUtils brooklyn, ManagedBundle b, UriBuilder baseUriBuilder, ManagementContext mgmt) { + public static BundleSummary bundleSummary(BrooklynRestResourceUtils brooklyn, ManagedBundle b, UriBuilder baseUriBuilder, ManagementContext mgmt, boolean detail) { BundleSummary result = new BundleSummary(b); - for (RegisteredType t: mgmt.getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(b))) { - result.addType(summary(brooklyn, t, baseUriBuilder)); + if (detail) { + result.setExtraField("osgiVersion", b.getOsgiVersionString()); + result.setExtraField("checksum", b.getChecksum()); + } + if (detail) { + for (RegisteredType t: mgmt.getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(b))) { + result.addType(summary(brooklyn, t, baseUriBuilder)); + } } return result; } public static BundleSummary bundleDetails(BrooklynRestResourceUtils brooklyn, ManagedBundle b, UriBuilder baseUriBuilder, ManagementContext mgmt) { - BundleSummary result = bundleSummary(brooklyn, b, baseUriBuilder, mgmt); - result.getExtraFields().put("osgiVersion", b.getOsgiVersionString()); - result.getExtraFields().put("checksum", b.getChecksum()); - return result; + return bundleSummary(brooklyn, b, baseUriBuilder, mgmt, true); } public static BundleInstallationRestResult bundleInstallationResult(OsgiBundleInstallationResult in, ManagementContext mgmt, BrooklynRestResourceUtils brooklynU, UriInfo ui) { From 05ad45e7ff1f78f655d7d3aa03aae557e70b8fb5 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 22 Sep 2017 13:07:19 +0100 Subject: [PATCH 3/5] remaining PR comments move /bundles and /types under /catalog, remove subtype, remove autodetect create in POST /catalog/bundles --- .../apache/brooklyn/rest/api/BundleApi.java | 23 +-- .../apache/brooklyn/rest/api/CatalogApi.java | 56 ++++++- .../apache/brooklyn/rest/api/SubtypeApi.java | 96 ------------ .../org/apache/brooklyn/rest/api/TypeApi.java | 9 +- .../apache/brooklyn/rest/BrooklynRestApi.java | 2 - .../rest/resources/BundleResource.java | 20 --- .../rest/resources/SubtypeResource.java | 82 ---------- .../brooklyn/rest/resources/TypeResource.java | 21 ++- ...t.java => BundleAndTypeResourcesTest.java} | 141 +++++++++--------- 9 files changed, 154 insertions(+), 296 deletions(-) delete mode 100644 rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SubtypeApi.java delete mode 100644 rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SubtypeResource.java rename rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/{BundleAndTypeAndSubtypeResourcesTest.java => BundleAndTypeResourcesTest.java} (87%) diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java index 6bf46eaa68..2eb5ed8a96 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java @@ -42,8 +42,8 @@ import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; -@Path("/bundles") -@Api("Bundles") +@Path("/catalog/bundles") +@Api("Catalog Bundles") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public interface BundleApi { @@ -144,23 +144,4 @@ public Response createFromArchive( @QueryParam("force") @DefaultValue("false") Boolean force); - @POST - @Consumes // anything (if doesn't match other methods with specific content types - @ApiOperation( - value = "Adds types to the registry from the given item, autodetecting type as ZIP/JAR or BOM YAML", - response = BundleInstallationRestResult.class - ) - @ApiResponses(value = { - @ApiResponse(code = 400, message = "Error processing the given archive, or the catalog.bom is invalid"), - @ApiResponse(code = 201, message = "Catalog items added successfully") - }) - public Response createAutodetecting( - @ApiParam( - name = "item", - value = "Item to install, as JAR/ZIP or Catalog YAML (autodetected)", - required = true) - byte[] item, - @ApiParam(name = "force", value = "Whether to forcibly remove it, even if in use and/or errors", required = false, defaultValue = "false") - @QueryParam("force") @DefaultValue("false") - Boolean force); } diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java index 3cbae5baa2..cb4298f8be 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java @@ -47,9 +47,6 @@ import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; -/** @deprecated since 0.12.0 use /bundle, /type, and /subtype */ -// but we will probably keep this around for a while as many places use it -@Deprecated @Path("/catalog") @Api("Catalog") @Consumes(MediaType.APPLICATION_JSON) @@ -156,6 +153,9 @@ public Response createFromUpload( @QueryParam("forceUpdate") @DefaultValue("false") boolean forceUpdate); + /** @deprecated since 0.12.0 delete the bundle via DELETE /catalog/bundles/xxx */ + // but we will probably keep this around for a while as many places use it + @Deprecated @DELETE @Path("/applications/{symbolicName}/{version}") @ApiOperation( @@ -173,6 +173,9 @@ public void deleteApplication( @ApiParam(name = "version", value = "The version identifier of the application or template to delete", required = true) @PathParam("version") String version) throws Exception; + /** @deprecated since 0.12.0 delete the bundle via DELETE /catalog/bundles/xxx */ + // but we will probably keep this around for a while as many places use it + @Deprecated @DELETE @Path("/entities/{symbolicName}/{version}") @ApiOperation( @@ -190,6 +193,9 @@ public void deleteEntity( @ApiParam(name = "version", value = "The version identifier of the entity or template to delete", required = true) @PathParam("version") String version) throws Exception; + /** @deprecated since 0.12.0 delete the bundle via DELETE /catalog/bundles/xxx */ + // but we will probably keep this around for a while as many places use it + @Deprecated @DELETE @Path("/policies/{policyId}/{version}") @ApiOperation( @@ -206,6 +212,9 @@ public void deletePolicy( @ApiParam(name = "version", value = "The version identifier of the policy to delete", required = true) @PathParam("version") String version) throws Exception; + /** @deprecated since 0.12.0 delete the bundle via DELETE /catalog/bundles/xxx */ + // but we will probably keep this around for a while as many places use it + @Deprecated @DELETE @Path("/locations/{locationId}/{version}") @ApiOperation( @@ -223,6 +232,9 @@ public void deleteLocation( @ApiParam(name = "version", value = "The version identifier of the location to delete", required = true) @PathParam("version") String version) throws Exception; + /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + // but we will probably keep this around for a while as many places use it + @Deprecated @GET @Path("/entities") @ApiOperation(value = "List available entity types optionally matching a query", @@ -237,6 +249,9 @@ public List listEntities( @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions); // bad name - it is just templates + /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + // but we will probably keep this around for a while as many places use it + @Deprecated @GET @Path("/applications") @ApiOperation(value = "Fetch a list of templates (for applications) optionally matching a query", @@ -250,6 +265,9 @@ public List listApplications( @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)") @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions); + /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + // but we will probably keep this around for a while as many places use it + @Deprecated @GET @Path("/entities/{symbolicName}/{version}") @ApiOperation( @@ -268,6 +286,9 @@ public CatalogEntitySummary getEntity( @ApiParam(name = "version", value = "The version identifier of the entity or template to retrieve", required = true) @PathParam("version") String version) throws Exception; + /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + // but we will probably keep this around for a while as many places use it + @Deprecated @GET @Path("/applications/{symbolicName}/{version}") @ApiOperation( @@ -286,6 +307,9 @@ public CatalogEntitySummary getApplication( @ApiParam(name = "version", value = "The version identifier of the application to retrieve", required = true) @PathParam("version") String version) throws Exception; + /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + // but we will probably keep this around for a while as many places use it + @Deprecated @GET @Path("/policies") @ApiOperation(value = "List available policies optionally matching a query", @@ -299,6 +323,9 @@ public List listPolicies( @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)") @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions); + /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + // but we will probably keep this around for a while as many places use it + @Deprecated @GET @Path("/policies/{policyId}/{version}") @ApiOperation( @@ -316,6 +343,9 @@ public CatalogPolicySummary getPolicy( @ApiParam(name = "version", value = "The version identifier of the application to retrieve", required = true) @PathParam("version") String version) throws Exception; + /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + // but we will probably keep this around for a while as many places use it + @Deprecated @GET @Path("/locations") @ApiOperation(value = "List available locations optionally matching a query", @@ -329,6 +359,9 @@ public List listLocations( @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)") @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions); + /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + // but we will probably keep this around for a while as many places use it + @Deprecated @GET @Path("/locations/{locationId}/{version}") @ApiOperation( @@ -364,6 +397,10 @@ public Response getIcon( @ApiParam(name = "version", value = "version identifier of catalog item (application, entity, policy, location)", required=true) @PathParam("version") String version); + /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=...; + * deprecation/disabling needs to be done in the bundle, and we might support deprecating/disabling bundles */ + // but we will probably keep this around for a while as many places use it + @Deprecated @POST @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM, MediaType.TEXT_PLAIN}) @ApiResponses(value = { @@ -376,6 +413,10 @@ public void setDeprecated( @ApiParam(name = "deprecated", value = "Whether or not the catalog item is deprecated", required = true) boolean deprecated); + /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=...; + * deprecation/disabling needs to be done in the bundle, and we might support deprecating/disabling bundles */ + // but we will probably keep this around for a while as many places use it + @Deprecated @POST @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM, MediaType.TEXT_PLAIN}) @ApiResponses(value = { @@ -388,6 +429,9 @@ public void setDisabled( @ApiParam(name = "disabled", value = "Whether or not the catalog item is disabled", required = true) boolean disabled); + /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + // but we will probably keep this around for a while as many places use it + @Deprecated @GET @Path("/enrichers") @ApiOperation(value = "List available enrichers types optionally matching a query", @@ -401,6 +445,9 @@ public List listEnrichers( @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)") @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions); + /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + // but we will probably keep this around for a while as many places use it + @Deprecated @GET @Path("/enrichers/{enricherId}/{version}") @ApiOperation(value = "Fetch an enricher's definition from the catalog", @@ -415,6 +462,9 @@ public CatalogEnricherSummary getEnricher( @ApiParam(name = "version", value = "The version identifier of the enricher to retrieve", required = true) @PathParam("version") String version) throws Exception; + /** @deprecated since 0.12.0 delete the bundle via DELETE /catalog/bundles/xxx */ + // but we will probably keep this around for a while as many places use it + @Deprecated @DELETE @Path("/enrichers/{enricherId}/{version}") @ApiOperation(value = "Deletes a specific version of an enricher's definition from the catalog") diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SubtypeApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SubtypeApi.java deleted file mode 100644 index 01890c56c7..0000000000 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SubtypeApi.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 org.apache.brooklyn.rest.api; - -import java.util.List; - -import javax.ws.rs.Consumes; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; - -import org.apache.brooklyn.rest.domain.TypeSummary; - -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; - -@Path("/subtypes") -@Api("Subtypes") -@Consumes(MediaType.APPLICATION_JSON) -@Produces(MediaType.APPLICATION_JSON) -public interface SubtypeApi { - - @Path("/{supertype}") - @GET - @ApiOperation(value = "Get all known types which declare the given argument as a supertype", - response = TypeSummary.class, responseContainer = "List") - public List list( - @ApiParam(name = "supertype", value = "Supertype to query", required = true) - @PathParam("supertype") - String supertype, - @ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") - @QueryParam("versions") - String versions, - @ApiParam(name = "regex", value = "Regular expression to search for") - @QueryParam("regex") @DefaultValue("") String regex, - @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") - @QueryParam("fragment") @DefaultValue("") String fragment); - - // conveniences for common items where internally it uses java class name - // caller can of course use /subtypes/org.apache.brooklyn.api.Entity - - @GET @Path("/application") - @ApiOperation(value = "Get all applications", response = TypeSummary.class, responseContainer = "List") - public List listApplications(@ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") @QueryParam("versions") String versions, - @ApiParam(name = "regex", value = "Regular expression to search for") @QueryParam("regex") @DefaultValue("") String regex, - @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") @QueryParam("fragment") @DefaultValue("") String fragment); - - @GET @Path("/entity") - @ApiOperation(value = "Get all entities", response = TypeSummary.class, responseContainer = "List") - public List listEntities(@ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") @QueryParam("versions") String versions, - @ApiParam(name = "regex", value = "Regular expression to search for") @QueryParam("regex") @DefaultValue("") String regex, - @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") @QueryParam("fragment") @DefaultValue("") String fragment); - - @GET @Path("/policy") - @ApiOperation(value = "Get all policies", response = TypeSummary.class, responseContainer = "List") - public List listPolicies(@ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") @QueryParam("versions") String versions, - @ApiParam(name = "regex", value = "Regular expression to search for") @QueryParam("regex") @DefaultValue("") String regex, - @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") @QueryParam("fragment") @DefaultValue("") String fragment); - - @GET @Path("/enricher") - @ApiOperation(value = "Get all enrichers", response = TypeSummary.class, responseContainer = "List") - public List listEnrichers(@ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") @QueryParam("versions") String versions, - @ApiParam(name = "regex", value = "Regular expression to search for") @QueryParam("regex") @DefaultValue("") String regex, - @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") @QueryParam("fragment") @DefaultValue("") String fragment); - - @GET @Path("/location") - // note some (deprecated) locations are only available on location manager - @ApiOperation(value = "Get all locations stored in the registry", response = TypeSummary.class, responseContainer = "List") - public List listLocations(@ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") @QueryParam("versions") String versions, - @ApiParam(name = "regex", value = "Regular expression to search for") @QueryParam("regex") @DefaultValue("") String regex, - @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") @QueryParam("fragment") @DefaultValue("") String fragment); - - // in future could have others, eg: tasks, feeds, etc - -} diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java index f8167b50f6..914dce2c6f 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java @@ -39,8 +39,8 @@ import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -@Path("/types") -@Api("Types") +@Path("/catalog/types") +@Api("Catalog Types") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Beta @@ -51,8 +51,11 @@ public interface TypeApi { response = TypeSummary.class, responseContainer = "List") public List list( + @ApiParam(name = "supertype", value = "Supertype to require (beta, currently intended only for 'entity', 'policy', 'enricher', and 'location')", required = false) + @QueryParam("supertype") + String supertype, @ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", - required = false, defaultValue = "latest") + required = false, defaultValue = "latest") @QueryParam("versions") String versions, @ApiParam(name = "regex", value = "Regular expression to search for (in name and description)") diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java index b3eb4e48cd..e172127d33 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java @@ -38,7 +38,6 @@ import org.apache.brooklyn.rest.resources.ScriptResource; import org.apache.brooklyn.rest.resources.SensorResource; import org.apache.brooklyn.rest.resources.ServerResource; -import org.apache.brooklyn.rest.resources.SubtypeResource; import org.apache.brooklyn.rest.resources.TypeResource; import org.apache.brooklyn.rest.resources.UsageResource; import org.apache.brooklyn.rest.util.DefaultExceptionMapper; @@ -56,7 +55,6 @@ public static Iterable getBrooklynRestResources() resources.add(new LocationResource()); resources.add(new CatalogResource()); resources.add(new TypeResource()); - resources.add(new SubtypeResource()); resources.add(new BundleResource()); resources.add(new ApplicationResource()); resources.add(new EntityResource()); diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java index b4664ea960..24fe3ca0fc 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java @@ -19,7 +19,6 @@ package org.apache.brooklyn.rest.resources; import java.io.ByteArrayInputStream; -import java.io.InputStreamReader; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -44,7 +43,6 @@ import org.apache.brooklyn.util.exceptions.ReferenceWithError; import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.osgi.VersionedName.VersionedNameComparator; -import org.apache.brooklyn.util.yaml.Yamls; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -168,22 +166,4 @@ public Response createFromArchive(byte[] zipInput, Boolean force) { return Response.status(Status.CREATED).entity( resultR ).build(); } - @Override - public Response createAutodetecting(byte[] item, Boolean force) { - Throwable yamlException = null; - try { - MutableList.copyOf( Yamls.parseAll(new InputStreamReader(new ByteArrayInputStream(item))) ); - } catch (Exception e) { - Exceptions.propagateIfFatal(e); - yamlException = e; - } - - if (yamlException==null) { - // treat as yaml if it parsed - return createFromYaml(new String(item), force); - } - - return createFromArchive(item, force); - } } - diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SubtypeResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SubtypeResource.java deleted file mode 100644 index 12716b36ee..0000000000 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SubtypeResource.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 org.apache.brooklyn.rest.resources; - -import java.util.List; - -import org.apache.brooklyn.api.entity.Application; -import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.api.location.Location; -import org.apache.brooklyn.api.policy.Policy; -import org.apache.brooklyn.api.sensor.Enricher; -import org.apache.brooklyn.api.typereg.RegisteredType; -import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; -import org.apache.brooklyn.core.typereg.RegisteredTypes; -import org.apache.brooklyn.rest.api.SubtypeApi; -import org.apache.brooklyn.rest.domain.TypeSummary; -import org.apache.brooklyn.rest.filter.HaHotStateRequired; -import org.apache.brooklyn.util.collections.MutableList; -import org.apache.brooklyn.util.text.StringPredicates; -import org.apache.brooklyn.util.text.Strings; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Predicate; -import com.google.common.base.Predicates; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableList; - -@HaHotStateRequired -public class SubtypeResource extends AbstractBrooklynRestResource implements SubtypeApi { - - @SuppressWarnings("unused") - private static final Logger log = LoggerFactory.getLogger(SubtypeResource.class); - - @Override - public List list(String supertype, String versions, String regex, String fragment) { - List> filters = MutableList.>of() - .append(RegisteredTypePredicates.entitledToSee(mgmt())) - .append(RegisteredTypePredicates.subtypeOf(supertype)); - if (TypeResource.isLatestOnly(versions, true)) { - // TODO inefficient - does n^2 comparisons where n is sufficient - // create RegisteredTypes.filterBestVersions to do a list after the initial parse - // (and javadoc in predicate method below) - filters.add(RegisteredTypePredicates.isBestVersion(mgmt())); - } - if (Strings.isNonEmpty(regex)) { - filters.add(RegisteredTypePredicates.nameOrAlias(StringPredicates.containsRegex(regex))); - } - if (Strings.isNonEmpty(fragment)) { - filters.add(RegisteredTypePredicates.nameOrAlias(StringPredicates.containsLiteralIgnoreCase(fragment))); - } - Predicate filter = Predicates.and(filters); - ImmutableList sortedItems = - FluentIterable.from(brooklyn().getTypeRegistry().getMatching(filter)) - .toSortedList(RegisteredTypes.RegisteredTypeNameThenBestFirstComparator.INSTANCE); - return TypeResource.toTypeSummary(brooklyn(), sortedItems, ui.getBaseUriBuilder()); - } - - @Override public List listApplications(String versions, String regex, String fragment) { return list(Application.class.getName(), versions, regex, fragment); } - @Override public List listEntities(String versions, String regex, String fragment) { return list(Entity.class.getName(), versions, regex, fragment); } - @Override public List listPolicies(String versions, String regex, String fragment) { return list(Policy.class.getName(), versions, regex, fragment); } - @Override public List listEnrichers(String versions, String regex, String fragment) { return list(Enricher.class.getName(), versions, regex, fragment); } - @Override public List listLocations(String versions, String regex, String fragment) { return list(Location.class.getName(), versions, regex, fragment); } - -} - diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java index 52627e3066..cf9f40f724 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java @@ -27,6 +27,11 @@ import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriBuilder; +import org.apache.brooklyn.api.entity.Application; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.policy.Policy; +import org.apache.brooklyn.api.sensor.Enricher; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; @@ -75,9 +80,23 @@ static boolean isLatestOnly(String versions, boolean defaultValue) { } @Override - public List list(String versions, String regex, String fragment) { + public List list(String supertype, String versions, String regex, String fragment) { List> filters = MutableList.>of() .append(RegisteredTypePredicates.entitledToSee(mgmt())); + if (Strings.isNonBlank(supertype)) { + // rewrite certain well known ones + // (in future this should happen automatically as Entity.class should be known as user-friendly name 'entity') + if ("entity".equals(supertype)) supertype = Entity.class.getName(); + else if ("enricher".equals(supertype)) supertype = Enricher.class.getName(); + else if ("policy".equals(supertype)) supertype = Policy.class.getName(); + else if ("location".equals(supertype)) supertype = Location.class.getName(); + // TODO application probably isn't at all interesting; keep it for backward compatibility, + // and meanwhile sort out things like "template" vs "quick launch" + // (probably adding tags on the API) + else if ("application".equals(supertype)) supertype = Application.class.getName(); + + filters.add(RegisteredTypePredicates.subtypeOf(supertype)); + } if (TypeResource.isLatestOnly(versions, true)) { // TODO inefficient - does n^2 comparisons where n is sufficient // create RegisteredTypes.filterBestVersions to do a list after the initial parse diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/BundleAndTypeAndSubtypeResourcesTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/BundleAndTypeResourcesTest.java similarity index 87% rename from rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/BundleAndTypeAndSubtypeResourcesTest.java rename to rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/BundleAndTypeResourcesTest.java index 03578f1794..87fa3782b1 100644 --- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/BundleAndTypeAndSubtypeResourcesTest.java +++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/BundleAndTypeResourcesTest.java @@ -89,9 +89,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; -public class BundleAndTypeAndSubtypeResourcesTest extends BrooklynRestResourceTest { +public class BundleAndTypeResourcesTest extends BrooklynRestResourceTest { - private static final Logger log = LoggerFactory.getLogger(BundleAndTypeAndSubtypeResourcesTest.class); + private static final Logger log = LoggerFactory.getLogger(BundleAndTypeResourcesTest.class); private static String TEST_VERSION = "0.1.2"; private static String TEST_LASTEST_VERSION = "0.1.3"; @@ -143,7 +143,8 @@ public void testRegisterCustomEntityTopLevelSyntaxWithBundleWhereEntityIsFromCor " item:", " type: org.apache.brooklyn.core.test.entity.TestEntity"); - Response response = client().path("/bundles") + Response response = client().path("/catalog/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/yaml") .post(yaml); assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode()); @@ -152,7 +153,7 @@ public void testRegisterCustomEntityTopLevelSyntaxWithBundleWhereEntityIsFromCor TypeSummary installedItem = installed.getTypes().get(symbolicName+":"+TEST_VERSION); Assert.assertNotNull(installedItem, ""+installed.getTypes()); - TypeDetail entityItem = client().path("/types/"+symbolicName + "/" + TEST_VERSION) + TypeDetail entityItem = client().path("/catalog/types/"+symbolicName + "/" + TEST_VERSION) .get(TypeDetail.class); Assert.assertEquals(new TypeSummary(entityItem), installedItem); @@ -163,11 +164,11 @@ public void testRegisterCustomEntityTopLevelSyntaxWithBundleWhereEntityIsFromCor assertEquals(entityItem.getVersion(), TEST_VERSION); // also check it's included in various lists - List list1 = client().path("/types/"+symbolicName).get(new GenericType>() {}); + List list1 = client().path("/catalog/types/"+symbolicName).get(new GenericType>() {}); assertEquals(list1, MutableList.of(installedItem)); - List list2 = client().path("/types").get(new GenericType>() {}); + List list2 = client().path("/catalog/types").get(new GenericType>() {}); Assert.assertTrue(list2.contains(installedItem), ""+list2); - List list3 = client().path("/subtypes/entity").get(new GenericType>() {}); + List list3 = client().path("/catalog/types").query("supertype", "entity").get(new GenericType>() {}); Assert.assertTrue(list3.contains(installedItem), ""+list3); // and internally let's check we have libraries @@ -178,7 +179,7 @@ public void testRegisterCustomEntityTopLevelSyntaxWithBundleWhereEntityIsFromCor assertEquals(Iterables.getOnlyElement(libs).getUrl(), bundleUrl); // now let's check other things on the item - URI expectedIconUrl = URI.create(getEndpointAddress() + "/types/" + symbolicName + "/" + entityItem.getVersion()+"/icon").normalize(); + URI expectedIconUrl = URI.create(getEndpointAddress() + "/catalog/types/" + symbolicName + "/" + entityItem.getVersion()+"/icon").normalize(); assertEquals(entityItem.getDisplayName(), "My Catalog App"); assertEquals(entityItem.getDescription(), "My description"); assertEquals(entityItem.getIconUrl(), expectedIconUrl.getPath()); @@ -196,7 +197,7 @@ public void testRegisterCustomEntityTopLevelSyntaxWithBundleWhereEntityIsFromCor } } - byte[] iconData = client().path("/types/" + symbolicName + "/" + TEST_VERSION+"/icon").get(byte[].class); + byte[] iconData = client().path("/catalog/types/" + symbolicName + "/" + TEST_VERSION+"/icon").get(byte[].class); assertEquals(iconData.length, 43); } @@ -220,7 +221,8 @@ public void testRegisterOsgiPolicyTopLevelSyntax() { " item:", " type: " + policyType); - TypeSummary installedItem = Iterables.getOnlyElement( client().path("/bundles") + TypeSummary installedItem = Iterables.getOnlyElement( client().path("/catalog/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/yaml") .post(yaml, BundleInstallationRestResult.class).getTypes().values() ); assertEquals(installedItem.getSymbolicName(), symbolicName); @@ -230,27 +232,27 @@ public void testRegisterOsgiPolicyTopLevelSyntax() { @Test public void testFilterListOfEntitiesByName() { - List entities = client().path("/types") + List entities = client().path("/catalog/types") .query("fragment", "vaNIllasOFTWAREpROCESS").get(new GenericType>() {}); log.info("Matching entities: " + entities); assertEquals(entities.size(), 1); - entities = client().path("/subtypes/entity") + entities = client().path("/catalog/types").query("supertype", "entity") .query("fragment", "vaNIllasOFTWAREpROCESS").get(new GenericType>() {}); log.info("Matching entities: " + entities); assertEquals(entities.size(), 1); - List entities2 = client().path("/types") + List entities2 = client().path("/catalog/types") .query("regex", "[Vv]an.[alS]+oftware\\w+").get(new GenericType>() {}); assertEquals(entities2.size(), 1); assertEquals(entities, entities2); - entities = client().path("/subtypes/entity") + entities = client().path("/catalog/types").query("supertype", "entity") .query("fragment", "bweqQzZ").get(new GenericType>() {}); Asserts.assertSize(entities, 0); - entities = client().path("/subtypes/entity") + entities = client().path("/catalog/types").query("supertype", "entity") .query("regex", "bweq+z+").get(new GenericType>() {}); Asserts.assertSize(entities, 0); } @@ -259,7 +261,7 @@ public void testFilterListOfEntitiesByName() { public void testGetCatalogEntityIconDetails() throws IOException { String catalogItemId = "testGetCatalogEntityIconDetails"; addTestCatalogItemAsEntity(catalogItemId); - Response response = client().path(URI.create("/types/" + catalogItemId + "/" + TEST_VERSION + "/icon")) + Response response = client().path(URI.create("/catalog/types/" + catalogItemId + "/" + TEST_VERSION + "/icon")) .get(); response.bufferEntity(); Assert.assertEquals(response.getStatus(), 200); @@ -285,12 +287,12 @@ private void addTestCatalogItem(String catalogItemId, String itemType, String ve " item:", " type: " + service); - client().path("/bundles").post(yaml); + client().path("/catalog/bundles").header(HttpHeaders.CONTENT_TYPE, "application/yaml").post(yaml); } @Test public void testListPolicies() { - Set policies = client().path("/subtypes/policy") + Set policies = client().path("/catalog/types").query("supertype", "policy") .get(new GenericType>() {}); assertTrue(policies.size() > 0); @@ -317,7 +319,8 @@ public void testLocationAddGetAndRemove() { " type: " + locationType); // Create location item - Map items = client().path("/bundles") + Map items = client().path("/catalog/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/yaml") .post(yaml, BundleInstallationRestResult.class).getTypes(); TypeSummary locationItem = Iterables.getOnlyElement(items.values()); @@ -325,11 +328,11 @@ public void testLocationAddGetAndRemove() { assertEquals(locationItem.getVersion(), TEST_VERSION); // Retrieve location item - TypeDetail location = client().path("/types/"+symbolicName+"/"+TEST_VERSION).get(TypeDetail.class); + TypeDetail location = client().path("/catalog/types/"+symbolicName+"/"+TEST_VERSION).get(TypeDetail.class); assertEquals(location.getSymbolicName(), symbolicName); // Retrieve all locations - Set locations = client().path("/subtypes/location") + Set locations = client().path("/catalog/types").query("supertype", "location") .get(new GenericType>() {}); boolean found = false; for (TypeSummary contender : locations) { @@ -341,21 +344,21 @@ public void testLocationAddGetAndRemove() { Assert.assertTrue(found, "contenders="+locations); // Delete - Response deleteResponse = client().path("/bundles/"+locationItem.getContainingBundle().replaceAll(":", "/")) + Response deleteResponse = client().path("/catalog/bundles/"+locationItem.getContainingBundle().replaceAll(":", "/")) .delete(); assertEquals(deleteResponse.getStatus(), Response.Status.OK.getStatusCode()); BundleInstallationRestResult deletionResponse = deleteResponse.readEntity(BundleInstallationRestResult.class); Assert.assertEquals(deletionResponse.getBundle(), symbolicName+":"+TEST_VERSION); Assert.assertEquals(deletionResponse.getTypes().keySet(), MutableSet.of(symbolicName+":"+TEST_VERSION)); - Response getPostDeleteResponse = client().path("/types/"+symbolicName+"/"+TEST_VERSION) + Response getPostDeleteResponse = client().path("/catalog/types/"+symbolicName+"/"+TEST_VERSION) .get(); assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode()); } @Test public void testListEnrichers() { - Set enrichers = client().path("/subtypes/enricher") + Set enrichers = client().path("/catalog/types").query("supertype", "enricher") .get(new GenericType>() {}); assertTrue(enrichers.size() > 0); @@ -382,7 +385,8 @@ public void testEnricherAddGet() { " type: " + enricherType); // Create location item - Map items = client().path("/bundles") + Map items = client().path("/catalog/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/yaml") .post(yaml, BundleInstallationRestResult.class).getTypes(); TypeSummary enricherItem = Iterables.getOnlyElement(items.values()); @@ -390,12 +394,12 @@ public void testEnricherAddGet() { assertEquals(enricherItem.getVersion(), TEST_VERSION); // Retrieve location item - TypeSummary enricher = client().path("/types/"+symbolicName+"/"+TEST_VERSION) + TypeSummary enricher = client().path("/catalog/types/"+symbolicName+"/"+TEST_VERSION) .get(TypeSummary.class); assertEquals(enricher.getSymbolicName(), symbolicName); // Retrieve all locations - Set enrichers = client().path("/subtypes/enricher") + Set enrichers = client().path("/catalog/types").query("supertype", "enricher") .get(new GenericType>() {}); boolean found = false; for (TypeSummary contender : enrichers) { @@ -428,7 +432,8 @@ public void testRegisterOsgiEnricherTopLevelSyntax() { " item:", " type: " + enricherType); - TypeSummary installedItem = Iterables.getOnlyElement( client().path("/bundles") + TypeSummary installedItem = Iterables.getOnlyElement( client().path("/catalog/bundles") + .header(HttpHeaders.CONTENT_TYPE, "application/yaml") .post(yaml, BundleInstallationRestResult.class).getTypes().values() ); assertEquals(installedItem.getSymbolicName(), symbolicName); @@ -448,24 +453,24 @@ public void testDeleteCustomEntityFromCatalog() { " item:", " type: org.apache.brooklyn.core.test.entity.TestEntity"); - client().path("/bundles") + client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/yaml") .post(yaml); - BundleSummary getInstalledBundle = client().path("/bundles/"+symbolicName+"/"+TEST_VERSION) + BundleSummary getInstalledBundle = client().path("/catalog/bundles/"+symbolicName+"/"+TEST_VERSION) .get(BundleSummary.class); assertEquals(getInstalledBundle.getSymbolicName(), symbolicName); assertEquals(getInstalledBundle.getVersion(), TEST_VERSION); Asserts.assertNotNull(getInstalledBundle.getTypes(), "expected 'types' in: "+getInstalledBundle.getExtraFields()); Asserts.assertStringContains(""+getInstalledBundle.getTypes(), "My Catalog App"); - Response deleteResponse = client().path("/bundles/"+symbolicName+"/"+TEST_VERSION) + Response deleteResponse = client().path("/catalog/bundles/"+symbolicName+"/"+TEST_VERSION) .delete(); assertEquals(deleteResponse.getStatus(), Response.Status.OK.getStatusCode()); // contents of delete tested in delete location method - Response getPostDeleteResponse = client().path("/bundles/"+symbolicName+"/"+TEST_VERSION) + Response getPostDeleteResponse = client().path("/catalog/bundles/"+symbolicName+"/"+TEST_VERSION) .get(); assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode()); } @@ -485,7 +490,7 @@ private void addCatalogItemWithInvalidBundleUrl(String bundleUrl) { " item:", " type: org.apache.brooklyn.core.test.entity.TestEntity"); - Response response = client().path("/bundles") + Response response = client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-yaml") .post(yaml); @@ -513,7 +518,7 @@ public void testAddMissingItem() { public void testInvalidArchive() throws Exception { File f = Os.newTempFile("osgi", "zip"); - Response response = client().path("/bundles") + Response response = client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") .post(Streams.readFully(new FileInputStream(f))); @@ -525,7 +530,7 @@ public void testInvalidArchive() throws Exception { public void testArchiveWithoutBom() throws Exception { File f = createZip(ImmutableMap.of()); - Response response = client().path("/bundles") + Response response = client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") .post(Streams.readFully(new FileInputStream(f))); @@ -544,7 +549,7 @@ public void testArchiveWithoutBundleAndVersion() throws Exception { " item:", " type: org.apache.brooklyn.core.test.entity.TestEntity"))); - Response response = client().path("/bundles") + Response response = client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") .post(Streams.readFully(new FileInputStream(f))); @@ -564,7 +569,7 @@ public void testArchiveWithoutBundle() throws Exception { " item:", " type: org.apache.brooklyn.core.test.entity.TestEntity"))); - Response response = client().path("/bundles") + Response response = client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") .post(Streams.readFully(new FileInputStream(f))); @@ -585,7 +590,7 @@ public void testArchiveWithoutVersion() throws Exception { " item:", " type: org.apache.brooklyn.core.test.entity.TestEntity"))); - Response response = client().path("/bundles") + Response response = client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") .post(Streams.readFully(new FileInputStream(f))); @@ -617,7 +622,7 @@ public void testJarWithoutMatchingBundle() throws Exception { "Bundle-Version: " + version, "Bundle-ManifestVersion: " + version))); - Response response = client().path("/bundles") + Response response = client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-jar") .post(Streams.readFully(new FileInputStream(f))); @@ -651,7 +656,7 @@ public void testJarWithoutMatchingVersion() throws Exception { "Bundle-Version: " + wrongVersion, "Bundle-ManifestVersion: " + wrongVersion))); - Response response = client().path("/bundles") + Response response = client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-jar") .post(Streams.readFully(new FileInputStream(f))); @@ -685,13 +690,13 @@ public void testOsgiBundleWithBom() throws Exception { f = bm.copyAdding(f, MutableMap.of(new ZipEntry("catalog.bom"), (InputStream) new ByteArrayInputStream(bom.getBytes()))); - Response response = client().path("/bundles") + Response response = client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-jar") .post(Streams.readFully(new FileInputStream(f))); assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode()); - TypeSummary entityItem = client().path("/types/"+symbolicName + "/" + version) + TypeSummary entityItem = client().path("/catalog/types/"+symbolicName + "/" + version) .get(TypeSummary.class); assertEquals(entityItem.getSymbolicName(), symbolicName); @@ -709,7 +714,7 @@ public void testOsgiBundleWithBom() throws Exception { assertEquals(lib.getSuppliedVersionString(), version); // now let's check other things on the item - URI expectedIconUrl = URI.create(getEndpointAddress() + "/types/" + symbolicName + "/" + entityItem.getVersion()+"/icon").normalize(); + URI expectedIconUrl = URI.create(getEndpointAddress() + "/catalog/types/" + symbolicName + "/" + entityItem.getVersion()+"/icon").normalize(); assertEquals(entityItem.getDisplayName(), "My Catalog App"); assertEquals(entityItem.getDescription(), "My description"); assertEquals(entityItem.getIconUrl(), expectedIconUrl.getPath()); @@ -727,7 +732,7 @@ public void testOsgiBundleWithBom() throws Exception { } } - byte[] iconData = client().path("/types/" + symbolicName + "/" + version + "/icon").get(byte[].class); + byte[] iconData = client().path("/catalog/types/" + symbolicName + "/" + version + "/icon").get(byte[].class); assertEquals(iconData.length, 43); } @@ -757,14 +762,14 @@ public void testOsgiBundleWithBomNotInBrooklynNamespace() throws Exception { f = bm.copyAdding(f, MutableMap.of(new ZipEntry("catalog.bom"), (InputStream) new ByteArrayInputStream(bom.getBytes()))); - Response response = client().path("/bundles") + Response response = client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") .post(Streams.readFully(new FileInputStream(f))); assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode()); - TypeDetail entityItem = client().path("/types/"+symbolicName + "/" + version) + TypeDetail entityItem = client().path("/catalog/types/"+symbolicName + "/" + version) .get(TypeDetail.class); Assert.assertNotNull(entityItem.getPlan().getData()); @@ -786,7 +791,7 @@ public void testOsgiBundleWithBomNotInBrooklynNamespace() throws Exception { // now let's check other things on the item assertEquals(entityItem.getDescription(), "My description"); - URI expectedIconUrl = URI.create(getEndpointAddress() + "/types/" + symbolicName + "/" + entityItem.getVersion() + "/icon").normalize(); + URI expectedIconUrl = URI.create(getEndpointAddress() + "/catalog/types/" + symbolicName + "/" + entityItem.getVersion() + "/icon").normalize(); assertEquals(entityItem.getIconUrl(), expectedIconUrl.getPath()); assertEquals(item.getIconUrl(), "classpath:" + iconPath); @@ -799,7 +804,7 @@ public void testOsgiBundleWithBomNotInBrooklynNamespace() throws Exception { assertTrue(actualInterfaces.containsAll(expectedInterfaces), "actual="+actualInterfaces); } - byte[] iconData = client().path("/types/" + symbolicName + "/" + version + "/icon").get(byte[].class); + byte[] iconData = client().path("/catalog/types/" + symbolicName + "/" + version + "/icon").get(byte[].class); assertEquals(iconData.length, 43); // Check that the catalog item is useable (i.e. can deploy the entity) @@ -863,7 +868,7 @@ public void testGetOnlyLatestApplication() { addTestCatalogItem(symbolicName, itemType, TEST_VERSION, serviceType); addTestCatalogItem(symbolicName, itemType, TEST_LASTEST_VERSION, serviceType); - TypeSummary application = client().path("/types/" + symbolicName + "/latest") + TypeSummary application = client().path("/catalog/types/" + symbolicName + "/latest") .get(TypeSummary.class); assertEquals(application.getVersion(), TEST_LASTEST_VERSION); } @@ -875,11 +880,11 @@ public void testGetOnlyLatestDifferentCases() { String symbolicName = "latest.catalog.application.id"; - TypeSummary application = client().path("/types/" + symbolicName + "/LaTeSt") + TypeSummary application = client().path("/catalog/types/" + symbolicName + "/LaTeSt") .get(TypeSummary.class); assertEquals(application.getVersion(), TEST_LASTEST_VERSION); - application = client().path("/types/" + symbolicName + "/LATEST") + application = client().path("/catalog/types/" + symbolicName + "/LATEST") .get(TypeSummary.class); assertEquals(application.getVersion(), TEST_LASTEST_VERSION); } @@ -893,7 +898,7 @@ public void testGetOnlyLatestEntity() { addTestCatalogItem(symbolicName, itemType, TEST_VERSION, serviceType); addTestCatalogItem(symbolicName, itemType, TEST_LASTEST_VERSION, serviceType); - TypeSummary application = client().path("/types/" + symbolicName + "/latest") + TypeSummary application = client().path("/catalog/types/" + symbolicName + "/latest") .get(TypeSummary.class); assertEquals(application.getVersion(), TEST_LASTEST_VERSION); } @@ -907,7 +912,7 @@ public void testGetOnlyLatestLocation() { addTestCatalogItem(symbolicName, itemType, TEST_VERSION, serviceType); addTestCatalogItem(symbolicName, itemType, TEST_LASTEST_VERSION, serviceType); - TypeSummary application = client().path("/types/" + symbolicName + "/latest") + TypeSummary application = client().path("/catalog/types/" + symbolicName + "/latest") .get(TypeSummary.class); assertEquals(application.getVersion(), TEST_LASTEST_VERSION); } @@ -945,22 +950,22 @@ public void testForceUpdateForYAML() { " item:", " type: org.apache.brooklyn.core.test.entity.TestEntity"); - client().path("/bundles").post(initialYaml); + client().path("/catalog/bundles").header(HttpHeaders.CONTENT_TYPE, "application/yaml").post(initialYaml); - TypeDetail initialApplication = client().path("/types/" + symbolicName + "/" + TEST_VERSION) + TypeDetail initialApplication = client().path("/catalog/types/" + symbolicName + "/" + TEST_VERSION) .get(TypeDetail.class); assertEquals(initialApplication.getDisplayName(), initialName); assertEquals(initialApplication.getDescription(), initialDescription); - Response invalidResponse = client().path("/bundles").post(updatedYaml); + Response invalidResponse = client().path("/catalog/bundles").header(HttpHeaders.CONTENT_TYPE, "application/yaml").post(updatedYaml); assertEquals(invalidResponse.getStatus(), Response.Status.BAD_REQUEST.getStatusCode()); - Response validResponse = client().path("/bundles").query("force", true).post(updatedYaml); + Response validResponse = client().path("/catalog/bundles").query("force", true).header(HttpHeaders.CONTENT_TYPE, "application/yaml").post(updatedYaml); assertEquals(validResponse.getStatus(), Response.Status.CREATED.getStatusCode()); - TypeSummary application = client().path("/types/" + symbolicName + "/" + TEST_VERSION) + TypeSummary application = client().path("/catalog/types/" + symbolicName + "/" + TEST_VERSION) .get(TypeSummary.class); assertEquals(application.getDisplayName(), updatedName); assertEquals(application.getDescription(), updatedDescription); @@ -997,29 +1002,29 @@ public void testForceUpdateForZip() throws Exception { " item:", " type: org.apache.brooklyn.core.test.entity.TestEntity"))); - client().path("/bundles") + client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") .post(Streams.readFully(new FileInputStream(initialZip))); - TypeSummary initialEntity = client().path("/types/" + symbolicName + "/" + TEST_VERSION) + TypeSummary initialEntity = client().path("/catalog/types/" + symbolicName + "/" + TEST_VERSION) .get(TypeSummary.class); assertEquals(initialEntity.getDisplayName(), initialName); assertEquals(initialEntity.getDescription(), initialDescription); - Response invalidResponse = client().path("/bundles") + Response invalidResponse = client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") .post(Streams.readFully(new FileInputStream(updatedZip))); assertEquals(invalidResponse.getStatus(), Response.Status.BAD_REQUEST.getStatusCode()); - Response validResponse = client().path("/bundles") + Response validResponse = client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-zip") .query("force", true) .post(Streams.readFully(new FileInputStream(updatedZip))); assertEquals(validResponse.getStatus(), Response.Status.CREATED.getStatusCode()); - TypeSummary entity = client().path("/types/" + symbolicName + "/" + TEST_VERSION) + TypeSummary entity = client().path("/catalog/types/" + symbolicName + "/" + TEST_VERSION) .get(TypeSummary.class); assertEquals(entity.getDisplayName(), updatedName); assertEquals(entity.getDescription(), updatedDescription); @@ -1056,29 +1061,29 @@ public void testForceUpdateForJar() throws Exception { " item:", " type: org.apache.brooklyn.core.test.entity.TestEntity"))); - client().path("/bundles") + client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-jar") .post(Streams.readFully(new FileInputStream(initialJar))); - TypeSummary initialEntity = client().path("/types/" + symbolicName + "/" + TEST_VERSION) + TypeSummary initialEntity = client().path("/catalog/types/" + symbolicName + "/" + TEST_VERSION) .get(TypeSummary.class); assertEquals(initialEntity.getDisplayName(), initialName); assertEquals(initialEntity.getDescription(), initialDescription); - Response invalidResponse = client().path("/bundles") + Response invalidResponse = client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-jar") .post(Streams.readFully(new FileInputStream(updatedJar))); assertEquals(invalidResponse.getStatus(), Response.Status.BAD_REQUEST.getStatusCode()); - Response validResponse = client().path("/bundles") + Response validResponse = client().path("/catalog/bundles") .header(HttpHeaders.CONTENT_TYPE, "application/x-jar") .query("force", true) .post(Streams.readFully(new FileInputStream(updatedJar))); assertEquals(validResponse.getStatus(), Response.Status.CREATED.getStatusCode()); - TypeSummary entity = client().path("/types/" + symbolicName + "/" + TEST_VERSION) + TypeSummary entity = client().path("/catalog/types/" + symbolicName + "/" + TEST_VERSION) .get(TypeSummary.class); assertEquals(entity.getDisplayName(), updatedName); assertEquals(entity.getDescription(), updatedDescription); From 8e693305d50a82ffa4567e900ed2686187ccf7fb Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 22 Sep 2017 13:19:11 +0100 Subject: [PATCH 4/5] deprecation is since 0.13.0 now (12 is out) --- .../brooklyn/api/catalog/BrooklynCatalog.java | 4 +++ .../mgmt/ha/OsgiBundleInstallationResult.java | 4 +-- .../core/typereg/RegisteredTypes.java | 2 +- .../apache/brooklyn/rest/api/CatalogApi.java | 34 +++++++++---------- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java index fa1d55e844..e03adbf271 100644 --- a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java +++ b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java @@ -53,6 +53,7 @@ public interface BrooklynCatalog { /** As {@link #getCatalogItem(String, String)} but only looking in legacy catalog * @deprecated since 0.12.0 only provided to allow TypeRegistry to see the legacy items */ + @Deprecated CatalogItem getCatalogItemLegacy(String symbolicName, String version); /** @return Deletes the item with the given {@link CatalogItem#getSymbolicName() @@ -70,6 +71,7 @@ public interface BrooklynCatalog { /** As non-legacy method but only looking in legacy catalog * @deprecated since 0.12.0 only provided to allow TypeRegistry to see the legacy items */ + @Deprecated CatalogItem getCatalogItemLegacy(Class type, String symbolicName, String version); /** @return All items in the catalog @@ -84,10 +86,12 @@ public interface BrooklynCatalog { /** convenience for filtering items in the catalog; see CatalogPredicates for useful filters * @deprecated since 0.12.0 use {@link BrooklynTypeRegistry} instead */ + @Deprecated Iterable> getCatalogItems(Predicate> filter); /** As non-legacy method but only looking in legacy catalog * @deprecated since 0.12.0 only provided to allow TypeRegistry to see the legacy items */ + @Deprecated Iterable> getCatalogItemsLegacy(Predicate> filter); /** persists the catalog item to the object store, if persistence is enabled */ diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java index d65f105145..05221a6bd2 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java @@ -61,7 +61,7 @@ public enum ResultCode { public boolean isError() { return isError; } } final List typesInstalled = MutableList.of(); - /** @deprecated since 0.12.0 use {@link #typesInstalled} */ + /** @deprecated since 0.13.0 use {@link #typesInstalled} */ @Deprecated private final List catalogItemsInstalled = MutableList.of(); @@ -80,7 +80,7 @@ public ResultCode getCode() { public List getTypesInstalled() { return typesInstalled; } - /** @deprecated since 0.12.0 use {@link #getTypesInstalled()} */ + /** @deprecated since 0.13.0 use {@link #getTypesInstalled()} */ @Deprecated public List getCatalogItemsInstalled() { return ImmutableList.copyOf(catalogItemsInstalled); diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java index 9e6d6103d5..49ea861233 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java @@ -446,7 +446,7 @@ public static boolean isAnyTypeOrSuper(Set candidateTypes, Predicate listEntities( @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions); // bad name - it is just templates - /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + /** @deprecated since 0.13.0 use /catalog/bundles and /catalog/types?supertype=... */ // but we will probably keep this around for a while as many places use it @Deprecated @GET @@ -265,7 +265,7 @@ public List listApplications( @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)") @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions); - /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + /** @deprecated since 0.13.0 use /catalog/bundles and /catalog/types?supertype=... */ // but we will probably keep this around for a while as many places use it @Deprecated @GET @@ -286,7 +286,7 @@ public CatalogEntitySummary getEntity( @ApiParam(name = "version", value = "The version identifier of the entity or template to retrieve", required = true) @PathParam("version") String version) throws Exception; - /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + /** @deprecated since 0.13.0 use /catalog/bundles and /catalog/types?supertype=... */ // but we will probably keep this around for a while as many places use it @Deprecated @GET @@ -307,7 +307,7 @@ public CatalogEntitySummary getApplication( @ApiParam(name = "version", value = "The version identifier of the application to retrieve", required = true) @PathParam("version") String version) throws Exception; - /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + /** @deprecated since 0.13.0 use /catalog/bundles and /catalog/types?supertype=... */ // but we will probably keep this around for a while as many places use it @Deprecated @GET @@ -323,7 +323,7 @@ public List listPolicies( @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)") @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions); - /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + /** @deprecated since 0.13.0 use /catalog/bundles and /catalog/types?supertype=... */ // but we will probably keep this around for a while as many places use it @Deprecated @GET @@ -343,7 +343,7 @@ public CatalogPolicySummary getPolicy( @ApiParam(name = "version", value = "The version identifier of the application to retrieve", required = true) @PathParam("version") String version) throws Exception; - /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + /** @deprecated since 0.13.0 use /catalog/bundles and /catalog/types?supertype=... */ // but we will probably keep this around for a while as many places use it @Deprecated @GET @@ -359,7 +359,7 @@ public List listLocations( @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)") @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions); - /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + /** @deprecated since 0.13.0 use /catalog/bundles and /catalog/types?supertype=... */ // but we will probably keep this around for a while as many places use it @Deprecated @GET @@ -397,7 +397,7 @@ public Response getIcon( @ApiParam(name = "version", value = "version identifier of catalog item (application, entity, policy, location)", required=true) @PathParam("version") String version); - /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=...; + /** @deprecated since 0.13.0 use /catalog/bundles and /catalog/types?supertype=...; * deprecation/disabling needs to be done in the bundle, and we might support deprecating/disabling bundles */ // but we will probably keep this around for a while as many places use it @Deprecated @@ -413,7 +413,7 @@ public void setDeprecated( @ApiParam(name = "deprecated", value = "Whether or not the catalog item is deprecated", required = true) boolean deprecated); - /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=...; + /** @deprecated since 0.13.0 use /catalog/bundles and /catalog/types?supertype=...; * deprecation/disabling needs to be done in the bundle, and we might support deprecating/disabling bundles */ // but we will probably keep this around for a while as many places use it @Deprecated @@ -429,7 +429,7 @@ public void setDisabled( @ApiParam(name = "disabled", value = "Whether or not the catalog item is disabled", required = true) boolean disabled); - /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + /** @deprecated since 0.13.0 use /catalog/bundles and /catalog/types?supertype=... */ // but we will probably keep this around for a while as many places use it @Deprecated @GET @@ -445,7 +445,7 @@ public List listEnrichers( @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)") @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions); - /** @deprecated since 0.12.0 use /catalog/bundles and /catalog/types?supertype=... */ + /** @deprecated since 0.13.0 use /catalog/bundles and /catalog/types?supertype=... */ // but we will probably keep this around for a while as many places use it @Deprecated @GET @@ -462,7 +462,7 @@ public CatalogEnricherSummary getEnricher( @ApiParam(name = "version", value = "The version identifier of the enricher to retrieve", required = true) @PathParam("version") String version) throws Exception; - /** @deprecated since 0.12.0 delete the bundle via DELETE /catalog/bundles/xxx */ + /** @deprecated since 0.13.0 delete the bundle via DELETE /catalog/bundles/xxx */ // but we will probably keep this around for a while as many places use it @Deprecated @DELETE From 54fb9c6a6f0ce07939df1ae417b4add17c9a786a Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 25 Sep 2017 10:00:01 +0100 Subject: [PATCH 5/5] address PR comments, mainly simplify ConfigSummary --- .../typereg/RegisteredTypePredicates.java | 78 +++++++++++++---- .../rest/domain/AdjunctConfigSummary.java | 85 ------------------- .../brooklyn/rest/domain/ConfigSummary.java | 66 ++++++++++---- .../rest/domain/EnricherConfigSummary.java | 12 +-- .../rest/domain/EntityConfigSummary.java | 67 +-------------- .../rest/domain/LocationConfigSummary.java | 40 ++------- .../rest/domain/PolicyConfigSummary.java | 30 +++++-- .../rest/transform/EntityTransformer.java | 11 +-- .../rest/transform/PolicyTransformer.java | 3 +- .../rest/transform/TypeTransformer.java | 6 +- 10 files changed, 159 insertions(+), 239 deletions(-) delete mode 100644 rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/AdjunctConfigSummary.java diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java index f0189e2a44..e6705efde4 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java @@ -20,6 +20,8 @@ import static com.google.common.base.Preconditions.checkNotNull; +import java.util.Set; + import javax.annotation.Nullable; import org.apache.brooklyn.api.entity.Application; @@ -220,40 +222,82 @@ public boolean apply(@Nullable RegisteredType item) { } public static Predicate anySuperType(final Predicate filter) { - return new AnySuperTypeMatches(filter); + return new AnySuperTypeSatisfies(filter); + } + /** True for any {@link RegisteredType} which has a type ancestor (or self) + * registered type which is equal to the given {@link RegisteredType} */ + public static Predicate subtypeOf(final RegisteredType filter) { + return anySuperType(Predicates.equalTo(filter)); } + /** True for any {@link RegisteredType} which has a type ancestor + * class which is equal to or a subtype of the given class */ public static Predicate subtypeOf(final Class filter) { // the assignableFrom predicate checks if this class is assignable from the subsequent *input*. // in other words, we're checking if any input is a subtype of this class - return anySuperType(new Predicate() { - @Override - public boolean apply(Object input) { - if (!(input instanceof Class)) return false; - return filter.isAssignableFrom((Class)input); - } - }); + return anySuperType(new IsSubtypeOfClass(filter)); } + private static class IsSubtypeOfClass implements Predicate { + private Class filter; + public IsSubtypeOfClass(Class filter) { + this.filter = filter; + } + @Override + public boolean apply(Object input) { + if (!(input instanceof Class)) return false; + return filter.isAssignableFrom((Class)input); + } + } + /** True for any {@link RegisteredType} which has a type ancestor (or self) + * whose registered type name or ID equals the string, or class name equals the string */ public static Predicate subtypeOf(final String filter) { - // the assignableFrom predicate checks if this class is assignable from the subsequent *input*. - // in other words, we're checking if any input is a subtype of this class - return anySuperType(new Predicate() { - @Override - public boolean apply(Object input) { - if (input instanceof Class) input = ((Class)input).getName(); - return filter.equals(input); + return anySuperType(new EqualsClassOrTypeName(filter)); + } + private static class EqualsClassOrTypeName implements Predicate { + private String filter; + public EqualsClassOrTypeName(String typeOrClassOrName) { + this.filter = typeOrClassOrName; + } + @Override + public boolean apply(Object input) { + if (input instanceof RegisteredType) { + return ((RegisteredType)input).getSymbolicName().equals(filter) || ((RegisteredType)input).getSymbolicName().equals(filter); } - }); + if (input instanceof Class) input = ((Class)input).getName(); + return filter.equals(input); + } } + /** @deprecated since 0.13.0 use {@link AnySuperTypeSatisfies}, kept for persistence compatibility */ + @SuppressWarnings("unused") + @Deprecated private static class AnySuperTypeMatches implements Predicate { + private final Predicate> filter; + + private AnySuperTypeMatches(Predicate> filter) { + this.filter = filter; + } + @Override + public boolean apply(@Nullable RegisteredType item) { + if (item==null) return false; + + Set candidateTypes = item.getSuperTypes(); + for (Object st: candidateTypes) { + if (st instanceof Class && filter.apply((Class)st)) return true; + } + return false; + } + } + + private static class AnySuperTypeSatisfies implements Predicate { private final Predicate filter; - private AnySuperTypeMatches(Predicate filter) { + private AnySuperTypeSatisfies(Predicate filter) { this.filter = filter; } @Override public boolean apply(@Nullable RegisteredType item) { if (item==null) return false; + if (filter.apply(item)) return true; return RegisteredTypes.isAnyTypeOrSuper(item.getSuperTypes(), filter); } } diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/AdjunctConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/AdjunctConfigSummary.java deleted file mode 100644 index 4bba4b20fa..0000000000 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/AdjunctConfigSummary.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 org.apache.brooklyn.rest.domain; - -import java.net.URI; -import java.util.Map; -import java.util.Objects; - -import org.apache.brooklyn.config.ConfigKey; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.collect.ImmutableMap; - -public class AdjunctConfigSummary extends ConfigSummary { - - private static final long serialVersionUID = 4339330833863794513L; - - @JsonInclude(Include.NON_NULL) - private final Map links; - - // json deserialization - AdjunctConfigSummary() { - links = null; - } - - public AdjunctConfigSummary( - @JsonProperty("name") String name, - @JsonProperty("type") String type, - @JsonProperty("description") String description, - @JsonProperty("defaultValue") Object defaultValue, - @JsonProperty("reconfigurable") boolean reconfigurable, - @JsonProperty("links") Map links) { - super(name, type, description, defaultValue, reconfigurable, null, null, null); - this.links = (links == null) ? ImmutableMap.of() : ImmutableMap.copyOf(links); - } - - public AdjunctConfigSummary(ConfigKey config, String label, Double priority, Map links) { - super(config, label, priority); - this.links = links != null ? ImmutableMap.copyOf(links) : null; - } - - @Override - public Map getLinks() { - return links; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof AdjunctConfigSummary)) return false; - if (!super.equals(o)) return false; - AdjunctConfigSummary that = (AdjunctConfigSummary) o; - return Objects.equals(links, that.links); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), links); - } - - @Override - public String toString() { - return "EnricherConfigSummary{" + - "links=" + links + - '}'; - } -} diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java index 6d9ceb33d9..77bf5fdd25 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java @@ -29,16 +29,19 @@ import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.util.collections.Jsonya; +import org.apache.brooklyn.util.text.StringPredicates; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.base.Function; +import com.google.common.base.Predicates; import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -public abstract class ConfigSummary implements HasName, Serializable { +public class ConfigSummary implements HasName, Serializable { private static final long serialVersionUID = -2831796487073496730L; @@ -54,12 +57,19 @@ public abstract class ConfigSummary implements HasName, Serializable { private final String label; @JsonInclude(Include.NON_NULL) private final Double priority; - @JsonInclude(Include.NON_NULL) + @JsonInclude(Include.NON_EMPTY) private final List> possibleValues; + @JsonInclude(Include.NON_NULL) + private final Boolean pinned; + @JsonInclude(Include.NON_EMPTY) + private final List constraints; + + @JsonInclude(Include.NON_EMPTY) + private final Map links; // json deserialization ConfigSummary() { - this(null, null, null, null, false, null, null, null); + this(null, null, null, null, false, null, null, null, null, null, null); } protected ConfigSummary( @@ -70,7 +80,10 @@ protected ConfigSummary( @JsonProperty("reconfigurable") boolean reconfigurable, @JsonProperty("label") String label, @JsonProperty("priority") Double priority, - @JsonProperty("possibleValues") List> possibleValues) { + @JsonProperty("possibleValues") List> possibleValues, + @JsonProperty("pinned") Boolean pinned, + @JsonProperty("constraints") List constraints, + @JsonProperty("links") Map links) { this.name = name; this.type = type; this.description = description; @@ -79,14 +92,12 @@ protected ConfigSummary( this.label = label; this.priority = priority; this.possibleValues = possibleValues; + this.pinned = pinned; + this.constraints = (constraints == null) ? ImmutableList.of() : ImmutableList.copyOf(constraints); + this.links = (links == null) ? ImmutableMap.of() : ImmutableMap.copyOf(links); } - protected ConfigSummary(ConfigKey config) { - this(config, null, null); - } - - @SuppressWarnings("rawtypes") - protected ConfigSummary(ConfigKey config, String label, Double priority) { + public ConfigSummary(ConfigKey config, String label, Double priority, Boolean pinned, Map links) { this.name = config.getName(); this.description = config.getDescription(); this.reconfigurable = config.isReconfigurable(); @@ -97,15 +108,19 @@ protected ConfigSummary(ConfigKey config, String label, Double priority) { */ this.label = label; this.priority = priority; + this.pinned = pinned; + this.constraints = !config.getConstraint().equals(Predicates.alwaysTrue()) + ? ImmutableList.of((config.getConstraint().getClass().equals(StringPredicates.isNonBlank().getClass()) ? "required" : config.getConstraint().toString())) + : ImmutableList.of(); if (config.getType().isEnum()) { this.type = Enum.class.getName(); - this.defaultValue = (config.getDefaultValue() == null) ? null : ((Enum) config.getDefaultValue()).name(); + this.defaultValue = (config.getDefaultValue() == null) ? null : ((Enum) config.getDefaultValue()).name(); this.possibleValues = FluentIterable - .from(Arrays.asList((Enum[])(config.getType().getEnumConstants()))) - .transform(new Function>() { + .from(Arrays.asList((Enum[])(config.getType().getEnumConstants()))) + .transform(new Function, Map>() { @Nullable @Override - public Map apply(@Nullable Enum input) { + public Map apply(@Nullable Enum input) { return ImmutableMap.of( "value", input != null ? input.name() : null, "description", input != null ? input.toString() : null); @@ -116,6 +131,7 @@ public Map apply(@Nullable Enum input) { this.defaultValue = Jsonya.convertToJsonPrimitive(config.getDefaultValue()); this.possibleValues = null; } + this.links = (links == null) ? ImmutableMap.of() : ImmutableMap.copyOf(links); } @Override @@ -152,7 +168,17 @@ public List> getPossibleValues() { return possibleValues; } - public abstract Map getLinks(); + public Boolean isPinned() { + return pinned; + } + + public List getConstraints() { + return constraints; + } + + public Map getLinks() { + return links; + } @Override public boolean equals(Object o) { @@ -166,12 +192,16 @@ public boolean equals(Object o) { Objects.equals(description, that.description) && Objects.equals(label, that.label) && Objects.equals(priority, that.priority) && - Objects.equals(possibleValues, that.possibleValues); + Objects.equals(possibleValues, that.possibleValues) && + Objects.equals(pinned, that.pinned) && + Objects.equals(constraints, that.constraints) && + Objects.equals(links, that.links); } @Override public int hashCode() { - return Objects.hash(name, type, defaultValue, description, reconfigurable, label, priority, possibleValues); + return Objects.hash(name, type, defaultValue, description, reconfigurable, label, priority, + possibleValues, pinned, constraints); } @Override @@ -185,6 +215,8 @@ public String toString() { ", label='" + label + '\'' + ", priority=" + priority + ", possibleValues=" + possibleValues + + ", pinned=" + pinned + + ", constraints=" + constraints + '}'; } } diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EnricherConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EnricherConfigSummary.java index c868fb82c0..de8e4d3dc4 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EnricherConfigSummary.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EnricherConfigSummary.java @@ -23,8 +23,9 @@ import org.apache.brooklyn.config.ConfigKey; -// TODO remove? this class has no value over its super -public class EnricherConfigSummary extends AdjunctConfigSummary { +/** @deprecated since 0.13.0 no different to ConfigSummary, use that */ +@Deprecated +public class EnricherConfigSummary extends ConfigSummary { private static final long serialVersionUID = 4339330833863794513L; @@ -32,12 +33,7 @@ public class EnricherConfigSummary extends AdjunctConfigSummary { private EnricherConfigSummary() {} public EnricherConfigSummary(ConfigKey config, String label, Double priority, Map links) { - super(config, label, priority, links); - } - - public EnricherConfigSummary(String name, String type, String description, Object defaultValue, boolean reconfigurable, - Map links) { - super(name, type, description, defaultValue, reconfigurable, links); + super(config, label, priority, null, links); } } diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityConfigSummary.java index aeba294dbb..131ae65699 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityConfigSummary.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityConfigSummary.java @@ -21,30 +21,17 @@ import java.net.URI; import java.util.List; import java.util.Map; -import java.util.Objects; import org.apache.brooklyn.config.ConfigKey; -import org.apache.brooklyn.util.text.StringPredicates; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.google.common.base.Predicates; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; +/** @deprecated since 0.13.0 no different to {@link ConfigSummary}, use that */ +@Deprecated public class EntityConfigSummary extends ConfigSummary { private static final long serialVersionUID = -1336134336883426030L; - @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) - private final Boolean pinned; - - @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) - private final List constraints; - - @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) - private final Map links; - public EntityConfigSummary( @JsonProperty("name") String name, @JsonProperty("type") String type, @@ -57,57 +44,11 @@ public EntityConfigSummary( @JsonProperty("pinned") Boolean pinned, @JsonProperty("constraints") List constraints, @JsonProperty("links") Map links) { - super(name, type, description, defaultValue, reconfigurable, label, priority, possibleValues); - this.pinned = pinned; - this.constraints = (constraints == null) ? ImmutableList.of() : ImmutableList.copyOf(constraints); - this.links = (links == null) ? ImmutableMap.of() : ImmutableMap.copyOf(links); + super(name, type, description, defaultValue, reconfigurable, label, priority, possibleValues, pinned, constraints, links); } public EntityConfigSummary(ConfigKey config, String label, Double priority, Boolean pinned, Map links) { - super(config, label, priority); - this.pinned = pinned; - this.constraints = !config.getConstraint().equals(Predicates.alwaysTrue()) - ? ImmutableList.of((config.getConstraint().getClass().equals(StringPredicates.isNonBlank().getClass()) ? "required" : config.getConstraint().toString())) - : ImmutableList.of(); - this.links = links != null ? ImmutableMap.copyOf(links) : null; - } - - public Boolean isPinned() { - return pinned; - } - - public List getConstraints() { - return constraints; + super(config, label, priority, pinned, links); } - @Override - public Map getLinks() { - return links; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - EntityConfigSummary that = (EntityConfigSummary) o; - if (pinned != that.pinned) return false; - if (constraints != null ? !constraints.equals(that.constraints) : that.constraints != null) return false; - return links != null ? links.equals(that.links) : that.links == null; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), links); - } - - @Override - public String toString() { - return "EntityConfigSummary{" + - "name='" + getName() + '\'' + - ", type='" + getType() + '\'' + - ", description='" + getDescription() + '\'' + - "links=" + links + - '}'; - } } diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/LocationConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/LocationConfigSummary.java index 21d5861efc..240921bdd5 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/LocationConfigSummary.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/LocationConfigSummary.java @@ -21,18 +21,17 @@ import java.net.URI; import java.util.List; import java.util.Map; -import java.util.Objects; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.google.common.collect.ImmutableMap; +/** @deprecated since 0.13.0 no different to ConfigSummary, use that */ +@Deprecated public class LocationConfigSummary extends ConfigSummary { private static final long serialVersionUID = 2232321501735217002L; - @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) - private final Map links; + @SuppressWarnings("unused") // json deserialization + private LocationConfigSummary() {} public LocationConfigSummary( @JsonProperty("name") String name, @@ -43,34 +42,11 @@ public LocationConfigSummary( @JsonProperty("label") String label, @JsonProperty("priority") Double priority, @JsonProperty("possibleValues") List> possibleValues, + @JsonProperty("pinned") Boolean pinned, + @JsonProperty("constraints") List constraints, @JsonProperty("links") Map links) { - super(name, type, description, defaultValue, reconfigurable, label, priority, possibleValues); - this.links = (links == null) ? ImmutableMap.of() : ImmutableMap.copyOf(links); + super(name, type, description, defaultValue, reconfigurable, label, priority, possibleValues, + pinned, constraints, links); } - @Override - public Map getLinks() { - return links; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof LocationConfigSummary)) return false; - if (!super.equals(o)) return false; - LocationConfigSummary that = (LocationConfigSummary) o; - return Objects.equals(links, that.links); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), links); - } - - @Override - public String toString() { - return "LocationConfigSummary{" + - "links=" + links + - '}'; - } } diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java index 5dfb898bc4..338604514b 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java @@ -19,25 +19,39 @@ package org.apache.brooklyn.rest.domain; import java.net.URI; +import java.util.List; import java.util.Map; import org.apache.brooklyn.config.ConfigKey; -//TODO remove? this class has no value over its super -public class PolicyConfigSummary extends AdjunctConfigSummary { +import com.fasterxml.jackson.annotation.JsonProperty; + +/** @deprecated since 0.13.0 no different to ConfigSummary, use that */ +@Deprecated +public class PolicyConfigSummary extends ConfigSummary { private static final long serialVersionUID = 4339330833863794513L; @SuppressWarnings("unused") // json deserialization private PolicyConfigSummary() {} - public PolicyConfigSummary(ConfigKey config, String label, Double priority, Map links) { - super(config, label, priority, links); + public PolicyConfigSummary( + @JsonProperty("name") String name, + @JsonProperty("type") String type, + @JsonProperty("description") String description, + @JsonProperty("defaultValue") Object defaultValue, + @JsonProperty("reconfigurable") boolean reconfigurable, + @JsonProperty("label") String label, + @JsonProperty("priority") Double priority, + @JsonProperty("possibleValues") List> possibleValues, + @JsonProperty("pinned") Boolean pinned, + @JsonProperty("constraints") List constraints, + @JsonProperty("links") Map links) { + super(name, type, description, defaultValue, reconfigurable, label, priority, possibleValues, pinned, constraints, links); } - - public PolicyConfigSummary(String name, String type, String description, Object defaultValue, boolean reconfigurable, - Map links) { - super(name, type, description, defaultValue, reconfigurable, links); + + public PolicyConfigSummary(ConfigKey config, String label, Double priority, Map links) { + super(config, label, priority, null, links); } } diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java index 6bf3f19aa0..73b2831eef 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java @@ -41,7 +41,7 @@ import org.apache.brooklyn.rest.api.CatalogApi; import org.apache.brooklyn.rest.api.EntityApi; import org.apache.brooklyn.rest.api.EntityConfigApi; -import org.apache.brooklyn.rest.domain.AdjunctConfigSummary; +import org.apache.brooklyn.rest.domain.ConfigSummary; import org.apache.brooklyn.rest.domain.EnricherConfigSummary; import org.apache.brooklyn.rest.domain.EntityConfigSummary; import org.apache.brooklyn.rest.domain.EntitySummary; @@ -125,8 +125,8 @@ public static EntityConfigSummary entityConfigSummary(ConfigKey config, Strin return new EntityConfigSummary(config, label, priority, pinned, mapOfLinks); } - public static AdjunctConfigSummary adjunctConfigSummary(ConfigKey config, String label, Double priority, Map links) { - return new AdjunctConfigSummary(config, label, priority, links); + public static ConfigSummary configSummary(ConfigKey config, String label, Double priority, Boolean pinned, Map links) { + return new ConfigSummary(config, label, priority, pinned, links); } public static PolicyConfigSummary policyConfigSummary(ConfigKey config, String label, Double priority, Map links) { @@ -198,9 +198,10 @@ public static EntityConfigSummary entityConfigSummary(SpecParameter input, At return entityConfigSummary(input.getConfigKey(), input.getLabel(), priority, input.isPinned(), null); } - public static AdjunctConfigSummary adjunctConfigSummary(SpecParameter input) { + public static ConfigSummary configSummary(SpecParameter input) { + // could increment priority, or take from annotation, or introduce new field Double priority = input.isPinned() ? Double.valueOf(1d) : null; - return policyConfigSummary(input.getConfigKey(), input.getLabel(), priority, null); + return configSummary(input.getConfigKey(), input.getLabel(), priority, input.isPinned(), null); } public static PolicyConfigSummary policyConfigSummary(SpecParameter input) { diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java index 8dad949188..d6ba1d33fe 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java @@ -96,7 +96,8 @@ public static PolicyConfigSummary policyConfigSummary(BrooklynRestResourceUtils return new PolicyConfigSummary(config.getName(), config.getTypeName(), config.getDescription(), PolicyConfigResource.getStringValueForDisplay(utils, policy, config.getDefaultValue()), - config.isReconfigurable(), + config.isReconfigurable(), + null, null, null, null, null, links); } } diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java index 4c9ccb23e7..e37ee6abc1 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java @@ -50,9 +50,9 @@ import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.rest.api.TypeApi; -import org.apache.brooklyn.rest.domain.AdjunctConfigSummary; import org.apache.brooklyn.rest.domain.BundleInstallationRestResult; import org.apache.brooklyn.rest.domain.BundleSummary; +import org.apache.brooklyn.rest.domain.ConfigSummary; import org.apache.brooklyn.rest.domain.EffectorSummary; import org.apache.brooklyn.rest.domain.EntityConfigSummary; import org.apache.brooklyn.rest.domain.SensorSummary; @@ -96,11 +96,11 @@ private static T embellish(T result, RegisteredType item RegisteredTypes.isSubtypeOf(item, Policy.class) || RegisteredTypes.isSubtypeOf(item, Enricher.class) || RegisteredTypes.isSubtypeOf(item, Feed.class) ) { try { - Set config = Sets.newLinkedHashSet(); + Set config = Sets.newLinkedHashSet(); AbstractBrooklynObjectSpec spec = b.getTypeRegistry().createSpec(item, null, null); for (final SpecParameter input : spec.getParameters()){ - config.add(EntityTransformer.adjunctConfigSummary(input)); + config.add(EntityTransformer.configSummary(input)); } result.setExtraField("config", config);