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 75fe634376..65c2b88699 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 @@ -19,13 +19,16 @@ package org.apache.brooklyn.api.catalog; import java.util.Collection; +import java.util.Map; import java.util.NoSuchElementException; import javax.annotation.Nullable; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.RegisteredType; +import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Predicate; @@ -57,6 +60,7 @@ 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 */ @@ -92,6 +96,33 @@ public interface BrooklynCatalog { */ AbstractBrooklynObjectSpec peekSpec(CatalogItem item); + /** Adds the given registered types defined in a bundle's catalog BOM; + * no validation performed, so caller should do that subsequently after + * loading all potential inter-dependent types. + *

+ * Nothing is returned but caller can retrieve the results by searching the + * type registry for types with the same containing bundle. + */ + @Beta // method may move elsewhere + public void addTypesFromBundleBom(String yaml, ManagedBundle bundle, boolean forceUpdate); + + /** As {@link #validateType(RegisteredType)} but taking a set of types, returning a map whose keys are + * those types where validation failed, mapped to the collection of errors validating that type. + * An empty map result indicates no validation errors in the types passed in. + */ + @Beta // method may move elsewhere + public Map> validateTypes(Iterable typesToValidate); + + /** Performs YAML validation on the given type, returning a collection of errors. + * An empty result indicates no validation errors in the type passed in. + *

+ * Validation may be side-effecting in that it sets metadata and refines supertypes + * for the given registered type. + */ + @Beta // method may move elsewhere + Collection validateType(RegisteredType typeToValidate); + + /** * Adds an item (represented in yaml) to the catalog. * Fails if the same version exists in catalog. @@ -137,6 +168,9 @@ public interface BrooklynCatalog { */ Iterable> addItems(String yaml, boolean forceUpdate); + /** As {@link #addItems(String, ManagedBundle)} but exposing forcing option as per {@link #addItem(String, boolean)}. */ + Iterable> addItems(String yaml, ManagedBundle bundle, boolean forceUpdate); + /** * adds an item to the 'manual' catalog; * this does not update the classpath or have a record to the java Class diff --git a/api/src/main/java/org/apache/brooklyn/api/location/LocationRegistry.java b/api/src/main/java/org/apache/brooklyn/api/location/LocationRegistry.java index 7804afc082..bbf6f52fc3 100644 --- a/api/src/main/java/org/apache/brooklyn/api/location/LocationRegistry.java +++ b/api/src/main/java/org/apache/brooklyn/api/location/LocationRegistry.java @@ -27,6 +27,7 @@ import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.mgmt.LocationManager; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; import org.apache.brooklyn.util.guava.Maybe; /** @@ -37,19 +38,32 @@ public interface LocationRegistry { /** map of ID (possibly randomly generated) to the definition (spec, name, id, and props; - * where spec is the spec as defined, for instance possibly another named:xxx location) */ + * where spec is the spec as defined, for instance possibly another named:xxx location). + * optionally will also return all things from other sources where this can look up. */ + public Map getDefinedLocations(boolean includeThingsWeAreFacadeFor); + + /** @deprecated since 0.12.0 use {@link #getDefinedLocations(boolean)} passing true. + * some clients might only want the things actually defined here, which is cheaper. */ + @Deprecated public Map getDefinedLocations(); /** returns a LocationDefinition given its ID (usually a random string), or null if none */ public LocationDefinition getDefinedLocationById(String id); /** returns a LocationDefinition given its name (e.g. for named locations, supply the bit after the "named:" prefix), - * or null if none */ + * looking inthe {@link BrooklynTypeRegistry} for registered items, then in this list, or null if none */ public LocationDefinition getDefinedLocationByName(String name); - /** adds or updates the given defined location */ + /** as {@link #updateDefinedLocationNonPersisted(LocationDefinition)} + * @deprecated since 0.12.0 use {@link #updateDefinedLocationNonPersisted(LocationDefinition)}; + * it's exactly the same, just the name makes it clear */ + @Deprecated public void updateDefinedLocation(LocationDefinition l); + /** adds or updates the given defined location in this registry; note it is not persisted; + * callers should consider adding to the {@link BrooklynTypeRegistry} instead */ + public void updateDefinedLocationNonPersisted(LocationDefinition l); + /** removes the defined location from the registry (applications running there are unaffected) */ public void removeDefinedLocation(String id); diff --git a/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/BrooklynMementoPersister.java b/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/BrooklynMementoPersister.java index e3522004ff..4bd013e438 100644 --- a/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/BrooklynMementoPersister.java +++ b/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/BrooklynMementoPersister.java @@ -38,6 +38,7 @@ import org.apache.brooklyn.api.sensor.Enricher; import org.apache.brooklyn.api.sensor.Feed; import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.util.time.Duration; import com.google.common.annotations.Beta; diff --git a/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/CatalogItemMemento.java b/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/CatalogItemMemento.java index 0ab7bc852c..11b301acfd 100644 --- a/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/CatalogItemMemento.java +++ b/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/CatalogItemMemento.java @@ -30,6 +30,8 @@ public interface CatalogItemMemento extends Memento { String getSymbolicName(); + String getContainingBundle(); + String getIconUrl(); String getVersion(); diff --git a/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObjectType.java b/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObjectType.java index 60021f5bd0..f4a175648c 100644 --- a/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObjectType.java +++ b/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObjectType.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.api.objs; import org.apache.brooklyn.api.catalog.CatalogItem; +import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; @@ -72,10 +73,37 @@ public Class getInterfaceType() { } public static BrooklynObjectType of(BrooklynObject instance) { - for (BrooklynObjectType t: values()) { - if (t.getInterfaceType()!=null && t.getInterfaceType().isInstance(instance)) - return t; + if (instance!=null) { + for (BrooklynObjectType t: values()) { + if (t.getInterfaceType()!=null && t.getInterfaceType().isInstance(instance)) + return t; + } } return UNKNOWN; } + + public static BrooklynObjectType of(Class objectTypeOrSpecType) { + if (objectTypeOrSpecType!=null) { + for (BrooklynObjectType t: values()) { + if (t.getInterfaceType()!=null && t.getInterfaceType().isAssignableFrom(objectTypeOrSpecType)) + return t; + if (t.getSpecType()!=null && t.getSpecType().isAssignableFrom(objectTypeOrSpecType)) + return t; + } + } + return UNKNOWN; + } + + public static BrooklynObjectType of(CatalogItemType t) { + if (t==null) return null; + switch (t) { + case ENRICHER: return BrooklynObjectType.ENRICHER; + case ENTITY: return BrooklynObjectType.ENTITY; + case LOCATION: return BrooklynObjectType.LOCATION; + case POLICY: return BrooklynObjectType.POLICY; + case TEMPLATE: return BrooklynObjectType.ENTITY; + default: return BrooklynObjectType.UNKNOWN; + } + } + } \ No newline at end of file diff --git a/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java b/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java index 5b15c75e95..4d5bbcae2e 100644 --- a/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java +++ b/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java @@ -36,7 +36,12 @@ public enum RegisteredTypeKind { * for the type registered (e.g. the {@link Entity} instance) */ SPEC, /** a registered type which will create the java type described */ - BEAN + BEAN, + /** a partially registered type which requires subsequent validation and changing the kind; + * until then, an item of this kind cannot be instantiated. + * items registered as templates (using a tag) may remain in this state + * if they do not resolve. */ + UNRESOLVED // note: additional kinds should have the visitor in core/RegisteredTypeKindVisitor updated // to flush out all places which want to implement support for all kinds } @@ -47,7 +52,7 @@ public enum RegisteredTypeKind { /** @return The item matching the given given * {@link RegisteredType#getSymbolicName() symbolicName} * and optionally {@link RegisteredType#getVersion()}, - * taking the best version if the version is null or a default marker, + * taking the best version if the version is null, blank, or a default marker, * returning null if no matches are found. */ RegisteredType get(String symbolicName, String version); /** as {@link #get(String, String)} but the given string here diff --git a/api/src/main/java/org/apache/brooklyn/api/typereg/ManagedBundle.java b/api/src/main/java/org/apache/brooklyn/api/typereg/ManagedBundle.java index 66ca59237e..49f5d7f7a8 100644 --- a/api/src/main/java/org/apache/brooklyn/api/typereg/ManagedBundle.java +++ b/api/src/main/java/org/apache/brooklyn/api/typereg/ManagedBundle.java @@ -27,9 +27,13 @@ public interface ManagedBundle extends BrooklynObject, Rebindable, OsgiBundleWit /** A URL-like thing that we can register with the OSGi framework * to uniquely identify this bundle-instance. - * This typically includes the unique {@link #getId()} of this item. */ + * This typically includes the unique {@link #getId()} of this item. + * This will not normally be a URL that can be loaded. */ String getOsgiUniqueUrl(); VersionedName getVersionedName(); + /** MD5 checksum of the contents of bundle as installed to OSGi */ + String getChecksum(); + } diff --git a/api/src/main/java/org/apache/brooklyn/api/typereg/OsgiBundleWithUrl.java b/api/src/main/java/org/apache/brooklyn/api/typereg/OsgiBundleWithUrl.java index 970fc68a27..55b30fc862 100644 --- a/api/src/main/java/org/apache/brooklyn/api/typereg/OsgiBundleWithUrl.java +++ b/api/src/main/java/org/apache/brooklyn/api/typereg/OsgiBundleWithUrl.java @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.api.typereg; +import org.apache.brooklyn.util.osgi.VersionedName; + import com.google.common.annotations.Beta; @Beta @@ -36,5 +38,8 @@ public interface OsgiBundleWithUrl { /** @return true if we have a name and version for this bundle; * false if not, e.g. if we only know the URL and we haven't loaded it yet */ public boolean isNameResolved(); + + /** @return the {@link VersionedName} for this bundle, or null if not available */ + public VersionedName getVersionedName(); } diff --git a/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredType.java b/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredType.java index c987473695..b7158e29aa 100644 --- a/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredType.java +++ b/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredType.java @@ -26,6 +26,7 @@ import org.apache.brooklyn.api.objs.BrooklynObject; import org.apache.brooklyn.api.objs.Identifiable; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; +import org.apache.brooklyn.util.osgi.VersionedName; import com.google.common.annotations.Beta; @@ -37,6 +38,9 @@ public interface RegisteredType extends Identifiable { String getSymbolicName(); String getVersion(); + + VersionedName getVersionedName(); + /** Bundle in symbolicname:id format where this type is defined */ // TODO would prefer this to be VersionedName if/when everything comes from OSGi bundles // unrevert 7260bf9cf3f3ebaaa790693e1b7217a81bef78a7 to start that, and adjust serialization @@ -111,6 +115,11 @@ public interface TypeImplementationPlan { String getPlanFormat(); /** data for the implementation; may be more specific */ Object getPlanData(); + + @Override boolean equals(Object obj); + @Override int hashCode(); } + @Override boolean equals(Object obj); + @Override int hashCode(); } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java index 375216b220..944cae99b9 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java @@ -160,18 +160,18 @@ public boolean canResolve() { public EntitySpec resolveSpec(Set encounteredRegisteredTypeSymbolicNames) { if (alreadyBuilt.getAndSet(true)) - throw new IllegalStateException("Spec can only be used once: "+this); + throw new IllegalStateException("Spec resolver can only be used once: "+this); EntitySpec spec = serviceSpecResolver.resolve(type, loader, encounteredRegisteredTypeSymbolicNames); if (spec == null) { // Try to provide some troubleshooting details final String msgDetails; - RegisteredType item = mgmt.getTypeRegistry().get(Strings.removeFromStart(type, "catalog:")); + RegisteredType item = mgmt.getTypeRegistry().get(Strings.removeAllFromStart(type, "catalog:", "brooklyn:")); String proto = Urls.getProtocol(type); if (item != null && encounteredRegisteredTypeSymbolicNames.contains(item.getSymbolicName())) { msgDetails = "Cycle between catalog items detected, starting from " + type + - ". Other catalog items being resolved up the stack are " + encounteredRegisteredTypeSymbolicNames + + ". Other catalog items being resolved recursively up the stack are " + encounteredRegisteredTypeSymbolicNames + ". Tried loading it as a Java class instead but failed."; } else if (proto != null) { if (BrooklynCampConstants.YAML_URL_PROTOCOL_WHITELIST.contains(proto)) { diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java index 55853d796e..f0116cf192 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java @@ -39,6 +39,7 @@ import org.apache.brooklyn.core.entity.AbstractEntity; import org.apache.brooklyn.core.mgmt.EntityManagementUtils; import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.core.typereg.UnsupportedTypePlanException; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.text.Strings; @@ -155,6 +156,9 @@ private static EntitySpec createEntitySpecFromServicesBlock(String plan, Broo return appSpec; } else { + if (at.getPlatformComponentTemplates()==null || at.getPlatformComponentTemplates().isEmpty()) { + throw new UnsupportedTypePlanException("No 'services' declared"); + } throw new IllegalStateException("Unable to instantiate YAML; invalid type or parameters in plan:\n"+plan); } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java index d8ec7fbd33..0901632726 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java @@ -25,6 +25,7 @@ import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.api.typereg.RegisteredType.TypeImplementationPlan; import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; import org.apache.brooklyn.core.typereg.AbstractFormatSpecificTypeImplementationPlan; import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer; import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan; @@ -47,6 +48,8 @@ public CampTypePlanTransformer() { @Override protected double scoreForNullFormat(Object planData, RegisteredType type, RegisteredTypeLoadingContext context) { + if (type!=null && type.getKind()!=RegisteredTypeKind.SPEC) return 0; + Maybe> plan = RegisteredTypes.getAsYamlMap(planData); if (plan.isAbsent()) return 0; if (plan.get().containsKey("services")) return 0.8; @@ -60,6 +63,8 @@ protected double scoreForNullFormat(Object planData, RegisteredType type, Regist @Override protected double scoreForNonmatchingNonnullFormat(String planFormat, Object planData, RegisteredType type, RegisteredTypeLoadingContext context) { + if (type!=null && type.getKind()!=RegisteredTypeKind.SPEC) return 0; + if (FORMATS.contains(planFormat.toLowerCase())) return 0.9; return 0; } @@ -73,7 +78,7 @@ protected double scoreForNonmatchingNonnullFormat(String planFormat, Object plan @Override protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception { // beans not supported by this? - return null; + throw new IllegalStateException("beans not supported here"); } @Override diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java index 1248110ad6..2d4424d970 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java @@ -73,6 +73,8 @@ protected LocalManagementContext newMgmtContext() { }; launcher.launch(); platform = launcher.getCampPlatform(); + + forceUpdate = false; } @Override diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java index c69f48101a..0300fc0bea 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java @@ -18,9 +18,14 @@ */ package org.apache.brooklyn.camp.brooklyn; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.Reader; +import java.util.Collection; import java.util.Map; import java.util.Set; +import java.util.zip.ZipEntry; import org.apache.brooklyn.api.catalog.BrooklynCatalog; import org.apache.brooklyn.api.entity.Application; @@ -30,19 +35,28 @@ import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.spi.creation.CampTypePlanTransformer; +import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.trait.Startable; import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; import org.apache.brooklyn.core.mgmt.EntityManagementUtils; +import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests.Builder; +import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; +import org.apache.brooklyn.core.typereg.BasicManagedBundle; import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.core.osgi.BundleMaker; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.exceptions.ReferenceWithError; import org.apache.brooklyn.util.net.Urls; +import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.stream.Streams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -97,7 +111,7 @@ protected LocalManagementContext newTestManagementContext() { } return builder.build(); } - + /** Override to enable OSGi in the management context for all tests in the class. */ protected boolean disableOsgi() { return true; @@ -218,8 +232,31 @@ protected void addCatalogItems(String catalogYaml) { mgmt().getCatalog().addItems(catalogYaml, forceUpdate); } + public static void addCatalogItemsAsOsgi(ManagementContext mgmt, String catalogYaml, VersionedName bundleName, boolean force) { + try { + BundleMaker bundleMaker = new BundleMaker(mgmt); + File bf = bundleMaker.createTempZip("test", MutableMap.of( + new ZipEntry(BasicBrooklynCatalog.CATALOG_BOM), new ByteArrayInputStream(catalogYaml.getBytes()))); + ReferenceWithError b = ((ManagementContextInternal)mgmt).getOsgiManager().get().installDeferredStart( + new BasicManagedBundle(bundleName.getSymbolicName(), bundleName.getVersionString(), null), + new FileInputStream(bf), + false); + // bundle not started (no need), and BOM not installed nor validated above; + // do BOM install and validation below manually to test the type registry approach + mgmt.getCatalog().addTypesFromBundleBom(catalogYaml, b.get().getMetadata(), force); + Map> validation = mgmt.getCatalog().validateTypes( + mgmt.getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(b.get().getVersionedName())) ); + if (!validation.isEmpty()) { + throw Exceptions.propagate("Brooklyn failed to load types: "+validation.keySet(), + Iterables.concat(validation.values())); + } + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + protected void deleteCatalogEntity(String catalogItem) { - mgmt().getCatalog().deleteCatalogItem(catalogItem, TEST_VERSION); + ((BasicBrooklynTypeRegistry) mgmt().getTypeRegistry()).delete(new VersionedName(catalogItem, TEST_VERSION)); } protected Logger getLogger() { @@ -254,7 +291,15 @@ protected int countCatalogItemsMatching(Predicate filter return Iterables.size(mgmt().getTypeRegistry().getMatching(filter)); } + /** forcibly update items when adding to catalog (default is not to do this) */ public void forceCatalogUpdate() { forceUpdate = true; } + + /** whether when adding to catalog to forcibly update */ + public final boolean isForceUpdate() { + return forceUpdate; + } + + } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ApplicationsYamlOsgiTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ApplicationsYamlOsgiTest.java new file mode 100644 index 0000000000..a93ee64868 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ApplicationsYamlOsgiTest.java @@ -0,0 +1,31 @@ +/* + * 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.camp.brooklyn; + +import org.testng.annotations.Test; + +@Test +public class ApplicationsYamlOsgiTest extends ApplicationsYamlTest { + + @Override + protected boolean disableOsgi() { + return false; + } + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ApplicationsYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ApplicationsYamlTest.java index bcda335b82..27f7927343 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ApplicationsYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ApplicationsYamlTest.java @@ -24,21 +24,24 @@ import org.apache.brooklyn.api.entity.Application; import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.TestSensorAndEffectorInitializer.TestConfigurableInitializer; import org.apache.brooklyn.camp.brooklyn.catalog.CatalogYamlLocationTest; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.mgmt.EntityManagementUtils; -import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; -import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; import org.apache.brooklyn.core.test.policy.TestEnricher; import org.apache.brooklyn.core.test.policy.TestPolicy; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.entity.stock.BasicApplication; import org.apache.brooklyn.entity.stock.BasicEntity; +import org.apache.brooklyn.test.Asserts; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.testng.Assert; import org.testng.annotations.Test; import com.google.common.base.Joiner; +import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; @@ -46,12 +49,6 @@ public class ApplicationsYamlTest extends AbstractYamlTest { private static final Logger log = LoggerFactory.getLogger(ApplicationsYamlTest.class); - @Override - protected LocalManagementContext newTestManagementContext() { - // Don't need osgi - return LocalManagementContextForTests.newInstance(); - } - @Test public void testWrapsEntity() throws Exception { Entity app = createAndStartApplication( @@ -403,6 +400,22 @@ public void testNameWithinItem() throws Exception { assertDoesNotWrap(app1, BasicApplication.class, "My name within item"); } + @Test + /** Tests catalog.bom format where service is defined alongside brooklyn.catalog, IE latter has no item/items */ + public void testItemFromServicesSectionInCatalog() { + addCatalogItems( + "brooklyn.catalog:", + " id: simple-test", + " version: "+TEST_VERSION, + "services:", + "- type: org.apache.brooklyn.entity.stock.BasicEntity"); + + Iterable retrievedItems = mgmt().getTypeRegistry() + .getMatching(RegisteredTypePredicates.symbolicName(Predicates.equalTo("simple-test"))); + Asserts.assertSize(retrievedItems, 1); + Assert.assertEquals(Iterables.getOnlyElement(retrievedItems).getVersion(), TEST_VERSION); + } + @Override protected Logger getLogger() { return log; diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java index 53193aa7be..98ccb0bddf 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java @@ -39,6 +39,7 @@ import org.apache.brooklyn.api.location.PortRange; import org.apache.brooklyn.camp.brooklyn.catalog.SpecParameterUnwrappingTest; import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.config.BasicConfigInheritance; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.config.ConfigPredicates; @@ -908,14 +909,13 @@ public void testManuallyAddWithParentFromCatalog() throws Exception { LOG.info("E1 keys: "+entity1.getEntityType().getConfigKeys()); LOG.info("E2 keys: "+entity2.getEntityType().getConfigKeys()); Assert.assertEquals(entity2.getEntityType().getConfigKeys(), entity1.getEntityType().getConfigKeys()); - Assert.assertEquals(entity1.getCatalogItemId(), "test-entity:0.0.0.SNAPSHOT"); + Assert.assertEquals(entity1.getCatalogItemId(), "test-entity:"+BasicBrooklynCatalog.NO_VERSION); // TODO currently the child has item ID set from CatalogUtils.setCatalogItemIdOnAddition // that should set a search path instead of setting the actual item // (ideally we'd assert null here) - Assert.assertEquals(entity2.getCatalogItemId(), "test-entity:0.0.0.SNAPSHOT"); + Assert.assertEquals(entity2.getCatalogItemId(), "test-entity:"+BasicBrooklynCatalog.NO_VERSION); } - @Test public void testManuallyAddInTaskOfOtherEntity() throws Exception { @@ -947,12 +947,12 @@ public TestEntity call() { LOG.info("E1 keys: "+entity1.getEntityType().getConfigKeys()); LOG.info("E2 keys: "+entity2.getEntityType().getConfigKeys()); Assert.assertEquals(entity2.getEntityType().getConfigKeys(), entity1.getEntityType().getConfigKeys()); - Assert.assertEquals(entity1.getCatalogItemId(), "test-entity:0.0.0.SNAPSHOT"); + Assert.assertEquals(entity1.getCatalogItemId(), "test-entity:"+BasicBrooklynCatalog.NO_VERSION); // TODO currently the child has item ID set from context in constructor of AbstractBrooklynObject; // that should set a search path instead of setting the actual item // (ideally we'd assert null here) - Assert.assertEquals(entity2.getCatalogItemId(), "test-entity:0.0.0.SNAPSHOT"); + Assert.assertEquals(entity2.getCatalogItemId(), "test-entity:"+BasicBrooklynCatalog.NO_VERSION); } @Test @@ -1229,7 +1229,8 @@ public void testConfigDefaultIsNotInheritedWith_LocalDefaultResolvesWithAncestor // check on spec - AbstractBrooklynObjectSpec spec = mgmt().getCatalog().peekSpec(mgmt().getCatalog().getCatalogItem("wrapper-entity", null)); + AbstractBrooklynObjectSpec spec = mgmt().getTypeRegistry().createSpec( + mgmt().getTypeRegistry().get("wrapper-entity", ""), null, null); Assert.assertEquals(spec.getParameters().size(), SpecParameterUnwrappingTest.NUM_ENTITY_DEFAULT_CONFIG_KEYS + NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT, "params: "+spec.getParameters()); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedOsgiYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedOsgiYamlTest.java index 4fc647d957..640e1c1cf7 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedOsgiYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedOsgiYamlTest.java @@ -22,6 +22,7 @@ import org.apache.brooklyn.api.catalog.BrooklynCatalog; import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest; import org.apache.brooklyn.entity.stock.BasicEntity; import org.apache.brooklyn.test.support.TestResourceUnavailableException; @@ -193,10 +194,10 @@ public void testCatalogReferenceMixesMetaAndUrl() throws Exception { " item:", " type: " + OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_SIMPLE_ENTITY); - BrooklynCatalog catalog = mgmt().getCatalog(); - Assert.assertNotNull(catalog.getCatalogItem("yaml.nested.catalog.nested", BrooklynCatalog.DEFAULT_VERSION)); - Assert.assertNotNull(catalog.getCatalogItem("yaml.nested.catalog.simple", BrooklynCatalog.DEFAULT_VERSION)); - Assert.assertNotNull(catalog.getCatalogItem("yaml.nested.catalog.more", BrooklynCatalog.DEFAULT_VERSION)); + BrooklynTypeRegistry catalog = mgmt().getTypeRegistry(); + Assert.assertNotNull(catalog.get("yaml.nested.catalog.nested", BrooklynCatalog.DEFAULT_VERSION)); + Assert.assertNotNull(catalog.get("yaml.nested.catalog.simple", BrooklynCatalog.DEFAULT_VERSION)); + Assert.assertNotNull(catalog.get("yaml.nested.catalog.more", BrooklynCatalog.DEFAULT_VERSION)); } protected void assertCatalogReference() throws Exception { diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlOsgiTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlOsgiTest.java new file mode 100644 index 0000000000..e46a34f229 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlOsgiTest.java @@ -0,0 +1,54 @@ +/* + * 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.camp.brooklyn; + +import org.apache.brooklyn.util.osgi.VersionedName; +import org.testng.annotations.Test; + +/** like superclass, but with OSGi enabled, complex references now work */ +public class ReferencedYamlOsgiTest extends ReferencedYamlTest { + + @Override + protected boolean disableOsgi() { + return false; + } + + @Override + protected void addCatalogItems(String catalogYaml) { + addCatalogItemsAsOsgi(mgmt(), catalogYaml, VersionedName.fromString("sample-bundle:0-SNAPSHOT"), isForceUpdate()); + } + + // these are not broken with OSGi + + @Test + public void testYamlReferencingEarlierItemLongFormEntity() throws Exception { + super.testYamlReferencingEarlierItemLongFormEntity(); + } + + @Test + public void testYamlReferencingEarlierItemInUrl() throws Exception { + super.testYamlReferencingEarlierItemInUrl(); + } + + @Test + public void testYamlReferencingEarlierItemInUrlAsType() throws Exception { + super.testYamlReferencingEarlierItemInUrlAsType(); + } + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlTest.java index 3480368e21..2626106cef 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlTest.java @@ -169,7 +169,7 @@ public void testYamlReferencingEarlierItemShortForm() throws Exception { checkChildEntitySpec(app, entityName); } - @Test(groups="WIP") // references to earlier items only work with short form syntax + @Test(groups="Broken") // long form discouraged but references should still work (but only in OSGi subclass) public void testYamlReferencingEarlierItemLongFormEntity() throws Exception { addCatalogItems( "brooklyn.catalog:", @@ -222,7 +222,7 @@ public void testYamlReferencingEarlierItemLongFormTemplate() throws Exception { checkChildEntitySpec(app, entityName); } - @Test(groups="WIP") //Not able to use caller provided catalog items when referencing entity specs (as opposed to catalog meta) + @Test(groups="Broken") // references to co-bundled items work even in nested url yaml (but only in OSGi subclass) public void testYamlReferencingEarlierItemInUrl() throws Exception { addCatalogItems( "brooklyn.catalog:", @@ -231,7 +231,7 @@ public void testYamlReferencingEarlierItemInUrl() throws Exception { " - id: yaml.basic", " version: " + TEST_VERSION, " item:", - " type: org.apache.brooklyn.entity.stock.BasicApplication", + " type: org.apache.brooklyn.entity.stock.BasicEntity", " - id: yaml.reference", " version: " + TEST_VERSION, " item: classpath://yaml-ref-catalog.yaml"); // this references yaml.basic above @@ -245,7 +245,7 @@ public void testYamlReferencingEarlierItemInUrl() throws Exception { checkChildEntitySpec(app, entityName); } - @Test(groups="WIP") //Not able to use caller provided catalog items when referencing entity specs (as opposed to catalog meta) + @Test(groups="Broken") // reference to co-bundled items work also in nested url yaml as a type (but only in OSGi subclass) public void testYamlReferencingEarlierItemInUrlAsType() throws Exception { addCatalogItems( "brooklyn.catalog:", @@ -254,7 +254,7 @@ public void testYamlReferencingEarlierItemInUrlAsType() throws Exception { " - id: yaml.basic", " version: " + TEST_VERSION, " item:", - " type: org.apache.brooklyn.entity.stock.BasicApplication", + " type: org.apache.brooklyn.entity.stock.BasicEntity", " - id: yaml.reference", " version: " + TEST_VERSION, " item:", diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java index 397bcf70fd..290c9e87f1 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java @@ -29,12 +29,10 @@ import org.apache.brooklyn.api.entity.Application; import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.core.BrooklynFeatureEnablement; -import org.apache.brooklyn.core.catalog.internal.CatalogBomScanner; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.EntityAsserts; import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; @@ -53,8 +51,6 @@ import org.apache.brooklyn.util.text.Identifiers; import org.apache.brooklyn.util.text.Strings; import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceReference; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.AfterMethod; @@ -80,7 +76,6 @@ protected LocalManagementContext newTestManagementContext() { @BeforeClass(alwaysRun = true) public void setUp() throws Exception { super.setUp(); - manuallyEnableBomScanner(mgmt()); bm = new BundleMaker( ((LocalManagementContext)mgmt()).getOsgiManager().get().getFramework(), ResourceUtils.create(this) ); } @@ -102,21 +97,6 @@ public void tearDown() throws Exception { super.tearDown(); } - public static void manuallyEnableBomScanner(ManagementContext mgmt) { - BrooklynFeatureEnablement.enable(BrooklynFeatureEnablement.FEATURE_LOAD_BUNDLE_CATALOG_BOM); - - CatalogBomScanner scanner = new CatalogBomScanner(); - BundleContext context = ((LocalManagementContext)mgmt).getOsgiManager().get().getFramework().getBundleContext(); - - context.registerService(ManagementContext.class.getName(), mgmt, null); - ServiceReference ref = context.getServiceReference(ManagementContext.class); - try { - scanner.bind(ref); - } catch (Exception e) { - throw Exceptions.propagate(e); - } - } - @AfterMethod(alwaysRun=true) public void clearFeatureEnablement() throws Exception { BrooklynFeatureEnablement.clearCache(); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java index aebd80d86c..7f7cbe7127 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java @@ -25,12 +25,12 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Map; +import java.util.Objects; -import org.apache.brooklyn.api.catalog.CatalogItem; -import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.core.config.external.AbstractExternalConfigSupplier; @@ -58,7 +58,6 @@ import org.testng.annotations.Test; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; import com.google.common.io.BaseEncoding; public class CatalogOsgiLibraryTest extends AbstractYamlTest { @@ -124,7 +123,7 @@ public void testLibraryStringWithClasspathUrl() throws Exception { " services:", " - type: org.apache.brooklyn.test.osgi.entities.SimpleApplication"); - CatalogItem item = mgmt().getCatalog().getCatalogItem("simple-osgi-library", "1.0"); + RegisteredType item = mgmt().getTypeRegistry().get("simple-osgi-library", "1.0"); assertCatalogLibraryUrl(item, classpathUrl); } @@ -141,7 +140,7 @@ public void testLibraryMapWithClasspathUrl() throws Exception { " services:", " - type: org.apache.brooklyn.test.osgi.entities.SimpleApplication"); - CatalogItem item = mgmt().getCatalog().getCatalogItem("simple-osgi-library", "1.0"); + RegisteredType item = mgmt().getTypeRegistry().get("simple-osgi-library", "1.0"); assertCatalogLibraryUrl(item, classpathUrl); } @@ -158,7 +157,7 @@ public void testLibraryHttpUrl() throws Exception { " services:", " - type: org.apache.brooklyn.test.osgi.entities.SimpleApplication"); - CatalogItem item = mgmt().getCatalog().getCatalogItem("simple-osgi-library", "1.0"); + RegisteredType item = mgmt().getTypeRegistry().get("simple-osgi-library", "1.0"); assertCatalogLibraryUrl(item, jarUrl.toString()); } @@ -220,7 +219,7 @@ public void testLibraryUrlUsingExternalizedConfig() throws Exception { " services:", " - type: org.apache.brooklyn.test.osgi.entities.SimpleApplication"); - CatalogItem item = mgmt().getCatalog().getCatalogItem("simple-osgi-library", "1.0"); + RegisteredType item = mgmt().getTypeRegistry().get("simple-osgi-library", "1.0"); assertCatalogLibraryUrl(item, classpathUrl); } @@ -268,6 +267,15 @@ protected void assertCanFindMessages(Entity entity) { Assert.fail("Expected to find 'messages.txt'"); } } + + protected void assertCannotFindMessages(Entity entity) { + ResourceUtils ru = ResourceUtils.create(entity); + Iterable files = ru.getResources("org/apache/brooklyn/test/osgi/resources/message.txt"); + if (files.iterator().hasNext()) { + Entities.dumpInfo(entity); + Assert.fail("Expected NOT to find 'messages.txt'"); + } + } @Test public void testLibraryIsUsedByChildInCatalogItem() throws Exception { @@ -295,16 +303,19 @@ public void testLibraryIsUsedByChildInCatalogItem() throws Exception { // the spec has no catalog item ID except at the root Application Entity app = createAndStartApplication("services: [ { type: item-from-library } ]"); - Entity entity = app.getChildren().iterator().next(); - entity = entity.getChildren().iterator().next(); - Entities.dumpInfo(entity); + Entity entity1 = app.getChildren().iterator().next(); + + Entities.dumpInfo(app); - // TODO re-enable when we've converted to a search path; + Assert.assertEquals(entity1.getCatalogItemId(), "item-from-library:1.0"); + assertCanFindMessages( entity1 ); + + // TODO enable when we've converted to a search path; // currently this test method passes because of CatalogUtils.setCatalogItemIdOnAddition // but we don't want to be doing that, we only want the search path - //Assert.assertNull(entity.getCatalogItemId(), "Entity had a catalog item ID, even though it was stockj"); - - assertCanFindMessages( entity ); + //Entity entity2 = entity1.getChildren().iterator().next(); + //Assert.assertNull(entity2.getCatalogItemId(), "Entity had a catalog item ID, even though it was stockj"); + //assertCannotFindMessages( entity2 ); } @Test @@ -403,7 +414,7 @@ protected void runLibraryUrlUsingExternalizedConfigForCredentials(boolean includ } String expectedUrl = "http://" + escapedUsername + ":" + escapedPassword+ "@" + jarUrl.getHost() + ":" + jarUrl.getPort() + jarUrl.getPath(); - CatalogItem item = mgmt().getCatalog().getCatalogItem("simple-osgi-library", "1.0"); + RegisteredType item = mgmt().getTypeRegistry().get("simple-osgi-library", "1.0"); assertCatalogLibraryUrl(item, expectedUrl); } @@ -420,8 +431,13 @@ public MyExternalConfigSupplier(ManagementContext mgmt, String name, Map item, String expectedUrl) { - CatalogBundle library = Iterables.getOnlyElement(item.getLibraries()); - assertEquals(library.getUrl(), expectedUrl); + protected void assertCatalogLibraryUrl(RegisteredType item, String expectedUrl) { + for (OsgiBundleWithUrl b: item.getLibraries()) { + if (Objects.equals(b.getUrl(), expectedUrl)) { + return; + } + } + Assert.fail("No library found with URL "+expectedUrl); } + } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java index 1d066f5ac3..0bea66c990 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java @@ -48,8 +48,10 @@ import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.ClassLoaderUtils; import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.exceptions.ReferenceWithError; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.osgi.OsgiTestResources; +import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.text.Strings; import org.osgi.framework.Bundle; import org.slf4j.Logger; @@ -72,24 +74,41 @@ public class CatalogOsgiVersionMoreEntityRebindTest extends AbstractYamlRebindTe protected boolean useOsgi() { return true; } - + @Test - public void testRebindAppIncludingBundle() throws Exception { - TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_COM_EXAMPLE_PATH); + public void testRebindAppIncludingBundleAllWorksAndPreservesChecksum() throws Exception { + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V1_PATH); ((ManagementContextInternal)mgmt()).getOsgiManager().get().install( new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V1_URL) ); + RegisteredType item = mgmt().getTypeRegistry().get(BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); + Assert.assertNotNull(item); + Assert.assertEquals(item.getContainingBundle(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.1.0"); + + ManagedBundle mb = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundle(VersionedName.fromString(item.getContainingBundle())); + Assert.assertNotNull(mb); + String c1 = mb.getChecksum(); + Assert.assertTrue(Strings.isNonBlank(c1), "Missing checksum for bundle"); + + Map bundles1 = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles(); + createAndStartApplication("services: [ { type: "+BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY+" } ]"); StartableApplication newApp = rebind(); // bundles installed Map bundles = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles(); - Asserts.assertSize(bundles.keySet(), 1); + Asserts.assertEquals(bundles, bundles1); - // types installed - RegisteredType t = mgmt().getTypeRegistry().get("org.apache.brooklyn.test.osgi.entities.more.MoreEntity"); - Assert.assertNotNull(t); + //item installed + item = mgmt().getTypeRegistry().get(BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); + Assert.assertNotNull(item); + Assert.assertEquals(item.getContainingBundle(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.1.0"); + + // containing bundle set, matches, and checksum matches + mb = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundle(VersionedName.fromString(item.getContainingBundle())); + Assert.assertEquals(mb, bundles.get(mb.getId())); + Assert.assertEquals(mb.getChecksum(), c1, "checksums should be the same after rebinding"); Assert.assertNotNull(newApp); } @@ -259,7 +278,7 @@ public void testClassAccessAfterUninstall() throws Exception { } } - @Test + // @Test public void testClassAccessAfterUpgrade() throws Exception { TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), BROOKLYN_TEST_OSGI_MORE_ENTITIES_0_1_0_PATH); @@ -285,9 +304,11 @@ public void testClassAccessAfterUpgrade() throws Exception { "HI BOB FROM V2"); // unforced upgrade should report already installed - Assert.assertEquals( ((ManagementContextInternal)mgmt()).getOsgiManager().get().install( - new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V2_EVIL_TWIN_URL) ).get().getCode(), - OsgiBundleInstallationResult.ResultCode.IGNORING_BUNDLE_AREADY_INSTALLED); + ReferenceWithError installEvilTwin = ((ManagementContextInternal)mgmt()).getOsgiManager().get().install( + new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V2_EVIL_TWIN_URL) ); + Assert.assertTrue(installEvilTwin.hasError()); + Assert.assertEquals(installEvilTwin.getWithoutError().getCode(), + OsgiBundleInstallationResult.ResultCode.ERROR_PREPARING_BUNDLE); // force upgrade OsgiBundleInstallationResult b2b = ((ManagementContextInternal)mgmt()).getOsgiManager().get().install(b2a.getMetadata(), @@ -386,5 +407,13 @@ public void testWrappedEntityClassLoaderDoesntHaveAncestorClassLoader() throws E Asserts.expectedFailureContainsIgnoreCase(e, "unable to load", BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); } } - + + @Test(groups="Broken") // AH think not going to support this; see notes in BasicBrooklynCatalog.scanAnnotationsInBundle + // it's hard to get the JAR for scanning, and doesn't fit with the OSGi way + public void testRebindJavaScanningBundleInCatalog() throws Exception { + CatalogScanOsgiTest.installJavaScanningMoreEntitiesV2(mgmt(), this); + rebind(); + RegisteredType item = mgmt().getTypeRegistry().get(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); + Assert.assertNotNull(item, "Scanned item should have been available after rebind"); + } } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java index 8961c6f7d1..10f2454e26 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java @@ -20,6 +20,7 @@ import static org.testng.Assert.assertTrue; +import java.util.List; import java.util.Map; import org.apache.brooklyn.api.entity.Entity; @@ -30,10 +31,10 @@ import org.apache.brooklyn.api.policy.PolicySpec; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; 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.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynEntityMatcher; -import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.mgmt.osgi.OsgiVersionMoreEntityTest; @@ -42,6 +43,7 @@ import org.apache.brooklyn.core.typereg.RegisteredTypes; 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.core.ResourceUtils; import org.apache.brooklyn.util.osgi.OsgiTestResources; import org.apache.brooklyn.util.text.Strings; @@ -76,7 +78,7 @@ public void testBrooklynManagedBundleInstall() throws Exception { // bundle installed Map bundles = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles(); Asserts.assertSize(bundles.keySet(), 1); - Assert.assertEquals(br.getMetadata().getId(), Iterables.getOnlyElement( bundles.keySet() )); + Assert.assertTrue(bundles.keySet().contains( br.getMetadata().getId() )); // types installed RegisteredType t = mgmt().getTypeRegistry().get(BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); @@ -247,7 +249,14 @@ public void testMorePolicyV2AutoscanWithClasspath() throws Exception { // this refers to the java item, where the libraries are defined item = mgmt().getTypeRegistry().get("org.apache.brooklyn.test.osgi.entities.more.MorePolicy"); Assert.assertEquals(item.getVersion(), "2.0.test_java"); - Assert.assertEquals(item.getLibraries().size(), 2); + // check the libraries are as expected and fully resolved + List libStr = MutableList.of(); + for (OsgiBundleWithUrl ob: item.getLibraries()) { + libStr.add(ob.getVersionedName()==null ? ob.getUrl() : ob.getVersionedName().toString()); + } + Assert.assertEquals(libStr, MutableList.of( + BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.2.0", + BROOKLYN_TEST_OSGI_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.1.0")); Entity app = createAndStartApplication( "services: ", diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java index fe86ed0648..6d7df19e60 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java @@ -236,30 +236,30 @@ public void testPartialBundleReferenceFails() { try { addCatalogItems( "brooklyn.catalog:", - " id: my.catalog.app.id.non_existing.ref", + " id: my.catalog.app.id.non_existing.ref.1", " version: " + TEST_VERSION, " itemType: entity", " libraries:", " - name: io.brooklyn.brooklyn-test-osgi-entities", " item:", " type: " + SIMPLE_ENTITY_TYPE); - fail(); - } catch (NullPointerException e) { - Assert.assertEquals(e.getMessage(), "both name and version are required"); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, "both name and version are required"); } try { addCatalogItems( "brooklyn.catalog:", - " id: my.catalog.app.id.non_existing.ref", + " id: my.catalog.app.id.non_existing.ref.2", " version: " + TEST_VERSION, " itemType: entity", " libraries:", " - version: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_VERSION, " item:", " type: " + SIMPLE_ENTITY_TYPE); - fail(); - } catch (NullPointerException e) { - Assert.assertEquals(e.getMessage(), "both name and version are required"); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, "both name and version are required"); } } @@ -323,13 +323,18 @@ public void testUpdatingItemAllowedIfSame() { addCatalogOSGiEntity(id); } - @Test(expectedExceptions = IllegalStateException.class) + @Test public void testUpdatingItemFailsIfDifferent() { TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH); String id = "my.catalog.app.id.duplicate"; addCatalogOSGiEntity(id); - addCatalogOSGiEntity(id, SIMPLE_ENTITY_TYPE, true); + try { + addCatalogOSGiEntity(id, SIMPLE_ENTITY_TYPE, true); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, id, "already installed", "cannot install a different bundle at a same non-snapshot version"); + } } @Test diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlLocationTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlLocationTest.java index 94b2c50242..d986a51ec3 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlLocationTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlLocationTest.java @@ -75,6 +75,14 @@ public void testAddCatalogItemOsgi() throws Exception { assertAdded(symbolicName, SIMPLE_LOCATION_TYPE); assertOsgi(symbolicName); removeAndAssert(symbolicName); + + // and do it again; the OSGi registry doesn't add it; + // it only works because the location registry initializes itself on first call + // by reading from the catalog + symbolicName = "my.catalog.location.id.load.2"; + addCatalogLocation(symbolicName, SIMPLE_LOCATION_TYPE, getOsgiLibraries()); + assertAdded(symbolicName, SIMPLE_LOCATION_TYPE); + assertOsgi(symbolicName); } @Test @@ -103,7 +111,7 @@ private void assertAdded(String symbolicName, String expectedJavaType) { // Item added to catalog should automatically be available in location registry LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocationByName(symbolicName); - assertEquals(def.getId(), symbolicName); + assertEquals(def.getId(), symbolicName+":"+TEST_VERSION); assertEquals(def.getName(), symbolicName); LocationSpec spec = mgmt().getTypeRegistry().createSpec(item, null, LocationSpec.class); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java new file mode 100644 index 0000000000..672c722460 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java @@ -0,0 +1,107 @@ +/* + * 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.camp.brooklyn.catalog; + +import java.util.Collection; + +import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.test.Asserts; +import org.testng.Assert; +import org.testng.annotations.Test; + +/** As parent tests, but using OSGi, and some of the additions are stricter / different */ +public class CatalogOsgiYamlVersioningTest extends CatalogYamlVersioningTest { + + @Override + protected boolean disableOsgi() { + return false; + } + + // now parent version of this test passes also in OSGi; + // the "sameEnough" check in BasicBrooklynTypeRegistry.addToLocalUnpersisted + // does _not_ look at containing bundle + // TODO delete this when we're happy with this behaviour +// @Override +// @Test +// public void testAddSameVersionWithoutBundle() { +// try { +// // parent test should fail in OSGi - anonymous bundle is snapshot so updating is attempted +// // but item version is not snapshot and containing bundle is different, so ultimately fails +// super.testAddSameVersionWithoutBundle(); +// Asserts.shouldHaveFailedPreviously("Expected to fail because containing bundle will be different when using OSGi"); +// } catch (Exception e) { +// assertExpectedFailureSaysDifferentIsBad(e); +// assertExpectedFailureIncludesSampleId(e); +// } +// } + + @Test + public void testAddSameVersionWithoutBundleWorksIfItemIsSnapshot() { + String symbolicName = "sampleId"; + String version = "0.1.0-SNAPSHOT"; + addCatalogEntityWithoutBundle(symbolicName, version); + // allowed because item is snapshot + addCatalogEntityWithoutBundle(symbolicName, version); + assertJustOneBundle(); + } + + @Test + public void testAddSameVersionWithoutBundleWorksIfForced() { + String symbolicName = "sampleId"; + String version = "0.1.0"; + addCatalogEntityWithoutBundle(symbolicName, version); + forceCatalogUpdate(); + addCatalogEntityWithoutBundle(symbolicName, version); + } + + @Override + protected void checkAddSameVersionFailsWhenIconIsDifferent(Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, + "cannot install a different bundle at a same non-snapshot version"); + assertExpectedFailureIncludesSampleId(e); + } + + @Test + public void testEmptyCatalogBundleIsRemoved() { + Collection bundles = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles().values(); + Assert.assertTrue(bundles.isEmpty(), "Expected no bundles before starting; but had: "+bundles); + } + + @Override + @Test + public void testAddSameVersionWorksIfSame() { + // in OSGi, assert additionally that we aren't leaking bundles + super.testAddSameVersionWorksIfSame(); + assertJustOneBundle(); + } + + protected void assertJustOneBundle() { + Collection bundles = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles().values(); + Assert.assertTrue(bundles.size()==1, "Expected one bundle after installing the same; but had: "+bundles); + } + + @Override + @Test + public void testAddSameSnapshotVersionSucceedsWhenIconIsDifferent() { + super.testAddSameSnapshotVersionSucceedsWhenIconIsDifferent(); + assertJustOneBundle(); + } + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanOsgiTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanOsgiTest.java new file mode 100644 index 0000000000..7b6a95a106 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanOsgiTest.java @@ -0,0 +1,154 @@ +/* + * 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.camp.brooklyn.catalog; + +import static org.testng.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.util.Map; +import java.util.zip.ZipEntry; + +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; +import org.apache.brooklyn.camp.brooklyn.test.lite.CampYamlLiteTest; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.entity.stock.BasicEntity; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.test.support.TestResourceUnavailableException; +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.os.Os; +import org.apache.brooklyn.util.osgi.OsgiTestResources; +import org.apache.brooklyn.util.stream.Streams; +import org.apache.brooklyn.util.text.Strings; +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.google.common.collect.Iterables; + +public class CatalogScanOsgiTest extends AbstractYamlTest { + + @Override protected boolean disableOsgi() { return false; } + + @Test(groups="Broken") // AH think not going to support this; see notes in BasicBrooklynCatalog.scanAnnotationsInBundle + public void testScanContainingBundle() throws Exception { + installJavaScanningMoreEntitiesV2(mgmt(), this); + + RegisteredType item = mgmt().getTypeRegistry().get(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); + Assert.assertNotNull(item, "Scanned item should have been loaded but wasn't"); + assertEquals(item.getVersion(), "0.2.0"); + assertEquals(item.getDisplayName(), "More Entity v2"); + assertEquals(item.getContainingBundle(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.2.0"); + assertEquals(Iterables.getOnlyElement(item.getLibraries()).getVersionedName().toString(), + OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.2.0"); + } + + static void installJavaScanningMoreEntitiesV2(ManagementContext mgmt, Object context) throws FileNotFoundException { + // scanning bundle functionality added in 0.12.0, relatively new compared to non-osgi scanning + + TestResourceUnavailableException.throwIfResourceUnavailable(context.getClass(), OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_PATH); + TestResourceUnavailableException.throwIfResourceUnavailable(context.getClass(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH); + + CampYamlLiteTest.installWithoutCatalogBom(mgmt, OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_PATH); + + BundleMaker bm = new BundleMaker(mgmt); + File f = Os.newTempFile(context.getClass(), "jar"); + Streams.copy(ResourceUtils.create(context).getResourceFromUrl(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH), new FileOutputStream(f)); + f = bm.copyRemoving(f, MutableSet.of("catalog.bom")); + f = bm.copyAdding(f, MutableMap.of(new ZipEntry("catalog.bom"), + new ByteArrayInputStream( Strings.lines( + "brooklyn.catalog:", + " scanJavaAnnotations: true").getBytes() ) )); + + ((ManagementContextInternal)mgmt).getOsgiManager().get().install(new FileInputStream(f)).checkNoError(); + } + + @Test + public void testScanLegacyListedLibraries() { + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_PATH); + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH); + addCatalogItems(bomForLegacySiblingLibraries()); + + RegisteredType hereItem = mgmt().getTypeRegistry().get("here-item"); + assertEquals(hereItem.getVersion(), "2.0-test_java"); + Asserts.assertSize(hereItem.getLibraries(), 2); + assertEquals(hereItem.getContainingBundle(), "test-items:2.0-test_java"); + + RegisteredType item = mgmt().getTypeRegistry().get(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); + // versions and libraries _are_ inherited in this legacy mode + assertEquals(item.getVersion(), "2.0-test_java"); + Asserts.assertSize(hereItem.getLibraries(), 2); + // and the containing bundle is recorded as the + assertEquals(item.getContainingBundle(), "test-items"+":"+"2.0-test_java"); + } + + static String bomForLegacySiblingLibraries() { + return Strings.lines("brooklyn.catalog:", + " bundle: test-items", + " version: 2.0-test_java", + " items:", + " - scanJavaAnnotations: true", + " item:", + " id: here-item", + " type: "+OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, + " libraries:", + " - classpath://" + OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_PATH, + " - classpath://" + OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH); + } + + @Test + public void testAddAnonymousBomTwiceDeletesOldEmptyOne() { + Map b1 = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles(); + addCatalogItems(bomAnonymous()); + Map b2_new = MutableMap.copyOf( + ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles() ); + for (String old: b1.keySet()) b2_new.remove(old); + + RegisteredType sample = mgmt().getTypeRegistry().get("sample"); + Asserts.assertNotNull(sample); + Asserts.assertSize(b2_new.values(), 1); + + addCatalogItems(bomAnonymous()); + Map b3 = MutableMap.copyOf( + ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles() ); + Map b3_new = MutableMap.copyOf(b3); + for (String old: b1.keySet()) b3_new.remove(old); + for (String old: b2_new.keySet()) b3_new.remove(old); + Asserts.assertSize(b3_new.values(), 1); + + Asserts.assertFalse(b3_new.keySet().contains( Iterables.getOnlyElement(b2_new.keySet()) )); + } + + static String bomAnonymous() { + return Strings.lines("brooklyn.catalog:", + " items:", + " - item:", + " id: sample", + " type: "+BasicEntity.class.getName()); + } + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanTest.java index ff7a0270cd..07cc23d7d8 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanTest.java @@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.google.common.base.Predicates; @@ -48,7 +49,7 @@ public class CatalogScanTest { - // TODO setUp/tearDown copied from AbstractYamlTest + // TODO tearDown could be merged with AbstractYamlTest // Moved from brooklyn-core. When we deleted support for catalog.xml, the scanned catalog // was then only stored in camp format, which meant we needed the camp parser. @@ -60,6 +61,12 @@ public class CatalogScanTest { private List mgmts = Lists.newCopyOnWriteArrayList(); private List launchers = Lists.newCopyOnWriteArrayList(); + @BeforeMethod(alwaysRun = true) + public void setUp() throws Exception { + defaultCatalog = null; + annotsCatalog = null; + } + /** Override to enable OSGi in the management context for all tests in the class. */ protected boolean disableOsgi() { return true; @@ -84,7 +91,7 @@ public void tearDown() throws Exception { private LocalManagementContext newManagementContext(BrooklynProperties props) { final LocalManagementContext localMgmt = LocalManagementContextForTests.builder(true) - .disableOsgi(disableOsgi()) + .setOsgiEnablementAndReuse(disableOsgi(), true) .useProperties(props) .build(); mgmts.add(localMgmt); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlAppOsgiTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlAppOsgiTest.java new file mode 100644 index 0000000000..90133c71ee --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlAppOsgiTest.java @@ -0,0 +1,31 @@ +/* + * 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.camp.brooklyn.catalog; + +import org.testng.annotations.Test; + +// OSGi variant of parent +@Test +public class CatalogYamlAppOsgiTest extends CatalogYamlAppTest { + + @Override + protected boolean disableOsgi() { + return false; + } +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlAppTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlAppTest.java index cc9066c403..e4a9bf9e84 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlAppTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlAppTest.java @@ -18,15 +18,15 @@ */ package org.apache.brooklyn.camp.brooklyn.catalog; -import org.testng.annotations.Test; - import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.camp.brooklyn.ApplicationsYamlTest; import org.apache.brooklyn.entity.stock.BasicEntity; +import org.testng.annotations.Test; /** - * Also see related tests in {@link ApplicationsYamlTest}. - * TODO Not clear what the difference is between these two test classes! + * Tests a few obscure circular references. + * See primary tests in {@link ApplicationsYamlTest}. + * Also see OSGi subclass. */ public class CatalogYamlAppTest extends AbstractYamlTest { @@ -40,12 +40,8 @@ public class CatalogYamlAppTest extends AbstractYamlTest { * "org.apache.brooklyn.entity.stock.BasicEntity", but that has been defined in the * catalog as this new blueprint (which overrides the previous value of it * being a reference to the Java class). - * - * We need to use an id that matches something else already on the classpath. - * Otherwise we'd get an error telling us "could not resolve item ..." when - * attempting to add the initial catalog item. */ - @Test(groups="WIP") // TODO Fix this! + @Test // already fixed prior to type-registry shift in 0.12.0, but also works with OSGi public void testAddCatalogItemWithCircularReference() throws Exception { // Add a catalog item with a circular reference to its own id. addCatalogItems( @@ -107,4 +103,5 @@ public void testAddCatalogItemWithMemberSpecCircularReference() throws Exception deleteCatalogEntity("org.apache.brooklyn.entity.stock.BasicApplication"); } } + } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java new file mode 100644 index 0000000000..ac11898513 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java @@ -0,0 +1,66 @@ +/* + * 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.camp.brooklyn.catalog; + +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; +import org.apache.brooklyn.entity.stock.BasicEntity; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.osgi.VersionedName; +import org.testng.annotations.Test; + +import com.google.common.collect.Iterables; + +/** Variant of parent tests using OSGi, bundles, and type registry, instead of lightweight non-osgi catalog */ +@Test +public class CatalogYamlEntityOsgiTypeRegistryTest extends CatalogYamlEntityTest { + + // use OSGi here + @Override protected boolean disableOsgi() { return false; } + + // use type registry appraoch + @Override + protected void addCatalogItems(String catalogYaml) { + addCatalogItemsAsOsgi(mgmt(), catalogYaml, new VersionedName(bundleName(), bundleVersion()), isForceUpdate()); + } + + protected String bundleName() { return "sample-bundle"; } + protected String bundleVersion() { return BasicBrooklynCatalog.NO_VERSION; } + + @Test // basic test that this approach to adding types works + public void testAddTypes() throws Exception { + String symbolicName = "my.catalog.app.id.load"; + addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), BasicEntity.class.getName()); + + Iterable itemsInstalled = mgmt().getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(new VersionedName(bundleName(), bundleVersion()))); + Asserts.assertSize(itemsInstalled, 1); + RegisteredType item = mgmt().getTypeRegistry().get(symbolicName, TEST_VERSION); + Asserts.assertEquals(item, Iterables.getOnlyElement(itemsInstalled), "Wrong item; installed: "+itemsInstalled); + } + + @Test // test broken in super works here + // TODO" comment at https://issues.apache.org/jira/browse/BROOKLYN-343 + public void testSameCatalogReferences() { + super.testSameCatalogReferences(); + } + + // also runs many other tests from super, here using the osgi/type-registry appraoch + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java index 0f378ffe50..9d907d9083 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java @@ -31,6 +31,7 @@ import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.test.entity.TestEntity; @@ -124,8 +125,9 @@ public void testAddCatalogItemUsingNameInsteadOfIdWithoutVersion() throws Except " item:", " type: "+ BasicEntity.class.getName()); RegisteredType catalogItem = mgmt().getTypeRegistry().get(id, BrooklynCatalog.DEFAULT_VERSION); - assertEquals(catalogItem.getVersion(), "0.0.0.SNAPSHOT"); - mgmt().getCatalog().deleteCatalogItem(id, "0.0.0.SNAPSHOT"); + assertEquals(catalogItem.getVersion(), BasicBrooklynCatalog.NO_VERSION); + mgmt().getCatalog().deleteCatalogItem(id, BasicBrooklynCatalog.NO_VERSION); + Assert.assertNull(mgmt().getTypeRegistry().get(id)); } // Legacy / backwards compatibility: should use id @@ -336,11 +338,16 @@ public void testUpdatingItemAllowedIfSame() { addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), TestEntity.class.getName()); } - @Test(expectedExceptions = IllegalStateException.class) + @Test public void testUpdatingItemFailsIfDifferent() { String symbolicName = "my.catalog.app.id.duplicate"; - addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), TestEntity.class.getName()); - addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), BasicEntity.class.getName()); + try { + addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), TestEntity.class.getName()); + addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), BasicEntity.class.getName()); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureContains(e, "different", symbolicName, TEST_VERSION, "already present"); + } } @Test @@ -373,7 +380,7 @@ public void testMissingTypeDoesNotRecurse() { try { addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION + "-update"), symbolicName); Asserts.shouldHaveFailedPreviously("Catalog addition expected to fail due to recursive reference to " + symbolicName); - } catch (IllegalStateException e) { + } catch (Exception e) { Asserts.expectedFailureContains(e, "recursive", symbolicName); } } @@ -389,7 +396,7 @@ public void testVersionedTypeDoesNotRecurse() throws Exception { try { addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION + "-update"), versionedId); Asserts.shouldHaveFailedPreviously("Catalog addition expected to fail due to recursive reference to " + versionedId); - } catch (IllegalStateException e) { + } catch (Exception e) { Asserts.expectedFailureContains(e, "recursive", symbolicName, versionedId); } } @@ -408,7 +415,7 @@ public void testIndirectRecursionFails() throws Exception { try { addCatalogEntity(IdAndVersion.of(callerSymbolicName, TEST_VERSION), calleeSymbolicName); Asserts.shouldHaveFailedPreviously(); - } catch (IllegalStateException e) { + } catch (Exception e) { Asserts.expectedFailureContains(e, "recursive"); } } @@ -439,8 +446,8 @@ public void testChildItemsDoNotRecurse() throws Exception { " brooklyn.children:", " - type: " + calleeSymbolicName); Asserts.shouldHaveFailedPreviously(); - } catch (IllegalStateException e) { - Asserts.expectedFailureContains(e, "recursive"); + } catch (Exception e) { + Asserts.expectedFailureContains(e, "recursive", callerSymbolicName, calleeSymbolicName); } } @@ -545,6 +552,7 @@ public void testHardcodedCatalog() throws Exception { @Test(groups = "Broken") // See https://issues.apache.org/jira/browse/BROOKLYN-343 + // Fixed in OSGi subclass public void testSameCatalogReferences() { addCatalogItems( "brooklyn.catalog:", @@ -692,11 +700,11 @@ public IdAndVersion(String id, String version) { } } - private void addCatalogEntity(String symbolicName, String entityType) { + protected void addCatalogEntity(String symbolicName, String entityType) { addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), entityType); } - private void addCatalogEntity(IdAndVersion idAndVersion, String serviceType) { + protected void addCatalogEntity(IdAndVersion idAndVersion, String serviceType) { addCatalogItems( "brooklyn.catalog:", " id: " + idAndVersion.id, diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java index 026b2e8ce1..80db3f348e 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java @@ -95,7 +95,6 @@ private void assertAdded(String symbolicName, String expectedJavaType) { // Item added to catalog should automatically be available in location registry LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocationByName(symbolicName); - assertEquals(def.getId(), symbolicName); assertEquals(def.getName(), symbolicName); LocationSpec spec = mgmt().getTypeRegistry().createSpec(item, null, LocationSpec.class); @@ -171,9 +170,9 @@ public void testTypeInheritance() throws Exception { addCatalogItems(yaml); - Map defs = mgmt().getLocationRegistry().getDefinedLocations(); - LocationDefinition def1 = checkNotNull(defs.get("loc1"), "loc1 missing; has %s", defs.keySet()); - LocationDefinition def2 = checkNotNull(defs.get("loc2"), "loc2 missing; has %s", defs.keySet()); + Map defs = mgmt().getLocationRegistry().getDefinedLocations(true); + LocationDefinition def1 = checkNotNull(defs.get("loc1:0.1.2"), "loc1 missing; has %s", defs.keySet()); + LocationDefinition def2 = checkNotNull(defs.get("loc2:0.1.2"), "loc2 missing; has %s", defs.keySet()); LocationSpec spec1 = mgmt().getLocationRegistry().getLocationSpec(def1).get(); LocationSpec spec2 = mgmt().getLocationRegistry().getLocationSpec(def2).get(); @@ -234,15 +233,15 @@ public void testNamePrecedence() throws Exception { addCatalogItems(yaml2); addCatalogItems(yaml3); - LocationDefinition def1 = mgmt().getLocationRegistry().getDefinedLocations().get("loc1"); + LocationDefinition def1 = mgmt().getLocationRegistry().getDefinedLocationByName("loc1"); LocationSpec spec1 = mgmt().getLocationRegistry().getLocationSpec(def1).get(); assertEquals(spec1.getDisplayName(), "My name in item metadata"); - LocationDefinition def2 = mgmt().getLocationRegistry().getDefinedLocations().get("loc2"); + LocationDefinition def2 = mgmt().getLocationRegistry().getDefinedLocationByName("loc2"); LocationSpec spec2 = mgmt().getLocationRegistry().getLocationSpec(def2).get(); assertEquals(spec2.getDisplayName(), "My name in top-level metadata"); - LocationDefinition def3b = mgmt().getLocationRegistry().getDefinedLocations().get("loc3b"); + LocationDefinition def3b = mgmt().getLocationRegistry().getDefinedLocationByName("loc3b"); LocationSpec spec3b = mgmt().getLocationRegistry().getLocationSpec(def3b).get(); assertEquals(spec3b.getDisplayName(), "My name within item 3b"); } @@ -261,7 +260,7 @@ public void testNameInCatalogMetadata() throws Exception { addCatalogItems(yaml); - LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocations().get("loc1"); + LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocationByName("loc1"); LocationSpec spec = mgmt().getLocationRegistry().getLocationSpec(def).get(); assertEquals(spec.getDisplayName(), "My name in top-level"); } @@ -280,7 +279,7 @@ public void testNameInItemMetadata() throws Exception { addCatalogItems(yaml); - LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocations().get("loc1"); + LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocationByName("loc1"); LocationSpec spec = mgmt().getLocationRegistry().getLocationSpec(def).get(); assertEquals(spec.getDisplayName(), "My name in item metadata"); } @@ -299,7 +298,7 @@ public void testNameWithinItem() throws Exception { addCatalogItems(yaml); - LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocations().get("loc1"); + LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocationByName("loc1"); LocationSpec spec = mgmt().getLocationRegistry().getLocationSpec(def).get(); assertEquals(spec.getDisplayName(), "My name within item"); } @@ -403,7 +402,7 @@ public void testManagedLocationsCreateAndCleanup() { } private void assertLocationRegistryCount(int size) { - Asserts.assertThat(mgmt().getLocationRegistry().getDefinedLocations().keySet(), CollectionFunctionals.sizeEquals(size)); + Asserts.assertThat(mgmt().getLocationRegistry().getDefinedLocations(true).keySet(), CollectionFunctionals.sizeEquals(size)); } private void assertLocationManagerInstancesCount(int size) { Asserts.assertThat(mgmt().getLocationManager().getLocations(), CollectionFunctionals.sizeEquals(size)); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java index 127a110fdd..66e5663d36 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java @@ -25,6 +25,8 @@ import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; +import java.io.File; +import java.util.Arrays; import java.util.List; import org.apache.brooklyn.api.catalog.CatalogItem; @@ -38,6 +40,7 @@ import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.StartableApplication; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest; import org.apache.brooklyn.core.mgmt.persist.BrooklynMementoPersisterToObjectStore; import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore; @@ -51,7 +54,6 @@ import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.test.support.TestResourceUnavailableException; import org.apache.brooklyn.util.exceptions.Exceptions; -import org.apache.brooklyn.util.osgi.OsgiTestResources; import org.apache.brooklyn.util.text.Strings; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; @@ -70,12 +72,10 @@ public class CatalogYamlRebindTest extends AbstractYamlRebindTest { // - config/attribute cannot be instantiated (e.g. because class no longer on classpath) // - entity file corrupt - private static final String OSGI_BUNDLE_SYMBOLID_NAME_FULL = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_SYMBOLIC_NAME_FULL; - private static final String OSGI_BUNDLE_URL = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL; - private static final String OSGI_SIMPLE_ENTITY_TYPE = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_SIMPLE_ENTITY; - private static final String OSGI_SIMPLE_POLICY_TYPE = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_SIMPLE_POLICY; - private static final String OSGI_SIMPLE_EFFECTOR_TYPE = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_SIMPLE_EFFECTOR; - + // Since 0.12.0 OSGi reads from bundles so many of the things this used to test are no longer supported; + // deprecation and disablement will have to be done on a bundle-wide basis + // and transforms will have to be done by forcibly replacing a bundle or another "upgrade" mechanism + enum RebindWithCatalogTestMode { NO_OP, STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, @@ -87,51 +87,43 @@ enum RebindWithCatalogTestMode { enum OsgiMode { NONE, - LIBRARY, - PREFIX + NORMAL } - private Boolean defaultEnablementOfFeatureAutoFixatalogRefOnRebind; + boolean useOsgi = false; + private Boolean defaultEnablementOfFeatureAutoFixCatalogRefOnRebind; @BeforeMethod(alwaysRun=true) @Override public void setUp() throws Exception { - defaultEnablementOfFeatureAutoFixatalogRefOnRebind = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND); + defaultEnablementOfFeatureAutoFixCatalogRefOnRebind = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND); super.setUp(); } @AfterMethod(alwaysRun=true) @Override public void tearDown() throws Exception { - if (defaultEnablementOfFeatureAutoFixatalogRefOnRebind != null) { - BrooklynFeatureEnablement.setEnablement(BrooklynFeatureEnablement.FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND, defaultEnablementOfFeatureAutoFixatalogRefOnRebind); + if (defaultEnablementOfFeatureAutoFixCatalogRefOnRebind != null) { + BrooklynFeatureEnablement.setEnablement(BrooklynFeatureEnablement.FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND, defaultEnablementOfFeatureAutoFixCatalogRefOnRebind); } super.tearDown(); } @Override protected boolean useOsgi() { - return true; + return useOsgi || (origManagementContext!=null && ((ManagementContextInternal)origManagementContext).getOsgiManager().isPresent()); } @DataProvider public Object[][] dataProvider() { return new Object[][] { {RebindWithCatalogTestMode.NO_OP, OsgiMode.NONE}, - {RebindWithCatalogTestMode.NO_OP, OsgiMode.LIBRARY}, - {RebindWithCatalogTestMode.NO_OP, OsgiMode.PREFIX}, {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.NONE}, - {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.LIBRARY}, - {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.PREFIX}, {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.NONE}, - {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.LIBRARY}, - {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.PREFIX}, {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.NONE}, - {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.LIBRARY}, - {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.PREFIX}, // For DELETE_CATALOG, see https://issues.apache.org/jira/browse/BROOKLYN-149. // Deletes the catalog item before rebind, but the referenced types are still on the @@ -143,8 +135,6 @@ public Object[][] dataProvider() { // Upgrades the catalog item before rebind, deleting the old version. // Will automatically upgrade. Test will enable "FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND" {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.NONE}, - {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.LIBRARY}, - {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.PREFIX}, }; } @@ -207,6 +197,8 @@ private String idFromPath(BrooklynObjectType type, String path) { public void testRebindWithCatalogAndAppUsingOptions(RebindWithCatalogTestMode mode, OsgiMode osgiMode, RebindOptions options) throws Exception { if (osgiMode != OsgiMode.NONE) { TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH); + + recreateOrigManagementContextWithOsgi(); } if (mode == RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION) { @@ -216,63 +208,17 @@ public void testRebindWithCatalogAndAppUsingOptions(RebindWithCatalogTestMode mo String appSymbolicName = "my.catalog.app.id.load"; String appVersion = "0.1.0"; - String appCatalogFormat; - if (osgiMode == OsgiMode.LIBRARY) { - appCatalogFormat = Joiner.on("\n").join( - "brooklyn.catalog:", - " id: " + appSymbolicName, - " version: %s", - " itemType: entity", - " libraries:", - " - url: " + OSGI_BUNDLE_URL, - " item:", - " type: " + OSGI_SIMPLE_ENTITY_TYPE, - " brooklyn.enrichers:", - " - type: " + TestEnricher.class.getName(), - " brooklyn.policies:", - " - type: " + OSGI_SIMPLE_POLICY_TYPE, - " brooklyn.initializers:", - " - type: " + OSGI_SIMPLE_EFFECTOR_TYPE); - } else if (osgiMode == OsgiMode.PREFIX) { - // This catalog item is just meant to load the bundle in the OSGi environment. Its content is irrelevant. - String libraryItem = Joiner.on("\n").join( - "brooklyn.catalog:", - " id: dummy", - " version: %s", - " itemType: entity", - " libraries:", - " - url: " + OSGI_BUNDLE_URL, - " item: " + BasicEntity.class.getName()); - addCatalogItems(String.format(libraryItem, appVersion)); - - // Use bundle prefixes here, pointing to the bundle already loaded above - appCatalogFormat = Joiner.on("\n").join( - "brooklyn.catalog:", - " id: " + appSymbolicName, - " version: %s", - " itemType: entity", - " item:", - " type: " + OSGI_BUNDLE_SYMBOLID_NAME_FULL + ":" + OSGI_SIMPLE_ENTITY_TYPE, - " brooklyn.enrichers:", - " - type: " + TestEnricher.class.getName(), - " brooklyn.policies:", - " - type: " + OSGI_BUNDLE_SYMBOLID_NAME_FULL + ":" + OSGI_SIMPLE_POLICY_TYPE, - " brooklyn.initializers:", - " - type: " + OSGI_BUNDLE_SYMBOLID_NAME_FULL + ":" + OSGI_SIMPLE_EFFECTOR_TYPE); - } else { - appCatalogFormat = Joiner.on("\n").join( - "brooklyn.catalog:", - " id: " + appSymbolicName, - " version: %s", - " itemType: entity", - " item:", - " type: " + BasicEntity.class.getName(), - " brooklyn.enrichers:", - " - type: " + TestEnricher.class.getName(), - " brooklyn.policies:", - " - type: " + TestPolicy.class.getName()); - } - + String appCatalogFormat = Joiner.on("\n").join( + "brooklyn.catalog:", + " id: " + appSymbolicName, + " version: %s", + " itemType: entity", + " item:", + " type: " + BasicEntity.class.getName(), + " brooklyn.enrichers:", + " - type: " + TestEnricher.class.getName(), + " brooklyn.policies:", + " - type: " + TestPolicy.class.getName()); String locSymbolicName = "my.catalog.loc.id.load"; String locVersion = "1.0.0"; @@ -412,4 +358,69 @@ public void testRebindWithCatalogAndAppUsingOptions(RebindWithCatalogTestMode mo } } } + + protected void recreateOrigManagementContextWithOsgi() { + // replace with OSGi context + Entities.destroyAll(origManagementContext); + try { + useOsgi = true; + tearDown(); + setUp(); + } catch (Exception e) { + throw Exceptions.propagate(e); + } finally { + useOsgi = false; + } + } + + @Test + public void testLongReferenceSequenceWithoutOsgi() throws Exception { + doTestLongReferenceSequence(); + } + + @Test + public void testLongReferenceSequenceWithOsgi() throws Exception { + recreateOrigManagementContextWithOsgi(); + doTestLongReferenceSequence(); + } + + private void doTestLongReferenceSequence() throws Exception { + // adds a0, a1 extending a0, a2 extending a1, ... a9 extending a8 + // osgi rebind of types can fail because bundles are restored in any order + // and dependencies might not yet be installed; + // ensure items are added first without validation, then validating + for (int i = 0; i<10; i++) { + addCatalogItems( + "brooklyn.catalog:", + " id: a" + i, + " version: 1", + " itemType: entity", + " item:", + " type: " + (i==0 ? BasicEntity.class.getName() : "a" + (i-1))); + } + origApp = (StartableApplication) createAndStartApplication("services: [ { type: a9 } ]"); + rebind(); + Entity child = Iterables.getOnlyElement( newApp.getChildren() ); + Asserts.assertTrue(child instanceof BasicEntity); + Asserts.assertEquals(child.getCatalogItemId(), "a9:1"); + } + + @Test + public void testDeleteEmptyBundleRemovedFromPersistence() throws Exception { + recreateOrigManagementContextWithOsgi(); + + String bom = Strings.lines( + "brooklyn.catalog:", + " itemType: entity", + " items:", + " - id: sample", + " item:", + " type: " + BasicEntity.class.getName()); + addCatalogItems(bom); + addCatalogItems(bom); + rebind(); + // should only contain one bundle / bundle.jar pair + Asserts.assertSize(Arrays.asList( new File(mementoDir, "bundles").list() ), 2); + } + } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java index bebda6f867..29428281e1 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java @@ -21,7 +21,6 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; @@ -33,6 +32,7 @@ import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.entity.stock.BasicApplication; import org.apache.brooklyn.entity.stock.BasicEntity; +import org.apache.brooklyn.test.Asserts; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -50,7 +50,7 @@ public void setUp() throws Exception { super.setUp(); types = mgmt().getTypeRegistry(); } - + @Test public void testAddItem() { String symbolicName = "sampleId"; @@ -67,19 +67,43 @@ public void testAddUnversionedItem() { } @Test - public void testAddSameVersionFailsWhenIconIsDifferent() { + public void testAddSameVersionWorksIfSame() { String symbolicName = "sampleId"; String version = "0.1.0"; addCatalogEntity(symbolicName, version); + // works if no different + addCatalogEntity(symbolicName, version); + } + + @Test + public void testAddSameVersionFailsWhenIconIsDifferent() { + String symbolicName = "sampleId"; + String version = "0.1.0"; addCatalogEntity(symbolicName, version); try { addCatalogEntity(symbolicName, version, BasicEntity.class.getName(), "classpath:/another/icon.png"); - fail("Expected to fail"); - } catch (IllegalStateException e) { - assertEquals(e.getMessage(), "Updating existing catalog entries is forbidden: " + symbolicName + ":" + version + ". Use forceUpdate argument to override."); + Asserts.shouldHaveFailedPreviously("Expected to fail"); + } catch (Exception e) { + checkAddSameVersionFailsWhenIconIsDifferent(e); } } + protected void checkAddSameVersionFailsWhenIconIsDifferent(Exception e) { + assertExpectedFailureSaysDifferentIsBad(e); + assertExpectedFailureIncludesSampleId(e); + } + + protected void assertExpectedFailureIncludesSampleId(Exception e) { + String symbolicName = "sampleId"; + String version = "0.1.0"; + Asserts.expectedFailureContainsIgnoreCase(e, + symbolicName + ":" + version); + } + protected void assertExpectedFailureSaysDifferentIsBad(Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, + "cannot add", "different"); + } + @Test public void testAddSameSnapshotVersionSucceedsWhenIconIsDifferent() { String symbolicName = "sampleId"; @@ -92,6 +116,15 @@ public void testAddSameSnapshotVersionSucceedsWhenIconIsDifferent() { assertTrue(item.getIconUrl().equals(icon), "Unexpected iconUrl: " + item.getIconUrl()); } + @Test + public void testAddSameVersionWithoutBundle() { + String symbolicName = "sampleId"; + String version = "0.1.0"; + addCatalogEntityWithoutBundle(symbolicName, version); + // allowed when not OSGi + addCatalogEntityWithoutBundle(symbolicName, version); + } + @Test public void testAddSameVersionForce() { String symbolicName = "sampleId"; @@ -281,4 +314,18 @@ private void addCatalogEntity(String symbolicName, String version, String type, " type: " + type); } + protected void addCatalogEntityWithoutBundle(String symbolicName, String version) { + addCatalogItems( + "brooklyn.catalog:", + " items:", + " - id: " + symbolicName, + (version != null ? " version: " + version : ""), + " itemType: entity", + " name: My Catalog App", + " description: My description", + " icon_url: "+"classpath://path/to/myicon.jpg", + " item:", + " type: " + BasicEntity.class.getName()); + } + } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterParsingOsgiTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterParsingOsgiTest.java index 814aed7c7b..06048c3893 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterParsingOsgiTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterParsingOsgiTest.java @@ -80,18 +80,9 @@ public void testOsgiType() { public void testOsgiClassScanned() { TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_PATH); TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH); - - addCatalogItems("brooklyn.catalog:", - " items:", - " - scanJavaAnnotations: true", - " version: 2.0.test_java", - " libraries:", - " - classpath://" + OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_PATH, - " - classpath://" + OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH); - + addCatalogItems(CatalogScanOsgiTest.bomForLegacySiblingLibraries()); + RegisteredType item = mgmt().getTypeRegistry().get(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); - assertEquals(item.getVersion(), "2.0.test_java"); - assertEquals(item.getLibraries().size(), 2); AbstractBrooklynObjectSpec spec = createSpec(item); List> inputs = spec.getParameters(); if (inputs.isEmpty()) Assert.fail("no inputs (if you're in the IDE, mvn clean install may need to be run to rebuild osgi test JARs)"); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterUnwrappingTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterUnwrappingTest.java index 1e20f5eefa..e5a69c70e5 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterUnwrappingTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterUnwrappingTest.java @@ -119,8 +119,7 @@ public void testParameters(Class testClass) { ConfigKey SIMPLE_CONFIG = ConfigKeys.newStringConfigKey("simple"); SpecParameter SIMPLE_PARAM = new BasicSpecParameter<>("simple", true, SIMPLE_CONFIG); - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); assertTrue(Iterables.tryFind(spec.getParameters(), Predicates.>equalTo(SIMPLE_PARAM)).isPresent()); } @@ -134,8 +133,7 @@ public void testDefaultParameters(Class testClass) { " item:", " type: "+ testClass.getName()); - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); assertEquals(ImmutableSet.copyOf(spec.getParameters()), ImmutableSet.copyOf(BasicSpecParameter.fromClass(mgmt(),testClass))); } @@ -153,8 +151,7 @@ public void testRootParametersUnwrapped() { " - simple"); final int NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT = 1; - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); List> params = spec.getParameters(); assertTrue(Iterables.tryFind(params, nameEqualTo("simple")).isPresent()); assertTrue(Iterables.tryFind(params, nameEqualTo(SHARED_CONFIG.getName())).isPresent()); @@ -181,8 +178,7 @@ public void testDependantCatalogsInheritParameters(Class item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); List> params = spec.getParameters(); // should have simple in parent yaml type and sample from parent java type assertEquals(params.size(), getNumDefaultConfigKeysFor(type.getSimpleName()) + NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT, @@ -223,8 +219,7 @@ public void testDependantCatalogsExtendsParameters(Class item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); List> params = spec.getParameters(); // should have another locally, simple in parent yaml type, and sample from parent java type assertEquals(params.size(), getNumDefaultConfigKeysFor(type.getSimpleName()) + NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT, @@ -254,8 +249,7 @@ public void testDependantCatalogMergesParameters(Class " label: override"); final int NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT = 1; - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); List> params = spec.getParameters(); // should have simple locally (and in parent yaml type) and sample from parent java type assertEquals(params.size(), getNumDefaultConfigKeysFor(type.getSimpleName()) + NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT, @@ -286,8 +280,7 @@ public void testDependantCatalogConfigOverridesParameters() { " - name: simple", " default: rabbits"); - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); List> params = spec.getParameters(); assertTrue(Iterables.tryFind(params, nameEqualTo("simple")).isPresent()); Optional> config = Iterables.tryFind(spec.getConfig().keySet(), ConfigPredicates.nameEqualTo("simple")); @@ -312,8 +305,7 @@ public void testCatalogConfigOverridesParameters() { " simple: value"); final int NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT = 1; - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); List> params = spec.getParameters(); assertEquals(params.size(), NUM_ENTITY_DEFAULT_CONFIG_KEYS + ConfigEntityForTest.NUM_CONFIG_KEYS_DEFINED_HERE + NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT); assertTrue(Iterables.tryFind(params, nameEqualTo("simple")).isPresent()); @@ -323,6 +315,15 @@ public void testCatalogConfigOverridesParameters() { assertEquals(value, "value"); } + private AbstractBrooklynObjectSpec peekSpec() { + return peekSpec(SYMBOLIC_NAME, TEST_VERSION); + } + + private AbstractBrooklynObjectSpec peekSpec(String name, String version) { + return mgmt().getTypeRegistry().createSpec( + mgmt().getTypeRegistry().get(name, version), null, null); + } + @Test public void testDependantCatalogConfigReplacesParameters() { addCatalogItems( @@ -341,8 +342,7 @@ public void testDependantCatalogConfigReplacesParameters() { " - name: simple", " default: rabbits"); - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); List> params = spec.getParameters(); assertTrue(Iterables.tryFind(params, nameEqualTo("simple")).isPresent()); Optional> config = Iterables.tryFind(spec.getConfig().keySet(), ConfigPredicates.nameEqualTo("simple")); @@ -365,8 +365,7 @@ public void testChildEntitiyHasParameters() { " brooklyn.parameters:", " - simple"); - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - EntitySpec parentSpec = (EntitySpec) catalog.peekSpec(item); + EntitySpec parentSpec = (EntitySpec) peekSpec(); EntitySpec spec = parentSpec.getChildren().get(0); List> params = spec.getParameters(); assertEquals(params.size(), NUM_ENTITY_DEFAULT_CONFIG_KEYS + 2, "params="+params); @@ -550,8 +549,7 @@ public void testParameterDefaultsUsedInConfig() throws Exception { " test: $brooklyn:config(\"num\")"); final int NUM_CONFIG_KEYS_FROM_WITH_PARAMS_TEST_BLUEPRINT = 1; - CatalogItem item = catalog.getCatalogItem(ConfigEntityForTest.class.getSimpleName() + "WithParams", TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(ConfigEntityForTest.class.getSimpleName() + "WithParams", TEST_VERSION); List> params = spec.getParameters(); assertEquals(params.size(), NUM_ENTITY_DEFAULT_CONFIG_KEYS + ConfigEntityForTest.NUM_CONFIG_KEYS_DEFINED_HERE + NUM_CONFIG_KEYS_FROM_WITH_PARAMS_TEST_BLUEPRINT, "params="+params); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java index 12abc50f67..92b053ebb0 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java @@ -29,9 +29,9 @@ import java.util.Map; import org.apache.brooklyn.api.catalog.CatalogItem; -import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; @@ -39,20 +39,22 @@ import org.apache.brooklyn.camp.spi.AssemblyTemplate; import org.apache.brooklyn.camp.spi.pdp.PdpYamlTest; import org.apache.brooklyn.camp.test.mock.web.MockWebPlatform; -import org.apache.brooklyn.core.catalog.CatalogPredicates; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.effector.AddChildrenEffector; import org.apache.brooklyn.core.effector.Effectors; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest; import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; import org.apache.brooklyn.core.test.entity.TestApplication; import org.apache.brooklyn.core.test.entity.TestEntity; import org.apache.brooklyn.core.typereg.BasicManagedBundle; import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.test.support.TestResourceUnavailableException; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.ResourceUtils; @@ -153,29 +155,28 @@ public void testAddChildrenEffector() throws Exception { } @Test + /** Tests catalog.bom format where service is defined alongside brooklyn.catalog, IE latter has no item/items */ public void testYamlServiceForCatalog() { TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH); installWithoutCatalogBom(mgmt, OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL); - CatalogItem realItem = Iterables.getOnlyElement(mgmt.getCatalog().addItems(Streams.readFullyStringAndClose(getClass().getResourceAsStream("test-app-service-blueprint.yaml")))); - Iterable> retrievedItems = mgmt.getCatalog() - .getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo("catalog-name"))); + Iterable> itemsInstalled = mgmt.getCatalog().addItems(Streams.readFullyStringAndClose(getClass().getResourceAsStream("test-app-service-blueprint.yaml"))); + Asserts.assertSize(itemsInstalled, 1); + CatalogItem itemInstalled = Iterables.getOnlyElement(itemsInstalled); + Assert.assertEquals(itemInstalled.getSymbolicName(), "catalog-name"); + Assert.assertEquals(itemInstalled.getVersion(), "0.9"); - Assert.assertEquals(Iterables.size(retrievedItems), 1, "Wrong retrieved items: "+retrievedItems); - CatalogItem retrievedItem = Iterables.getOnlyElement(retrievedItems); - Assert.assertEquals(retrievedItem, realItem); - - Collection bundles = retrievedItem.getLibraries(); - Assert.assertEquals(bundles.size(), 1); - CatalogBundle bundle = Iterables.getOnlyElement(bundles); + Iterable retrievedItems = mgmt.getTypeRegistry() + .getMatching(RegisteredTypePredicates.symbolicName(Predicates.equalTo("catalog-name"))); + Asserts.assertSize(retrievedItems, 1); + RegisteredType retrievedItem = Iterables.getOnlyElement(retrievedItems); + Assert.assertEquals(retrievedItem.getVersion(), "0.9"); + + Collection bundles = retrievedItem.getLibraries(); + Asserts.assertSize(bundles, 1); + OsgiBundleWithUrl bundle = Iterables.getOnlyElement(bundles); Assert.assertEquals(bundle.getUrl(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL); Assert.assertEquals(bundle.getSuppliedVersionString(), "0.1.0"); - - EntitySpec spec1 = (EntitySpec) mgmt.getCatalog().peekSpec(retrievedItem); - assertNotNull(spec1); - Assert.assertEquals(spec1.getConfig().get(TestEntity.CONF_NAME), "sample"); - - // TODO other assertions, about children } @Test @@ -208,11 +209,11 @@ private String prepAndGetSampleMyCatalogAppYaml(String symbolicName, String bund " type: " + MockWebPlatform.APPSERVER.getName()); } - protected void installWithoutCatalogBom(LocalManagementContext mgmt, String bundleUrl) { + public static void installWithoutCatalogBom(ManagementContext mgmt, String bundleUrl) { // install bundle for class access but without loading its catalog.bom, // since we only have mock matchers here // (if we don't do this, the default routines install it and try to process the catalog.bom, failing) - mgmt.getOsgiManager().get().installDeferredStart(new BasicManagedBundle(null, null, bundleUrl), null).get(); + ((ManagementContextInternal)mgmt).getOsgiManager().get().installDeferredStart(new BasicManagedBundle(null, null, bundleUrl), null, false).get(); } private void assertMgmtHasSampleMyCatalogApp(String symbolicName, String bundleUrl) { diff --git a/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java b/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java index fe3609a901..8bbe2a4ca5 100644 --- a/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java +++ b/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java @@ -22,7 +22,6 @@ import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode; import org.apache.brooklyn.core.internal.BrooklynProperties; -import org.apache.brooklyn.core.internal.storage.BrooklynStorage; import org.apache.brooklyn.util.core.internal.ssh.ShellTool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -115,15 +114,6 @@ public class BrooklynFeatureEnablement { public static final String FEATURE_VALIDATE_LOCATION_SSH_KEYS = "brooklyn.validate.locationSshKeys"; - /** - * Whether to scan newly loaded bundles for catalog.bom and load it. - * - * The functionality loads catalog items regardless of the persistence state so best used with persistence disabled. - * If a bundle is uploaded its BOM is scanned regardless of this property (this only applies to bundles - * installed through a non-brooklyn method, eg karaf.) - */ - public static final String FEATURE_LOAD_BUNDLE_CATALOG_BOM = FEATURE_PROPERTY_PREFIX+".osgi.catalog_bom"; - /** * Values explicitly set by Java calls. */ @@ -160,7 +150,6 @@ static void setDefaults() { setDefault(FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND, false); setDefault(FEATURE_SSH_ASYNC_EXEC, false); setDefault(FEATURE_VALIDATE_LOCATION_SSH_KEYS, true); - setDefault(FEATURE_LOAD_BUNDLE_CATALOG_BOM, false); } static { diff --git a/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java b/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java index f28d3fbde2..4cc13529f6 100644 --- a/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java +++ b/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java @@ -36,6 +36,7 @@ import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.mgmt.classloading.OsgiBrooklynClassLoadingContext; import org.apache.brooklyn.core.mgmt.ha.OsgiManager; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; @@ -127,7 +128,7 @@ public String getVersionFromClasspath() { if (Strings.isNonBlank(v)) return v; v = getVersionFromOsgiManifest(); if (Strings.isNonBlank(v)) return v; - return "0.0.0-SNAPSHOT"; + return BasicBrooklynCatalog.NO_VERSION; } @Override 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 2b199a37bd..30c2f0b2aa 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 @@ -21,12 +21,21 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; import javax.annotation.Nullable; @@ -34,42 +43,64 @@ import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType; +import org.apache.brooklyn.api.entity.Application; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; -import org.apache.brooklyn.api.location.Location; -import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; +import org.apache.brooklyn.api.objs.BrooklynObject; +import org.apache.brooklyn.api.objs.BrooklynObjectType; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; 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.catalog.CatalogPredicates; import org.apache.brooklyn.core.catalog.internal.CatalogClasspathDo.CatalogScanningModes; -import org.apache.brooklyn.core.location.BasicLocationRegistry; +import org.apache.brooklyn.core.mgmt.BrooklynTags; +import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; +import org.apache.brooklyn.core.mgmt.ha.OsgiManager; import org.apache.brooklyn.core.mgmt.internal.CampYamlParser; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; +import org.apache.brooklyn.core.typereg.BasicManagedBundle; +import org.apache.brooklyn.core.typereg.BasicRegisteredType; +import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan; import org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer; import org.apache.brooklyn.core.typereg.RegisteredTypeNaming; +import org.apache.brooklyn.core.typereg.RegisteredTypes; 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.flags.TypeCoercions; +import org.apache.brooklyn.util.core.osgi.BundleMaker; import org.apache.brooklyn.util.core.task.Tasks; +import org.apache.brooklyn.util.exceptions.CompoundRuntimeException; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.exceptions.ReferenceWithError; import org.apache.brooklyn.util.exceptions.UserFacingException; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.AggregateClassLoader; import org.apache.brooklyn.util.javalang.JavaClassNames; import org.apache.brooklyn.util.javalang.LoadedClassLoader; +import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.osgi.VersionedName; +import org.apache.brooklyn.util.stream.Streams; +import org.apache.brooklyn.util.text.Identifiers; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.time.Time; import org.apache.brooklyn.util.yaml.Yamls; import org.apache.brooklyn.util.yaml.Yamls.YamlExtract; +import org.osgi.framework.Bundle; +import org.osgi.framework.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; +import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Predicates; @@ -86,8 +117,19 @@ public class BasicBrooklynCatalog implements BrooklynCatalog { public static final String POLICIES_KEY = "brooklyn.policies"; public static final String ENRICHERS_KEY = "brooklyn.enrichers"; public static final String LOCATIONS_KEY = "brooklyn.locations"; - public static final String NO_VERSION = "0.0.0.SNAPSHOT"; - + public static final String NO_VERSION = "0.0.0-SNAPSHOT"; + + public static final String CATALOG_BOM = "catalog.bom"; + // should always be 1.0; see bottom of + // http://www.eclipse.org/virgo/documentation/virgo-documentation-3.7.0.M01/docs/virgo-user-guide/html/ch02s02.html + // (some things talk of 2.0, but haven't investigated that) + public static final String OSGI_MANIFEST_VERSION_VALUE = "1.0"; + + /** Header on bundle indicating it is a wrapped BOM with no other resources */ + public static final String BROOKLYN_WRAPPED_BOM_BUNDLE = "Brooklyn-Wrapped-BOM"; + @VisibleForTesting + public static final boolean AUTO_WRAP_CATALOG_YAML_AS_BUNDLE = true; + private static final Logger log = LoggerFactory.getLogger(BasicBrooklynCatalog.class); public static class BrooklynLoaderTracker { @@ -97,7 +139,7 @@ public static void setLoader(BrooklynClassLoadingContext val) { loader.set(val); } - // TODO Stack, for recursive calls? + // If needed, could use stack; see ClassLoaderFromStack... public static void unsetLoader(BrooklynClassLoadingContext val) { loader.set(null); } @@ -249,8 +291,22 @@ public CatalogItem getCatalogItem(String symbolicName, String version) { return itemDo.getDto(); } + private static ThreadLocal deletingCatalogItem = new ThreadLocal<>(); @Override public void deleteCatalogItem(String symbolicName, String version) { + if (!Boolean.TRUE.equals(deletingCatalogItem.get())) { + // while we switch from catalog to type registry, make sure deletion covers both; + // thread local lets us call to other once then he calls us and we do other code path + deletingCatalogItem.set(true); + try { + ((BasicBrooklynTypeRegistry) mgmt.getTypeRegistry()).delete( + mgmt.getTypeRegistry().get(symbolicName, version) ); + return; + } finally { + deletingCatalogItem.remove(); + } + } + log.debug("Deleting manual catalog item from "+mgmt+": "+symbolicName + ":" + version); checkNotNull(symbolicName, "id"); checkNotNull(version, "version"); @@ -273,11 +329,6 @@ public void deleteCatalogItem(String symbolicName, String version) { if (log.isTraceEnabled()) { log.trace("Scheduling item for persistence removal: {}", itemDto.getId()); } - if (itemDto.getCatalogItemType() == CatalogItemType.LOCATION) { - @SuppressWarnings("unchecked") - CatalogItem> locationItem = (CatalogItem>) itemDto; - ((BasicLocationRegistry)mgmt.getLocationRegistry()).removeDefinedLocation(locationItem); - } mgmt.getRebindManager().getChangeListener().onUnmanaged(itemDto); } @@ -415,12 +466,6 @@ private static Maybe> getFirstAsMap(Map map, String firstKey, Stri return (Maybe) getFirstAs(map, Map.class, firstKey, otherKeys); } - private List> collectCatalogItems(String yaml) { - List> result = MutableList.of(); - collectCatalogItems(yaml, result, ImmutableMap.of()); - return result; - } - public static Map getCatalogMetadata(String yaml) { Map itemDef = Yamls.getAs(Yamls.parseAll(yaml), Map.class); return getFirstAsMap(itemDef, "brooklyn.catalog").orNull(); @@ -449,21 +494,27 @@ public static VersionedName getVersionedName(Map catalogMetadata, boolean r return new VersionedName(bundle, version); } - private void collectCatalogItems(String yaml, List> result, Map parentMeta) { + /** See comments on {@link #collectCatalogItemsFromItemMetadataBlock(String, ManagedBundle, Map, List, boolean, Map, int, boolean)}; + * this is a shell around that that parses the `brooklyn.catalog` header on the BOM YAML file */ + private void collectCatalogItemsFromCatalogBomRoot(String yaml, ManagedBundle containingBundle, List> result, boolean requireValidation, Map parentMeta, int depth, boolean force) { Map itemDef = Yamls.getAs(Yamls.parseAll(yaml), Map.class); Map catalogMetadata = getFirstAsMap(itemDef, "brooklyn.catalog").orNull(); if (catalogMetadata==null) log.warn("No `brooklyn.catalog` supplied in catalog request; using legacy mode for "+itemDef); catalogMetadata = MutableMap.copyOf(catalogMetadata); - collectCatalogItems(Yamls.getTextOfYamlAtPath(yaml, "brooklyn.catalog").getMatchedYamlTextOrWarn(), - catalogMetadata, result, parentMeta); + collectCatalogItemsFromItemMetadataBlock(Yamls.getTextOfYamlAtPath(yaml, "brooklyn.catalog").getMatchedYamlTextOrWarn(), + containingBundle, catalogMetadata, result, requireValidation, parentMeta, 0, force); itemDef.remove("brooklyn.catalog"); catalogMetadata.remove("item"); catalogMetadata.remove("items"); if (!itemDef.isEmpty()) { - log.debug("Reading brooklyn.catalog peer keys as item ('top-level syntax')"); + // AH - i forgot we even supported this. probably no point anymore, + // now that catalog defs can reference an item yaml and things can be bundled together? + log.warn("Deprecated read of catalog item from sibling keys of `brooklyn.catalog` section, " + + "instead of the more common appraoch of putting inside an `item` within it. " + + "Rewrite to use nested/reference syntax instead or contact the community for assistance or feedback."); Map rootItem = MutableMap.of("item", itemDef); String rootItemYaml = yaml; YamlExtract yamlExtract = Yamls.getTextOfYamlAtPath(rootItemYaml, "brooklyn.catalog"); @@ -472,12 +523,46 @@ private void collectCatalogItems(String yaml, List> if (rootItemYaml.startsWith(match)) rootItemYaml = Strings.removeFromStart(rootItemYaml, match); else rootItemYaml = Strings.replaceAllNonRegex(rootItemYaml, "\n"+match, ""); } - collectCatalogItems("item:\n"+makeAsIndentedObject(rootItemYaml), rootItem, result, catalogMetadata); + collectCatalogItemsFromItemMetadataBlock("item:\n"+makeAsIndentedObject(rootItemYaml), containingBundle, rootItem, result, requireValidation, catalogMetadata, 1, force); } } + /** + * Expects item metadata, containing an `item` containing the definition, + * and/or `items` containing a list of item metadata (recursing with depth). + * + * Supports two modes depending whether result is passed here: + * + * * CatalogItems validated and returned, but not added to catalog here, instead returned in result; + * caller does that, and CI instances are persisted and loaded directly after rebind + * + * * RegisteredTypes added to (unpersisted) type registry if result is null; + * caller than validates, optionally removes broken ones, + * given the ability to add multiple interdependent BOMs/bundles and then validate; + * bundles with BOMs are persisted instead of catalog items + * + * I (Alex) think the first one should be considered legacy, and removed once we do + * everything with bundles. (At that point we can kill nearly ALL the code in this package, + * needing just a subset of the parsing and validation routines in this class which can + * be tidied up a lot.) + * + * Parameters suggest other combinations besides the above, but they aren't guaranteed to work. + * This is a private method and expect to clean it up a lot as per above. + * + * @param sourceYaml - metadata source for reference + * @param containingBundle - bundle where this is being loaded, or null + * @param itemMetadata - map of this item metadata reap + * @param result - list where items should be added, or add to type registry if null + * @param requireValidation - whether to require items to be validated; if false items might not be valid, + * and/or their catalog item types might not be set correctly yet; caller should normally validate later + * (useful if we have to load a bunch of things before they can all be validated) + * @param parentMetadata - inherited metadata + * @param depth - depth this is running in + * @param force - whether to force the catalog addition (only applies if result is null) + */ @SuppressWarnings("unchecked") - private void collectCatalogItems(String sourceYaml, Map itemMetadata, List> result, Map parentMetadata) { + private void collectCatalogItemsFromItemMetadataBlock(String sourceYaml, ManagedBundle containingBundle, Map itemMetadata, List> result, boolean requireValidation, + Map parentMetadata, int depth, boolean force) { if (sourceYaml==null) sourceYaml = new Yaml().dump(itemMetadata); @@ -511,30 +596,76 @@ private void collectCatalogItems(String sourceYaml, Map itemMetadata, List< // brooklyn.libraries we treat specially, to append the list, with the child's list preferred in classloading order // `libraries` is supported in some places as a legacy syntax; it should always be `brooklyn.libraries` for new apps // TODO in 0.8.0 require brooklyn.libraries, don't allow "libraries" on its own - List librariesNew = MutableList.copyOf(getFirstAs(itemMetadataWithoutItemDef, List.class, "brooklyn.libraries", "libraries").orNull()); - Collection libraryBundlesNew = CatalogItemDtoAbstract.parseLibraries(librariesNew); - - List librariesCombined = MutableList.copyOf(librariesNew) - .appendAll(getFirstAs(parentMetadata, List.class, "brooklyn.libraries", "libraries").orNull()); - if (!librariesCombined.isEmpty()) - catalogMetadata.put("brooklyn.libraries", librariesCombined); - Collection libraryBundles = CatalogItemDtoAbstract.parseLibraries(librariesCombined); + List librariesAddedHereNames = MutableList.copyOf(getFirstAs(itemMetadataWithoutItemDef, List.class, "brooklyn.libraries", "libraries").orNull()); + Collection librariesAddedHereBundles = CatalogItemDtoAbstract.parseLibraries(librariesAddedHereNames); + + MutableSet librariesCombinedNames = MutableSet.of(); + if (!isNoBundleOrSimpleWrappingBundle(mgmt, containingBundle)) { + // ensure containing bundle is declared, first, for search purposes + librariesCombinedNames.add(containingBundle.getVersionedName().toOsgiString()); + } + librariesCombinedNames.putAll(librariesAddedHereNames); + librariesCombinedNames.putAll(getFirstAs(parentMetadata, Collection.class, "brooklyn.libraries", "libraries").orNull()); + if (!librariesCombinedNames.isEmpty()) { + catalogMetadata.put("brooklyn.libraries", librariesCombinedNames); + } + Collection libraryBundles = CatalogItemDtoAbstract.parseLibraries(librariesCombinedNames); - // TODO as this may take a while if downloading, the REST call should be async - // (this load is required for the scan below and I think also for yaml resolution) - CatalogUtils.installLibraries(mgmt, libraryBundlesNew); + // TODO this may take a while if downloading; ideally the REST call would be async + // but this load is required for resolving YAML in this BOM (and if java-scanning); + // need to think through how we expect dependencies to be installed + CatalogUtils.installLibraries(mgmt, librariesAddedHereBundles); + + // use resolved bundles + librariesAddedHereBundles = resolveWherePossible(mgmt, librariesAddedHereBundles); + libraryBundles = resolveWherePossible(mgmt, libraryBundles); Boolean scanJavaAnnotations = getFirstAs(itemMetadataWithoutItemDef, Boolean.class, "scanJavaAnnotations", "scan_java_annotations").orNull(); if (scanJavaAnnotations==null || !scanJavaAnnotations) { // don't scan } else { - // scan for annotations: if libraries here, scan them; if inherited libraries error; else scan classpath - if (!libraryBundlesNew.isEmpty()) { - result.addAll(scanAnnotationsFromBundles(mgmt, libraryBundlesNew, catalogMetadata)); - } else if (libraryBundles.isEmpty()) { - result.addAll(scanAnnotationsFromLocal(mgmt, catalogMetadata)); + if (isNoBundleOrSimpleWrappingBundle(mgmt, containingBundle)) { + Collection> scanResult; + // BOMs wrapped in JARs, or without JARs, have special treatment + if (isLibrariesMoreThanJustContainingBundle(librariesAddedHereBundles, containingBundle)) { + // legacy mode, since 0.12.0, scan libraries referenced in a legacy non-bundle BOM + log.warn("Deprecated use of scanJavaAnnotations to scan other libraries ("+librariesAddedHereBundles+"); libraries should declare they scan themselves"); + scanResult = scanAnnotationsLegacyInListOfLibraries(mgmt, librariesAddedHereBundles, catalogMetadata, containingBundle); + } else if (!isLibrariesMoreThanJustContainingBundle(libraryBundles, containingBundle)) { + // for default catalog, no libraries declared, we want to scan local classpath + // bundle should be named "brooklyn-default-catalog" + if (containingBundle!=null && !containingBundle.getSymbolicName().contains("brooklyn-default-catalog")) { + // a user uplaoded a BOM trying to tell us to do a local java scan; previously supported but becoming unsupported + log.warn("Deprecated use of scanJavaAnnotations in non-Java BOM outwith the default catalog setup"); + } else if (depth>0) { + // since 0.12.0, require this to be right next to where libraries are defined, or at root + log.warn("Deprecated use of scanJavaAnnotations declared in item; should be declared at the top level of the BOM"); + } + scanResult = scanAnnotationsFromLocalNonBundleClasspath(mgmt, catalogMetadata, containingBundle); + } else { + throw new IllegalStateException("Cannot scan for Java catalog items when libraries declared on an ancestor; scanJavaAnnotations should be specified alongside brooklyn.libraries (or ideally those libraries should specify to scan)"); + } + if (scanResult!=null && !scanResult.isEmpty()) { + if (result!=null) { + result.addAll( scanResult ); + } else { + // not returning a result; we need to add here + for (CatalogItem item: scanResult) { + mgmt.getCatalog().addItem(item); + } + } + } } else { - throw new IllegalStateException("Cannot scan catalog node no local bundles, and with inherited bundles we will not scan the classpath"); + throw new IllegalArgumentException("Scanning for Java annotations is not supported in BOMs in bundles; " + + "entries should be listed explicitly in the catalog.bom"); + // see comments on scanAnnotationsInBundle +// if (depth>0) { +// // since 0.12.0, require this to be right next to where libraries are defined, or at root +// log.warn("Deprecated use of scanJavaAnnotations declared in item; should be declared at the top level of the BOM"); +// } +// // normal JAR install, only scan that bundle (the one containing the catalog.bom) +// // note metadata not relevant here +// result.addAll(scanAnnotationsInBundle(mgmt, containingBundle)); } } @@ -546,18 +677,18 @@ private void collectCatalogItems(String sourceYaml, Map itemMetadata, List< int count = 0; for (Object ii: checkType(items, "items", List.class)) { if (ii instanceof String) { - collectUrlReferencedCatalogItems((String) ii, result, catalogMetadata); + collectUrlReferencedCatalogItems((String) ii, containingBundle, result, requireValidation, catalogMetadata, depth+1, force); } else { Map i = checkType(ii, "entry in items list", Map.class); - collectCatalogItems(Yamls.getTextOfYamlAtPath(sourceYaml, "items", count).getMatchedYamlTextOrWarn(), - i, result, catalogMetadata); + collectCatalogItemsFromItemMetadataBlock(Yamls.getTextOfYamlAtPath(sourceYaml, "items", count).getMatchedYamlTextOrWarn(), + containingBundle, i, result, requireValidation, catalogMetadata, depth+1, force); } count++; } } if (url != null) { - collectUrlReferencedCatalogItems(checkType(url, "include in catalog meta", String.class), result, catalogMetadata); + collectUrlReferencedCatalogItems(checkType(url, "include in catalog meta", String.class), containingBundle, result, requireValidation, catalogMetadata, depth+1, force); } if (item==null) return; @@ -582,8 +713,10 @@ private void collectCatalogItems(String sourceYaml, Map itemMetadata, List< } PlanInterpreterGuessingType planInterpreter = new PlanInterpreterGuessingType(null, item, sourceYaml, itemType, libraryBundles, result).reconstruct(); + Exception resolutionError = null; if (!planInterpreter.isResolved()) { - throw Exceptions.create("Could not resolve definition of item" + // don't throw yet, we may be able to add it in an unresolved state + resolutionError = Exceptions.create("Could not resolve definition of item" + (Strings.isNonBlank(id) ? " '"+id+"'" : Strings.isNonBlank(symbolicName) ? " '"+symbolicName+"'" : Strings.isNonBlank(name) ? " '"+name+"'" : "") // better not to show yaml, takes up lots of space, and with multiple plan transformers there might be multiple errors; // some of the errors themselves may reproduce it @@ -591,10 +724,12 @@ private void collectCatalogItems(String sourceYaml, Map itemMetadata, List< // + ":\n"+sourceYaml , planInterpreter.getErrors()); } + // now allowed to be null here itemType = planInterpreter.getCatalogItemType(); + Map itemAsMap = planInterpreter.getItem(); // the "plan yaml" includes the services: ... or brooklyn.policies: ... outer key, - // as opposed to the rawer { type: xxx } map without that outer key which is valid as item input + // as opposed to the rawer { type: foo } map without that outer key which is valid as item input // TODO this plan yaml is needed for subsequent reconstruction; would be nicer if it weren't! // if symname not set, infer from: id, then name, then item id, then item name @@ -712,35 +847,132 @@ private void collectCatalogItems(String sourceYaml, Map itemMetadata, List< String description = getFirstAs(catalogMetadata, String.class, "description").orNull(); description = setFromItemIfUnset(description, itemAsMap, "description"); - // icon.url is discouraged, but kept for legacy compatibility; should deprecate this + // icon.url is discouraged (using '.'), but kept for legacy compatibility; should deprecate this final String catalogIconUrl = getFirstAs(catalogMetadata, String.class, "iconUrl", "icon_url", "icon.url").orNull(); final String deprecated = getFirstAs(catalogMetadata, String.class, "deprecated").orNull(); final Boolean catalogDeprecated = Boolean.valueOf(deprecated); - // run again now that we know the ID + // run again now that we know the ID to catch recursive definitions and possibly other mistakes (itemType inconsistency?) planInterpreter = new PlanInterpreterGuessingType(id, item, sourceYaml, itemType, libraryBundles, result).reconstruct(); - if (!planInterpreter.isResolved()) { - throw new IllegalStateException("Could not resolve plan once id and itemType are known (recursive reference?): "+sourceYaml); + if (resolutionError==null && !planInterpreter.isResolved()) { + resolutionError = new IllegalStateException("Plan resolution for "+id+" breaks after id and itemType are set; is there a recursive reference or other type inconsistency?\n"+sourceYaml); } String sourcePlanYaml = planInterpreter.getPlanYaml(); - CatalogItemDtoAbstract dto = createItemBuilder(itemType, symbolicName, version) - .libraries(libraryBundles) - .displayName(displayName) - .description(description) - .deprecated(catalogDeprecated) - .iconUrl(catalogIconUrl) - .plan(sourcePlanYaml) - .build(); + if (result==null) { + // horrible API but basically if `result` is null then add to local unpersisted registry instead, + // without forcing resolution and ignoring errors; this lets us deal with forward references, but + // we'll have to do a validation step subsequently. (already we let bundles deal with persistence, + // just need TODO to make sure we delete previously-persisted things which now come through this path.) + // NB: when everything is a bundle and we've removed all scanning then this can be the _only_ path + // and code can be massively simpler + // TODO allow these to be set in catalog.bom ? + List aliases = MutableList.of(); + List tags = MutableList.of(); + Boolean catalogDisabled = null; + + MutableList superTypes = MutableList.of(); + + if (itemType==CatalogItemType.TEMPLATE) { + tags.add(BrooklynTags.CATALOG_TEMPLATE); + itemType = CatalogItemType.ENTITY; + superTypes.add(Application.class); + } + + if (resolutionError!=null) { + if (!tags.contains(BrooklynTags.CATALOG_TEMPLATE)) { + if (requireValidation) { + throw Exceptions.propagate(resolutionError); + } + // warn? add as "unresolved" ? just do nothing? + } + } + String format = null; // could support specifying format? + + if (itemType!=null) { + // if supertype is known, set it here; + // we don't set kind (spec) because that is inferred from the supertype type + superTypes.appendIfNotNull(BrooklynObjectType.of(itemType).getInterfaceType()); + } + + if (version==null) { + // use this as default version when nothing specified + version = BasicBrooklynCatalog.NO_VERSION; + } + + if (sourcePlanYaml==null) { + // happens if unresolved and not valid yaml, replace with item yaml + // which normally has "type: " prefixed + sourcePlanYaml = planInterpreter.itemYaml; + } + + BasicRegisteredType type = (BasicRegisteredType) RegisteredTypes.newInstance( + RegisteredTypeKind.UNRESOLVED, + symbolicName, version, new BasicTypeImplementationPlan(format, sourcePlanYaml), + superTypes, aliases, tags, containingBundle==null ? null : containingBundle.getVersionedName().toString(), + MutableList.copyOf(libraryBundles), + displayName, description, catalogIconUrl, catalogDeprecated, catalogDisabled); + + ((BasicBrooklynTypeRegistry) mgmt.getTypeRegistry()).addToLocalUnpersistedTypeRegistry(type, force); + + } else { + if (resolutionError!=null) { + // if there was an error, throw it here + throw Exceptions.propagate(resolutionError); + } + + CatalogItemDtoAbstract dto = createItemBuilder(itemType, symbolicName, version) + .libraries(libraryBundles) + .displayName(displayName) + .description(description) + .deprecated(catalogDeprecated) + .iconUrl(catalogIconUrl) + .plan(sourcePlanYaml) + .build(); + + dto.setManagementContext((ManagementContextInternal) mgmt); + result.add(dto); + } + } + + protected static Collection resolveWherePossible(ManagementContext mgmt, Collection libraryBundles) { + Collection libraryBundlesResolved = MutableSet.of(); + for (CatalogBundle b: libraryBundles) { + libraryBundlesResolved.add(CatalogBundleDto.resolve(mgmt, b).or(b)); + } + return libraryBundlesResolved; + } - dto.setManagementContext((ManagementContextInternal) mgmt); - result.add(dto); + private static boolean isLibrariesMoreThanJustContainingBundle(Collection library, ManagedBundle containingBundle) { + if (library==null || library.isEmpty()) return false; + if (containingBundle==null) return !library.isEmpty(); + if (library.size()>1) return true; + CatalogBundle li = Iterables.getOnlyElement(library); + return !containingBundle.getVersionedName().equalsOsgi(li.getVersionedName()); } - private void collectUrlReferencedCatalogItems(String url, List> result, Map parentMeta) { + @Beta + public static boolean isNoBundleOrSimpleWrappingBundle(ManagementContext mgmt, ManagedBundle b) { + if (b==null) return true; + Maybe osgi = ((ManagementContextInternal)mgmt).getOsgiManager(); + if (osgi.isAbsent()) { + // odd, shouldn't happen, installing bundle but not using osgi + throw new IllegalStateException("OSGi not being used but installing a bundle"); + } + Maybe bb = osgi.get().findBundle(b); + if (bb.isAbsent()) { + // odd, shouldn't happen, bundle not managed + // (originally seen during a race where the empty-remover ran while we were installing) + throw new IllegalStateException("Loading from a bundle which is not installed"); + } + String wrapped = bb.get().getHeaders().get(BROOKLYN_WRAPPED_BOM_BUNDLE); + return wrapped!=null && wrapped.equalsIgnoreCase("true"); + } + + private void collectUrlReferencedCatalogItems(String url, ManagedBundle containingBundle, List> result, boolean requireValidation, Map parentMeta, int depth, boolean force) { @SuppressWarnings("unchecked") - List parentLibrariesRaw = MutableList.copyOf(getFirstAs(parentMeta, List.class, "brooklyn.libraries", "libraries").orNull()); + List parentLibrariesRaw = MutableList.copyOf(getFirstAs(parentMeta, Iterable.class, "brooklyn.libraries", "libraries").orNull()); Collection parentLibraries = CatalogItemDtoAbstract.parseLibraries(parentLibrariesRaw); BrooklynClassLoadingContext loader = CatalogUtils.newClassLoadingContext(mgmt, ":0.0.0", parentLibraries); String yaml; @@ -750,7 +982,7 @@ private void collectUrlReferencedCatalogItems(String url, List item, String fieldAt return oldValue; } - private Collection> scanAnnotationsFromLocal(ManagementContext mgmt, Map catalogMetadata) { + private Collection> scanAnnotationsFromLocalNonBundleClasspath(ManagementContext mgmt, Map catalogMetadata, ManagedBundle containingBundle) { CatalogDto dto = CatalogDto.newNamedInstance("Local Scanned Catalog", "All annotated Brooklyn entities detected in the classpath", "scanning-local-classpath"); - return scanAnnotationsInternal(mgmt, new CatalogDo(dto), catalogMetadata); + return scanAnnotationsInternal(mgmt, new CatalogDo(dto), catalogMetadata, containingBundle); } - private Collection> scanAnnotationsFromBundles(ManagementContext mgmt, Collection libraries, Map catalogMetadata) { + private Collection> scanAnnotationsLegacyInListOfLibraries(ManagementContext mgmt, Collection libraries, Map catalogMetadata, ManagedBundle containingBundle) { CatalogDto dto = CatalogDto.newNamedInstance("Bundles Scanned Catalog", "All annotated Brooklyn entities detected in bundles", "scanning-bundles-classpath-"+libraries.hashCode()); List urls = MutableList.of(); - for (CatalogBundle b: libraries) { - // TODO currently does not support pre-installed bundles identified by name:version + for (OsgiBundleWithUrl b: libraries) { + // does not support pre-installed bundles identified by name:version // (ie where URL not supplied) if (Strings.isNonBlank(b.getUrl())) { urls.add(b.getUrl()); + } else { + log.warn("scanJavaAnnotations does not apply to pre-installed bundles; skipping "+b); } } @@ -792,28 +1026,59 @@ private String setFromItemIfUnset(String oldValue, Map item, String fieldAt CatalogDo subCatalog = new CatalogDo(dto); subCatalog.addToClasspath(urls.toArray(new String[0])); - return scanAnnotationsInternal(mgmt, subCatalog, catalogMetadata); + return scanAnnotationsInternal(mgmt, subCatalog, catalogMetadata, containingBundle); } - private Collection> scanAnnotationsInternal(ManagementContext mgmt, CatalogDo subCatalog, Map catalogMetadata) { - // TODO this does java-scanning only; - // the call when scanning bundles should use the CatalogItem instead and use OSGi when loading for scanning - // (or another scanning mechanism). see comments on CatalogClasspathDo.load + @SuppressWarnings("unused") // keep during 0.12.0 until we are decided we won't support this; search for this method name + // note that it breaks after rebind since we don't have the JAR -- see notes below + private Collection> scanAnnotationsInBundle(ManagementContext mgmt, ManagedBundle containingBundle) { + CatalogDto dto = CatalogDto.newNamedInstance("Bundle "+containingBundle.getVersionedName().toOsgiString()+" Scanned Catalog", "All annotated Brooklyn entities detected in bundles", "scanning-bundle-"+containingBundle.getVersionedName().toOsgiString()); + CatalogDo subCatalog = new CatalogDo(dto); + // need access to a JAR to scan this + String url = null; + if (containingBundle instanceof BasicManagedBundle) { + File f = ((BasicManagedBundle)containingBundle).getTempLocalFileWhenJustUploaded(); + if (f!=null) { + url = "file:"+f.getAbsolutePath(); + } + } + // type.getSubPathName(), type, id+".jar", com.google.common.io.Files.asByteSource(f), exceptionHandler); + if (url==null) { + url = containingBundle.getUrl(); + } + if (url==null) { + // NOT available after persistence/rebind + // as shown by test in CatalogOsgiVersionMoreEntityRebindTest + throw new IllegalArgumentException("Error preparing to scan "+containingBundle.getVersionedName()+": no URL available"); + } + // org.reflections requires the URL to be "file:" containg ".jar" + File fJar = Os.newTempFile(containingBundle.getVersionedName().toOsgiString(), ".jar"); + try { + Streams.copy(ResourceUtils.create().getResourceFromUrl(url), new FileOutputStream(fJar)); + subCatalog.addToClasspath(new String[] { "file:"+fJar.getAbsolutePath() }); + Collection> result = scanAnnotationsInternal(mgmt, subCatalog, MutableMap.of("version", containingBundle.getSuppliedVersionString()), containingBundle); + return result; + } catch (FileNotFoundException e) { + throw Exceptions.propagateAnnotated("Error extracting "+url+" to scan "+containingBundle.getVersionedName(), e); + } finally { + fJar.delete(); + } + } + + private Collection> scanAnnotationsInternal(ManagementContext mgmt, CatalogDo subCatalog, Map catalogMetadata, ManagedBundle containingBundle) { subCatalog.mgmt = mgmt; subCatalog.setClasspathScanForEntities(CatalogScanningModes.ANNOTATIONS); subCatalog.load(); - // TODO apply metadata? (extract YAML from the items returned) - // also see doc .../catalog/index.md which says we might not apply metadata @SuppressWarnings({ "unchecked", "rawtypes" }) Collection> result = (Collection)Collections2.transform( (Collection>)(Collection)subCatalog.getIdCache().values(), - itemDoToDtoAddingSelectedMetadataDuringScan(catalogMetadata)); + itemDoToDtoAddingSelectedMetadataDuringScan(mgmt, catalogMetadata, containingBundle)); return result; } private class PlanInterpreterGuessingType { - final String id; + final String idAsSymbolicNameWithoutVersion; final Map item; final String itemYaml; final Collection libraryBundles; @@ -825,18 +1090,20 @@ private class PlanInterpreterGuessingType { List errors = MutableList.of(); List entityErrors = MutableList.of(); - public PlanInterpreterGuessingType(@Nullable String id, Object item, String itemYaml, @Nullable CatalogItemType optionalCiType, + public PlanInterpreterGuessingType(@Nullable String idAsSymbolicNameWithoutVersion, Object itemDefinitionParsedToStringOrMap, String itemYaml, @Nullable CatalogItemType optionalCiType, Collection libraryBundles, List> itemsDefinedSoFar) { // ID is useful to prevent recursive references (possibly only supported for entities?) - this.id = id; + this.idAsSymbolicNameWithoutVersion = idAsSymbolicNameWithoutVersion; - if (item instanceof String) { + if (itemDefinitionParsedToStringOrMap instanceof String) { // if just a string supplied, wrap as map - this.item = MutableMap.of("type", item); + this.item = MutableMap.of("type", itemDefinitionParsedToStringOrMap); this.itemYaml = "type:\n"+makeAsIndentedObject(itemYaml); - } else { - this.item = (Map)item; + } else if (itemDefinitionParsedToStringOrMap instanceof Map) { + this.item = (Map)itemDefinitionParsedToStringOrMap; this.itemYaml = itemYaml; + } else { + throw new IllegalArgumentException("Item definition should be a string or map to use the guesser"); } this.catalogItemType = optionalCiType; this.libraryBundles = libraryBundles; @@ -850,8 +1117,16 @@ public PlanInterpreterGuessingType reconstruct() { } else { attemptType(null, CatalogItemType.ENTITY); - + + List oldEntityErrors = MutableList.copyOf(entityErrors); + // try with services key attemptType("services", CatalogItemType.ENTITY); + entityErrors.removeAll(oldEntityErrors); + entityErrors.addAll(oldEntityErrors); + // errors when wrapped in services block are better currently + // as we parse using CAMP and need that + // so prefer those for now (may change with YOML) + attemptType(POLICIES_KEY, CatalogItemType.POLICY); attemptType(ENRICHERS_KEY, CatalogItemType.ENRICHER); attemptType(LOCATIONS_KEY, CatalogItemType.LOCATION); @@ -895,52 +1170,54 @@ private boolean attemptType(String key, CatalogItemType candidateCiType) { else candidateYaml = key + ":\n" + makeAsIndentedList(itemYaml); } - // first look in collected items, if a key is given String type = (String) item.get("type"); - - if (type!=null && key!=null) { - for (CatalogItemDtoAbstract candidate: itemsDefinedSoFar) { - if (candidateCiType == candidate.getCatalogItemType() && - (type.equals(candidate.getSymbolicName()) || type.equals(candidate.getId()))) { - // matched - exit - catalogItemType = candidateCiType; - planYaml = candidateYaml; - resolved = true; - return true; - } - } - } - { - // legacy routine; should be the same as above code added in 0.12 because: - // if type is symbolic_name, the type will match above, and version will be null so any version allowed to match - // if type is symbolic_name:version, the id will match, and the version will also have to match - // SHOULD NEVER NEED THIS - remove during or after 0.13 - String typeWithId = type; - String version = null; - if (CatalogUtils.looksLikeVersionedId(type)) { - version = CatalogUtils.getVersionFromVersionedId(type); - type = CatalogUtils.getSymbolicNameFromVersionedId(type); - } + if (itemsDefinedSoFar!=null) { + // first look in collected items, if a key is given + if (type!=null && key!=null) { for (CatalogItemDtoAbstract candidate: itemsDefinedSoFar) { if (candidateCiType == candidate.getCatalogItemType() && (type.equals(candidate.getSymbolicName()) || type.equals(candidate.getId()))) { - if (version==null || version.equals(candidate.getVersion())) { - log.error("Lookup of '"+type+"' version '"+version+"' only worked using legacy routines; please advise Brooklyn community so they understand why"); - // matched - exit - catalogItemType = candidateCiType; - planYaml = candidateYaml; - resolved = true; - return true; + // matched - exit + catalogItemType = candidateCiType; + planYaml = candidateYaml; + resolved = true; + return true; + } + } + } + { + // legacy routine; should be the same as above code added in 0.12 because: + // if type is symbolic_name, the type will match above, and version will be null so any version allowed to match + // if type is symbolic_name:version, the id will match, and the version will also have to match + // SHOULD NEVER NEED THIS - remove during or after 0.13 + String typeWithId = type; + String version = null; + if (CatalogUtils.looksLikeVersionedId(type)) { + version = CatalogUtils.getVersionFromVersionedId(type); + type = CatalogUtils.getSymbolicNameFromVersionedId(type); + } + if (type!=null && key!=null) { + for (CatalogItemDtoAbstract candidate: itemsDefinedSoFar) { + if (candidateCiType == candidate.getCatalogItemType() && + (type.equals(candidate.getSymbolicName()) || type.equals(candidate.getId()))) { + if (version==null || version.equals(candidate.getVersion())) { + log.error("Lookup of '"+type+"' version '"+version+"' only worked using legacy routines; please advise Brooklyn community so they understand why"); + // matched - exit + catalogItemType = candidateCiType; + planYaml = candidateYaml; + resolved = true; + return true; + } } } } + + type = typeWithId; + // above line is a change to behaviour; previously we proceeded below with the version dropped in code above; + // but that seems like a bug as the code below will have ignored version. + // likely this means we are now stricter about loading things that reference new versions, but correctly so. } - - type = typeWithId; - // above line is a change to behaviour; previously we proceeded below with the version dropped in code above; - // but that seems like a bug as the code below will have ignored version. - // likely this means we are now stricter about loading things that reference new versions, but correctly so. } // then try parsing plan - this will use loader @@ -974,7 +1251,7 @@ private boolean attemptType(String key, CatalogItemType candidateCiType) { if (candidateCiType==CatalogItemType.ENTITY) { entityErrors.add(e); } - if (log.isTraceEnabled()) + if (log.isTraceEnabled()) log.trace("Guessing type of plan, it looks like it isn't "+candidateCiType+"/"+key+": "+e); } } @@ -1008,7 +1285,7 @@ private boolean attemptType(String key, CatalogItemType candidateCiType) { } private String getIdWithRandomDefault() { - return id != null ? id : Strings.makeRandomId(10); + return idAsSymbolicNameWithoutVersion != null ? idAsSymbolicNameWithoutVersion : Strings.makeRandomId(10); } public Map getItem() { return item; @@ -1060,14 +1337,9 @@ public CatalogItem addItem(String yaml) { @Override public List> addItems(String yaml) { - return addItems(yaml, null); + return addItems(yaml, false); } - @Override - public List> addItems(String yaml, ManagedBundle bundle) { - return addItems(yaml, bundle, false); - } - @Override public CatalogItem addItem(String yaml, boolean forceUpdate) { return Iterables.getOnlyElement(addItems(yaml, forceUpdate)); @@ -1075,13 +1347,78 @@ public CatalogItem addItem(String yaml, boolean forceUpdate) { @Override public List> addItems(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 + 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(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(); + } + uninstallEmptyWrapperBundles(); + if (result.getCode().isError()) { + throw new IllegalStateException(result.getMessage()); + } + return toLegacyCatalogItems(result.getCatalogItemsInstalled()); + + // 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 return addItems(yaml, null, forceUpdate); } - private List> addItems(String yaml, ManagedBundle bundle, boolean forceUpdate) { - log.debug("Adding manual catalog item to "+mgmt+": "+yaml); + @SuppressWarnings("deprecation") + private List> toLegacyCatalogItems(Iterable itemIds) { + List> result = MutableList.of(); + for (String id: itemIds) { + CatalogItem item = CatalogUtils.getCatalogItemOptionalVersion(mgmt, id); + if (item==null) { + // using new Type Registry (OSGi addition); + result.add(RegisteredTypes.toPartialCatalogItem( mgmt.getTypeRegistry().get(id) )); + } else { + result.add(item); + } + } + return result; + } + + @Override + public List> addItems(String yaml, ManagedBundle bundle) { + return addItems(yaml, bundle, false); + } + + @Override + public List> addItems(String yaml, ManagedBundle bundle, boolean forceUpdate) { + log.debug("Adding catalog item to "+mgmt+": "+yaml); checkNotNull(yaml, "yaml"); - List> result = collectCatalogItems(yaml); + List> result = MutableList.of(); + collectCatalogItemsFromCatalogBomRoot(yaml, bundle, result, true, ImmutableMap.of(), 0, forceUpdate); // do this at the end for atomic updates; if there are intra-yaml references, we handle them specially for (CatalogItemDtoAbstract item: result) { @@ -1093,6 +1430,207 @@ private List> addItems(String yaml, ManagedBundle bun return result; } + @Override @Beta + public void addTypesFromBundleBom(String yaml, ManagedBundle bundle, boolean forceUpdate) { + log.debug("Adding catalog item to "+mgmt+": "+yaml); + checkNotNull(yaml, "yaml"); + collectCatalogItemsFromCatalogBomRoot(yaml, bundle, null, false, MutableMap.of(), 0, forceUpdate); + } + + @Override @Beta + public Map> validateTypes(Iterable typesToValidate) { + List typesRemainingToValidate = MutableList.copyOf(typesToValidate); + while (true) { + Map> result = MutableMap.of(); + for (RegisteredType t: typesToValidate) { + Collection tr = validateType(t); + if (!tr.isEmpty()) { + result.put(t, tr); + } + } + if (result.isEmpty() || result.size()==typesRemainingToValidate.size()) { + return result; + } + // recurse wherever there were problems so long as we are reducing the number of problem types + // (this lets us solve complex reference problems without needing a complex dependency tree, + // in max O(N^2) time) + typesRemainingToValidate = MutableList.copyOf(result.keySet()); + } + } + + @Override @Beta + public Collection validateType(RegisteredType typeToValidate) { + ReferenceWithError result = resolve(typeToValidate); + if (result.hasError()) { + if (RegisteredTypes.isTemplate(typeToValidate)) { + // ignore for templates + return Collections.emptySet(); + } + if (result.getError() instanceof CompoundRuntimeException) { + return ((CompoundRuntimeException)result.getError()).getAllCauses(); + } + return Collections.singleton(result.getError()); + } + // replace what's in catalog with resolved+validated version + ((BasicBrooklynTypeRegistry) mgmt.getTypeRegistry()).addToLocalUnpersistedTypeRegistry(result.get(), true); + return Collections.emptySet(); + } + + /** + * Resolves the given object with respect to the catalog. Returns any errors found while trying to resolve. + * The argument may be changed (e.g. its kind set, supertypes set), and normal usage is to add + * a type in an "unresolved" state if things may need to reference it, then call resolve here, + * then replace what was added with the argument given here. */ + @Beta + public ReferenceWithError resolve(RegisteredType typeToValidate) { + Throwable inconsistentSuperTypesError=null, specError=null, beanError=null; + List guesserErrors = MutableList.of(); + + // collect supertype spec / most specific java + Set supers = typeToValidate.getSuperTypes(); + BrooklynObjectType boType = null; + for (Object superI: supers) { + BrooklynObjectType boTypeI = null; + if (superI instanceof BrooklynObject) boTypeI = BrooklynObjectType.of((BrooklynObject)superI); + else if (superI instanceof Class) boTypeI = BrooklynObjectType.of((Class)superI); + if (boTypeI!=null && boTypeI!=BrooklynObjectType.UNKNOWN) { + if (boType==null) boType = boTypeI; + else { + if (boTypeI!=boType) { + inconsistentSuperTypesError = new IllegalStateException("Inconsistent supertypes for "+typeToValidate+"; indicates "+boType+" and "+boTypeI); + } + } + } + } + Class superJ = null; + for (Object superI: supers) { + if (superI instanceof Class) { + if (superJ==null) superJ = (Class) superI; + else if (superJ.isAssignableFrom((Class)superI)) superJ = (Class) superI; + } + } + + // could filter what we try based on kind; also could support itemType spec (generic) and + // more importantly bean to allow arbitrary types to be added to catalog + + RegisteredType resultT = null; + + Object resultO = null; + if (resultO==null && boType!=null) try { + // try spec instantiation if we know the BO Type (no point otherwise) + resultT = RegisteredTypes.copyResolved(RegisteredTypeKind.SPEC, typeToValidate); + try { + resultO = ((BasicBrooklynTypeRegistry)mgmt.getTypeRegistry()).createSpec(resultT, null, boType.getSpecType()); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + specError = e; + resultT = null; + } + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + // ignore if we couldn't resolve as spec + } + + if (resultO==null) try { + // try it as a bean + resultT = RegisteredTypes.copyResolved(RegisteredTypeKind.BEAN, typeToValidate); + try { + resultO = ((BasicBrooklynTypeRegistry)mgmt.getTypeRegistry()).createBean(resultT, null, superJ); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + beanError = e; + resultT = null; + } + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + // ignore if we couldn't resolve as spec + } + + if (resultO==null) try { + // try the legacy PlanInterpreterGuessingType + // (this is the only place where we will guess specs, so it handles + // most of our traditional catalog items in BOMs) + String yaml = RegisteredTypes.getImplementationDataStringForSpec(typeToValidate); + PlanInterpreterGuessingType guesser = new PlanInterpreterGuessingType(typeToValidate.getSymbolicName(), Iterables.getOnlyElement( Yamls.parseAll(yaml) ), + yaml, null, CatalogItemDtoAbstract.parseLibraries( typeToValidate.getLibraries() ), null); + guesser.reconstruct(); + guesserErrors.addAll(guesser.getErrors()); + + if (guesser.isResolved()) { + // guesser resolved, but we couldn't create; did guesser change something? + + CatalogItemType ciType = guesser.getCatalogItemType(); + // try this even for templates; errors in them will be ignored by validator + // but might be interesting to someone calling resolve directly + + boolean changedSomething = false; + // reset resultT and change things as needed based on guesser + resultT = typeToValidate; + if (boType==null) { + // guesser inferred a type + boType = BrooklynObjectType.of(ciType); + if (boType!=null) { + supers = MutableSet.copyOf(supers); + supers.add(boType.getInterfaceType()); + // didn't know type before, retry now that we know the type + resultT = RegisteredTypes.copyResolved(RegisteredTypeKind.SPEC, resultT); + RegisteredTypes.addSuperTypes(resultT, supers); + changedSomething = true; + } + } + + if (!Objects.equal(guesser.getPlanYaml(), yaml)) { + RegisteredTypes.changePlan(resultT, + new BasicTypeImplementationPlan(null /* CampTypePlanTransformer.FORMAT */, guesser.getPlanYaml())); + changedSomething = true; + } + + if (changedSomething) { + // try again with new plan or supertype info + return resolve(resultT); + + } else if (Objects.equal(boType, BrooklynObjectType.of(ciType))) { + if (specError==null) { + throw new IllegalStateException("Guesser resolved but TypeRegistry couldn't create"); + } else { + // do nothing; type was already known, prefer the spec error + } + } else { + throw new IllegalStateException("Guesser resolved as "+ciType+" but we expected "+boType); + } + } else { + throw new IllegalStateException("Guesser could not resolve"); + } + + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + guesserErrors.add(e); + resultT = null; + } + + if (resultO!=null && resultT!=null) { + if (resultO instanceof BrooklynObject) { + // if it was a bean that points at a BO then switch it to a spec and try to re-validate + return resolve(RegisteredTypes.copyResolved(RegisteredTypeKind.SPEC, typeToValidate)); + } + RegisteredTypes.cacheActualJavaType(resultT, resultO.getClass()); + + supers = MutableSet.copyOf(supers); + supers.add(resultO.getClass()); + supers.add(BrooklynObjectType.of(resultO.getClass()).getInterfaceType()); + RegisteredTypes.addSuperTypes(resultT, supers); + + return ReferenceWithError.newInstanceWithoutError(resultT); + } + + List errors = MutableList.of() + .appendIfNotNull(inconsistentSuperTypesError) + .appendAll(guesserErrors) + .appendIfNotNull(beanError) + .appendIfNotNull(specError); + return ReferenceWithError.newInstanceThrowingError(null, Exceptions.create("Could not resolve "+typeToValidate, errors)); + } + private CatalogItem addItemDto(CatalogItemDtoAbstract itemDto, boolean forceUpdate) { CatalogItem existingDto = checkItemAllowedAndIfSoReturnAnyDuplicate(itemDto, true, forceUpdate); if (existingDto!=null) { @@ -1121,11 +1659,7 @@ private CatalogItem addItemDto(CatalogItemDtoAbstract itemDto, boolea } private void onAdditionUpdateOtherRegistries(CatalogItemDtoAbstract itemDto) { - if (itemDto.getCatalogItemType() == CatalogItemType.LOCATION) { - @SuppressWarnings("unchecked") - CatalogItem> locationItem = (CatalogItem>) itemDto; - ((BasicLocationRegistry)mgmt.getLocationRegistry()).updateDefinedLocation(locationItem); - } + // nothing needed (previously updated BasicLocationRegistry but now that is a facade) } /** returns item DTO if item is an allowed duplicate, or null if it should be added (there is no duplicate), @@ -1140,11 +1674,11 @@ private void onAdditionUpdateOtherRegistries(CatalogItemDtoAbstract itemDt CatalogItem existingDto = existingItem.getDto(); if (existingDto.equals(itemDto)) { if (allowDuplicates) return existingItem; - throw new IllegalStateException("Updating existing catalog entries, even with the same content, is forbidden: " + - itemDto.getSymbolicName() + ":" + itemDto.getVersion() + ". Use forceUpdate argument to override."); + throw new IllegalStateException("Not allowed to update existing catalog entries, even with the same content: " + + itemDto.getSymbolicName() + ":" + itemDto.getVersion()); } else { - throw new IllegalStateException("Updating existing catalog entries is forbidden: " + - itemDto.getSymbolicName() + ":" + itemDto.getVersion() + ". Use forceUpdate argument to override."); + throw new IllegalStateException("Cannot add " + itemDto.getSymbolicName() + ":" + itemDto.getVersion() + + " to catalog; a different definition is already present"); } } @@ -1232,24 +1766,43 @@ public CatalogItem apply(@Nullable CatalogItemDo item) { }; } - private static Function, CatalogItem> itemDoToDtoAddingSelectedMetadataDuringScan(final Map catalogMetadata) { + private static Function, CatalogItem> itemDoToDtoAddingSelectedMetadataDuringScan(final ManagementContext mgmt, final Map catalogMetadata, ManagedBundle containingBundle) { return new Function, CatalogItem>() { @Override public CatalogItem apply(@Nullable CatalogItemDo item) { if (item==null) return null; CatalogItemDtoAbstract dto = (CatalogItemDtoAbstract) item.getDto(); - // when scanning we only allow version and libraries to be overwritten + // allow metadata to overwrite version and library bundles; + // however this should only be used for local classpath scanning and legacy external libraries; + // bundle scans should _not_ use this String version = getFirstAs(catalogMetadata, String.class, "version").orNull(); if (Strings.isNonBlank(version)) dto.setVersion(version); - Object librariesCombined = catalogMetadata.get("brooklyn.libraries"); - if (librariesCombined instanceof Collection) { + Collection libraryBundles = MutableSet.of(); + if (!isNoBundleOrSimpleWrappingBundle(mgmt, containingBundle)) { + libraryBundles.add(new CatalogBundleDto(containingBundle.getSymbolicName(), containingBundle.getSuppliedVersionString(), null)); + } + libraryBundles.addAll(dto.getLibraries()); + Object librariesInherited; + librariesInherited = catalogMetadata.get("brooklyn.libraries"); + if (librariesInherited instanceof Collection) { + // will be set by scan -- slightly longwinded way to retrieve, but scanning java should be deprecated I think (AH) + libraryBundles.addAll(resolveWherePossible(mgmt, CatalogItemDtoAbstract.parseLibraries((Collection) librariesInherited))); + } + librariesInherited = catalogMetadata.get("libraries"); + if (librariesInherited instanceof Collection) { + log.warn("Legacy 'libraries' encountered; use 'brooklyn.libraries'"); // will be set by scan -- slightly longwinded way to retrieve, but scanning for osgi needs an overhaul in any case - Collection libraryBundles = CatalogItemDtoAbstract.parseLibraries((Collection) librariesCombined); - dto.setLibraries(libraryBundles); + libraryBundles.addAll(resolveWherePossible(mgmt, CatalogItemDtoAbstract.parseLibraries((Collection) librariesInherited))); + } + dto.setLibraries(libraryBundles); + + if (containingBundle!=null && dto.getContainingBundle()==null) { + dto.setContainingBundle(containingBundle.getVersionedName()); } + // replace java type with plan yaml -- needed for libraries / catalog item to be picked up, // but probably useful to transition away from javaType altogether dto.setSymbolicName(dto.getJavaType()); @@ -1296,4 +1849,23 @@ public void addSpec(String itemId, AbstractBrooklynObjectSpec spec) { cache.put(itemId, spec); } } + + private Object uninstallingEmptyLock = new Object(); + public void uninstallEmptyWrapperBundles() { + log.debug("uninstalling empty wrapper bundles"); + synchronized (uninstallingEmptyLock) { + Maybe osgi = ((ManagementContextInternal)mgmt).getOsgiManager(); + if (osgi.isAbsent()) return; + for (ManagedBundle b: osgi.get().getInstalledWrapperBundles()) { + if (isNoBundleOrSimpleWrappingBundle(mgmt, b)) { + Iterable typesInBundle = osgi.get().getTypesFromBundle(b.getVersionedName()); + if (Iterables.isEmpty(typesInBundle)) { + log.info("Uninstalling now-empty BOM wrapper bundle "+b.getVersionedName()+" ("+b.getOsgiUniqueUrl()+")"); + osgi.get().uninstallUploadedBundle(b); + } + } + } + } + } + } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java deleted file mode 100644 index 1f528c8756..0000000000 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java +++ /dev/null @@ -1,130 +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.core.catalog.internal; - -import java.util.List; - -import javax.annotation.Nullable; - -import org.apache.brooklyn.api.mgmt.ManagementContext; -import org.apache.brooklyn.core.BrooklynFeatureEnablement; -import org.apache.brooklyn.util.text.Strings; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceReference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.annotations.Beta; -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableList; - -/** Scans bundles being added, filtered by a whitelist and blacklist, and adds catalog.bom files to the catalog. - * See karaf blueprint.xml for configuration, and tests in dist project. */ -@Beta -public class CatalogBomScanner { - - private final String ACCEPT_ALL_BY_DEFAULT = ".*"; - - private static final Logger LOG = LoggerFactory.getLogger(CatalogBomScanner.class); - - private List whiteList = ImmutableList.of(ACCEPT_ALL_BY_DEFAULT); - private List blackList = ImmutableList.of(); - - private CatalogBundleTracker catalogBundleTracker; - - public void bind(ServiceReference mgmtContextReference) throws Exception { - if (isEnabled()) { - LOG.debug("Binding management context with whiteList [{}] and blacklist [{}]", - Strings.join(getWhiteList(), "; "), - Strings.join(getBlackList(), "; ")); - - final BundleContext bundleContext = mgmtContextReference.getBundle().getBundleContext(); - ManagementContext mgmt = bundleContext.getService(mgmtContextReference); - CatalogBundleLoader bundleLoader = new CatalogBundleLoader(new SymbolicNameAccessControl(), mgmt); - - catalogBundleTracker = new CatalogBundleTracker(bundleContext, bundleLoader); - catalogBundleTracker.open(); - } - } - - public void unbind(ServiceReference mgmtContextReference) throws Exception { - if (isEnabled()) { - LOG.debug("Unbinding management context"); - if (null != catalogBundleTracker) { - CatalogBundleTracker temp = catalogBundleTracker; - catalogBundleTracker = null; - temp.close(); - } - mgmtContextReference.getBundle().getBundleContext().ungetService(mgmtContextReference); - } - } - - private boolean isEnabled() { - return BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_LOAD_BUNDLE_CATALOG_BOM); - } - - public List getWhiteList() { - return whiteList; - } - - public void setWhiteList(List whiteList) { - this.whiteList = whiteList; - } - - public void setWhiteList(String whiteListText) { - LOG.debug("Setting whiteList to ", whiteListText); - this.whiteList = Strings.parseCsv(whiteListText); - } - - public List getBlackList() { - return blackList; - } - - public void setBlackList(List blackList) { - this.blackList = blackList; - } - - public void setBlackList(String blackListText) { - LOG.debug("Setting blackList to ", blackListText); - this.blackList = Strings.parseCsv(blackListText); - } - - public class SymbolicNameAccessControl implements Predicate { - @Override - public boolean apply(@Nullable Bundle input) { - return passesWhiteAndBlacklists(input); - } - } - - private boolean passesWhiteAndBlacklists(Bundle bundle) { - return on(bundle, getWhiteList()) && !on(bundle, getBlackList()); - } - - private boolean on(Bundle bundle, List list) { - for (String candidate : list) { - final String symbolicName = bundle.getSymbolicName(); - if (symbolicName.matches(candidate.trim())) { - return true; - } - } - return false; - } - -} diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java index dbbfc760a8..1653fccfeb 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java @@ -23,7 +23,13 @@ import com.google.common.base.Preconditions; import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.mgmt.ha.OsgiManager; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.text.BrooklynVersionSyntax; +import org.osgi.framework.Bundle; public class CatalogBundleDto implements CatalogBundle { private String symbolicName; @@ -67,7 +73,13 @@ public String getSuppliedVersionString() { public String getOsgiVersionString() { return version==null ? version : BrooklynVersionSyntax.toValidOsgiVersion(version); } - + + @Override + public VersionedName getVersionedName() { + if (!isNameResolved()) return null; + return new VersionedName(getSymbolicName(), getSuppliedVersionString()); + } + @Override public String getUrl() { return url; @@ -99,5 +111,13 @@ public boolean equals(Object obj) { return true; } + public static Maybe resolve(ManagementContext mgmt, CatalogBundle b) { + if (b.isNameResolved()) return Maybe.of(b); + OsgiManager osgi = ((ManagementContextInternal)mgmt).getOsgiManager().orNull(); + if (osgi==null) return Maybe.absent("No OSGi manager"); + Maybe b2 = osgi.findBundle(b); + if (b2.isAbsent()) return Maybe.absent("Nothing installed for "+b); + return Maybe.of(new CatalogBundleDto(b2.get().getSymbolicName(), b2.get().getVersion().toString(), b.getUrl())); + } } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java index 4bf88240c5..339280e5ff 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java @@ -24,28 +24,26 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.util.Collection; import java.util.List; import java.util.Map; import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.stream.Streams; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yaml.Yamls; import org.osgi.framework.Bundle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.Yaml; import com.google.common.annotations.Beta; -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; @Beta @@ -53,25 +51,25 @@ public class CatalogBundleLoader { private static final Logger LOG = LoggerFactory.getLogger(CatalogBundleLoader.class); private static final String CATALOG_BOM_URL = "catalog.bom"; - private static final String BROOKLYN_CATALOG = "brooklyn.catalog"; - private static final String BROOKLYN_LIBRARIES = "brooklyn.libraries"; - private Predicate applicationsPermitted; private ManagementContext managementContext; - public CatalogBundleLoader(Predicate applicationsPermitted, ManagementContext managementContext) { - this.applicationsPermitted = applicationsPermitted; + public CatalogBundleLoader(ManagementContext managementContext) { this.managementContext = managementContext; } - /** - * Scan the given bundle for a catalog.bom and adds it to the catalog. - * - * @param bundle The bundle to add - * @return A list of items added to the catalog - * @throws RuntimeException if the catalog items failed to be added to the catalog - */ - public Iterable> scanForCatalog(Bundle bundle) { + public void scanForCatalog(Bundle bundle, boolean force, boolean validate) { + scanForCatalogInternal(bundle, force, validate, false); + } + + /** @deprecated since 0.12.0 */ + @Deprecated // scans a bundle which is installed but Brooklyn isn't managing (will probably remove) + public Iterable> scanForCatalogLegacy(Bundle bundle, boolean force) { + LOG.warn("Bundle "+bundle+" being loaded with deprecated legacy loader"); + return scanForCatalogInternal(bundle, force, true, true); + } + + private Iterable> scanForCatalogInternal(Bundle bundle, boolean force, boolean validate, boolean legacy) { ManagedBundle mb = ((ManagementContextInternal)managementContext).getOsgiManager().get().getManagedBundle( new VersionedName(bundle)); @@ -81,19 +79,34 @@ public CatalogBundleLoader(Predicate applicationsPermitted, ManagementCo if (null != bom) { LOG.debug("Found catalog BOM in {} {} {}", CatalogUtils.bundleIds(bundle)); String bomText = readBom(bom); - String bomWithLibraryPath = addLibraryDetails(bundle, bomText); - catalogItems = this.managementContext.getCatalog().addItems(bomWithLibraryPath, mb); - for (CatalogItem item : catalogItems) { - LOG.debug("Added to catalog: {}, {}", item.getSymbolicName(), item.getVersion()); + if (mb==null) { + LOG.warn("Bundle "+bundle+" containing BOM is not managed by Brooklyn; using legacy item installation"); + legacy = true; + } + if (legacy) { + catalogItems = this.managementContext.getCatalog().addItems(bomText, mb, force); + for (CatalogItem item : catalogItems) { + LOG.debug("Added to catalog: {}, {}", item.getSymbolicName(), item.getVersion()); + } + } else { + this.managementContext.getCatalog().addTypesFromBundleBom(bomText, mb, force); + if (validate) { + Map> validationErrors = this.managementContext.getCatalog().validateTypes( + this.managementContext.getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(mb.getVersionedName())) ); + if (!validationErrors.isEmpty()) { + throw Exceptions.propagate("Failed to install "+mb.getVersionedName()+", types "+validationErrors.keySet()+" gave errors", + Iterables.concat(validationErrors.values())); + } + } + } + + if (!legacy && BasicBrooklynCatalog.isNoBundleOrSimpleWrappingBundle(managementContext, mb)) { + ((ManagementContextInternal)managementContext).getOsgiManager().get().addInstalledWrapperBundle(mb); } } else { LOG.debug("No BOM found in {} {} {}", CatalogUtils.bundleIds(bundle)); } - if (!applicationsPermitted.apply(bundle)) { - catalogItems = removeAnyApplications(catalogItems); - } - return catalogItems; } @@ -112,63 +125,20 @@ public void removeFromCatalog(CatalogItem item) { }, " "), e); } } + + public void removeFromCatalog(VersionedName n) { + ((ManagementContextInternal)managementContext).getOsgiManager().get().uninstallCatalogItemsFromBundle(n); + } private String readBom(URL bom) { try (final InputStream ins = bom.openStream()) { return Streams.readFullyString(ins); } catch (IOException e) { - throw Exceptions.propagate("Error loading Catalog BOM from " + bom, e); + throw Exceptions.propagateAnnotated("Error loading Catalog BOM from " + bom, e); } } - private String addLibraryDetails(Bundle bundle, String bomText) { - @SuppressWarnings("unchecked") - final Map bom = (Map) Iterables.getOnlyElement(Yamls.parseAll(bomText)); - final Object catalog = bom.get(CatalogBundleLoader.BROOKLYN_CATALOG); - if (null != catalog) { - if (catalog instanceof Map) { - @SuppressWarnings("unchecked") - Map catalogMap = (Map) catalog; - addLibraryDetails(bundle, catalogMap); - } else { - LOG.warn("Unexpected syntax for {} (expected Map, but got {}), ignoring", - CatalogBundleLoader.BROOKLYN_CATALOG, catalog.getClass().getName()); - } - } - final String updatedBom = backToYaml(bom); - LOG.trace("Updated catalog bom:\n{}", updatedBom); - return updatedBom; - } - - private void addLibraryDetails(Bundle bundle, Map catalog) { - if (!catalog.containsKey(CatalogBundleLoader.BROOKLYN_LIBRARIES)) { - catalog.put(CatalogBundleLoader.BROOKLYN_LIBRARIES, MutableList.of()); - } - final Object librarySpec = catalog.get(CatalogBundleLoader.BROOKLYN_LIBRARIES); - if (!(librarySpec instanceof List)) { - throw new RuntimeException("expected " + CatalogBundleLoader.BROOKLYN_LIBRARIES + " to be a list, but got " - + (librarySpec == null ? "null" : librarySpec.getClass().getName())); - } - @SuppressWarnings("unchecked") - List> libraries = (List>) librarySpec; - if (bundle.getSymbolicName() == null || bundle.getVersion() == null) { - throw new IllegalStateException("Cannot scan " + bundle + " for catalog files: name or version is null"); - } - libraries.add(ImmutableMap.of( - "name", bundle.getSymbolicName(), - "version", bundle.getVersion().toString())); - LOG.debug("library spec is {}", librarySpec); - } - - private String backToYaml(Map bom) { - final DumperOptions options = new DumperOptions(); - options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - options.setPrettyFlow(true); - return new Yaml(options).dump(bom); - } - - private Iterable> removeAnyApplications( - Iterable> catalogItems) { + private Iterable> removeApplications(Iterable> catalogItems) { List> result = MutableList.of(); diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleTracker.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleTracker.java deleted file mode 100644 index 074d7f7352..0000000000 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleTracker.java +++ /dev/null @@ -1,78 +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.core.catalog.internal; - -import org.apache.brooklyn.api.catalog.CatalogItem; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleEvent; -import org.osgi.util.tracker.BundleTracker; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.annotations.Beta; - -@Beta -public class CatalogBundleTracker extends BundleTracker>> { - - private static final Logger LOG = LoggerFactory.getLogger(CatalogBundleTracker.class); - - private CatalogBundleLoader catalogBundleLoader; - - public CatalogBundleTracker(BundleContext bundleContext, CatalogBundleLoader catalogBundleLoader) { - super(bundleContext, Bundle.ACTIVE, null); - this.catalogBundleLoader = catalogBundleLoader; - } - - /** - * Scans the bundle being added for a catalog.bom file and adds any entries in it to the catalog. - * - * @param bundle The bundle being added to the bundle context. - * @param bundleEvent The event of the addition. - * @return The items added to the catalog; these will be tracked by the {@link BundleTracker} mechanism - * and supplied to the {@link #removedBundle(Bundle, BundleEvent, Iterable)} method. - * @throws RuntimeException if the catalog items failed to be added to the catalog - */ - @Override - public Iterable> addingBundle(Bundle bundle, BundleEvent bundleEvent) { - return catalogBundleLoader.scanForCatalog(bundle); - } - - /** - * Remove the given entries from the catalog, related to the given bundle. - * - * @param bundle The bundle being removed to the bundle context. - * @param bundleEvent The event of the removal. - * @param items The items being removed - * @throws RuntimeException if the catalog items failed to be added to the catalog - */ - @Override - public void removedBundle(Bundle bundle, BundleEvent bundleEvent, Iterable> items) { - if (!items.iterator().hasNext()) { - return; - } - LOG.debug("Unloading catalog BOM entries from {} {} {}", CatalogUtils.bundleIds(bundle)); - for (CatalogItem item : items) { - LOG.debug("Unloading {} {} from catalog", item.getSymbolicName(), item.getVersion()); - - catalogBundleLoader.removeFromCatalog(item); - } - } -} diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogClasspathDo.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogClasspathDo.java index bd0c3c7b81..70a8374373 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogClasspathDo.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogClasspathDo.java @@ -111,7 +111,9 @@ public CatalogClasspathDo(CatalogDo catalog) { /** causes all scanning-based classpaths to scan the classpaths * (but does _not_ load all JARs) */ - // TODO this does a Java scan; we also need an OSGi scan which uses the OSGi classloaders when loading for scanning and resolving dependencies + // NOTE this does a Java scan; we also need an OSGi scan which uses the OSGi classloaders when loading for scanning and resolving dependencies + // TODO AH thinks we should delete ALL this old catalog stuff and scanning and annotations; it's redundant now that we use bundles, + // and scanning doesn't fit with the OSGi way of doing things synchronized void load() { if (classpath == null || isLoaded) return; diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogDo.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogDo.java index 48529297f4..39cc8d231d 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogDo.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogDo.java @@ -137,7 +137,7 @@ private void loadCatalogItems(boolean failOnLoadError) { } catch (Exception e) { Exceptions.propagateIfFatal(e); if (failOnLoadError) { - Exceptions.propagate(e); + Exceptions.propagateAnnotated("Loading bundles for catalog item " + entry.getCatalogItemId() + " failed", e); } else { log.error("Loading bundles for catalog item " + entry + " failed: " + e.getMessage(), e); } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java index 45e7e7aba9..f906344101 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java @@ -34,6 +34,7 @@ import org.apache.brooklyn.util.core.flags.TypeCoercions; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.FatalRuntimeException; +import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException; import org.apache.brooklyn.util.exceptions.RuntimeInterruptedException; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.JavaClassNames; @@ -394,15 +395,18 @@ public void handleException(Throwable throwable, Object details) { if (throwable instanceof RuntimeInterruptedException) throw (RuntimeInterruptedException) throwable; - String throwableText = Exceptions.collapseText(throwable); - log.error("Error loading catalog item '"+details+"': "+throwableText); - log.debug("Trace for error loading catalog item '"+details+"': "+throwableText, throwable); + if (details instanceof CatalogItem) { + if (((CatalogItem)details).getCatalogItemId() != null) { + details = ((CatalogItem)details).getCatalogItemId(); + } + } + PropagatedRuntimeException wrap = new PropagatedRuntimeException("Error loading catalog item "+details, throwable); + log.debug("Trace for: "+wrap, wrap); - // TODO give more detail when adding - ((ManagementContextInternal)getManagementContext()).errors().add(throwable); + ((ManagementContextInternal)getManagementContext()).errors().add(wrap); if (isStartingUp && failOnStartupErrors) { - throw new FatalRuntimeException("Unable to load catalog item '"+details+"': "+throwableText, throwable); + throw new FatalRuntimeException("Unable to load catalog item "+details, wrap); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemDtoAbstract.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemDtoAbstract.java index 9047fcb543..9f6e3d478c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemDtoAbstract.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemDtoAbstract.java @@ -29,6 +29,7 @@ import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.mgmt.rebind.RebindSupport; import org.apache.brooklyn.api.mgmt.rebind.mementos.CatalogItemMemento; +import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.mgmt.rebind.BasicCatalogItemRebindSupport; import org.apache.brooklyn.core.objs.AbstractBrooklynObject; @@ -386,11 +387,11 @@ protected void setTags(Set tags) { /** * Parses an instance of CatalogLibrariesDto from the given List. Expects the list entries - * to be either Strings or Maps of String -> String. Will skip items that are not. + * to be either Strings or Maps of String -> String or bundles. Will skip items that are not. *

* If a string is supplied, this tries heuristically to identify whether a reference is a bundle or a URL, as follows: * - if the string contains a slash, it is treated as a URL (or classpath reference), e.g. /file.txt; - * - if the string is {@link RegisteredTypeNaming#isGoodTypeColonVersion(String)} with an OSGi version it is treated as a bundle, e.g. file:1; + * - if the string is {@link RegisteredTypeNaming#isGoodBrooklynTypeColonVersion(String))} with an OSGi version it is treated as a bundle, e.g. file:1; * - if the string is ambiguous (has a single colon) a warning is given, * and typically it is treated as a URL because OSGi versions are needed here, e.g. file:v1 is a URL, * but for a transitional period (possibly ending in 0.13 as warning is introduced in 0.12) for compatibility with previous versions, @@ -445,6 +446,10 @@ public static Collection parseLibraries(Collection possibleLib } dto.add(new CatalogBundleDto(name, version, url)); + } else if (object instanceof OsgiBundleWithUrl) { + dto.add(new CatalogBundleDto( ((OsgiBundleWithUrl)object).getSymbolicName(), ((OsgiBundleWithUrl)object).getSuppliedVersionString(), ((OsgiBundleWithUrl)object).getUrl() )); + } else if (object instanceof VersionedName) { + dto.add(new CatalogBundleDto( ((VersionedName)object).getSymbolicName(), ((VersionedName)object).getVersionString(), null )); } else { LOG.debug("Unexpected entry in libraries list neither string nor map: " + object); } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java index bc744a0980..3e25384f08 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.List; +import java.util.NoSuchElementException; import javax.annotation.Nullable; @@ -31,6 +32,7 @@ import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; import org.apache.brooklyn.api.objs.BrooklynObject; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; +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.BrooklynLogging; @@ -50,7 +52,9 @@ import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Time; import org.osgi.framework.Bundle; @@ -97,11 +101,11 @@ public static BrooklynClassLoadingContext getClassLoadingContext(Entity entity) log.warn("Cannot load "+catId+" to get classloader for "+entity+"; will try with standard loader, but might fail subsequently"); return JavaBrooklynClassLoadingContext.create(mgmt); } - return newClassLoadingContext(mgmt, cat.get()); + return newClassLoadingContext(mgmt, cat.get(), JavaBrooklynClassLoadingContext.create(mgmt)); } public static BrooklynClassLoadingContext newClassLoadingContext(@Nullable ManagementContext mgmt, String catalogItemId, Collection libraries) { - return newClassLoadingContext(mgmt, catalogItemId, libraries, null); + return newClassLoadingContext(mgmt, catalogItemId, libraries, JavaBrooklynClassLoadingContext.create(mgmt)); } @Deprecated /** @deprecated since 0.9.0; becoming private because we should now always have a registered type callers can pass instead of the catalog item id */ @@ -146,12 +150,12 @@ public static BrooklynClassLoadingContext newClassLoadingContext(@Nullable Manag } public static BrooklynClassLoadingContext newClassLoadingContextForCatalogItems( - ManagementContext managementContext, String catalogItemId, List searchPath) { + ManagementContext managementContext, String primaryItemId, List searchPath) { BrooklynClassLoadingContextSequential seqLoader = new BrooklynClassLoadingContextSequential(managementContext); - addCatalogItemContext(managementContext, seqLoader, catalogItemId); + addSearchItem(managementContext, seqLoader, primaryItemId, false /* primary ID may be temporary */); for (String searchId : searchPath) { - addCatalogItemContext(managementContext, seqLoader, searchId); + addSearchItem(managementContext, seqLoader, searchId, true); } return seqLoader; } @@ -175,7 +179,7 @@ public static void installLibraries(ManagementContext managementContext, @Nullab Stopwatch timer = Stopwatch.createStarted(); List results = MutableList.of(); for (CatalogBundle bundleUrl : libraries) { - OsgiBundleInstallationResult result = osgi.get().installDeferredStart(BasicManagedBundle.of(bundleUrl), null).get(); + OsgiBundleInstallationResult result = osgi.get().installDeferredStart(BasicManagedBundle.of(bundleUrl), null, true).get(); if (log.isDebugEnabled()) { logDebugOrTraceIfRebinding(log, "Installation of library "+bundleUrl+": "+result); } @@ -341,29 +345,62 @@ public static void setDisabled(ManagementContext mgmt, String symbolicNameAndOpt @Deprecated public static void setDeprecated(ManagementContext mgmt, String symbolicName, String version, boolean newValue) { CatalogItem item = mgmt.getCatalog().getCatalogItem(symbolicName, version); - Preconditions.checkNotNull(item, "No such item: "+symbolicName+" v "+version); - item.setDeprecated(newValue); - mgmt.getCatalog().persist(item); + if (item!=null) { + item.setDeprecated(newValue); + mgmt.getCatalog().persist(item); + } else { + RegisteredType type = mgmt.getTypeRegistry().get(symbolicName, version); + if (type!=null) { + RegisteredTypes.setDeprecated(type, newValue); + } else { + throw new NoSuchElementException(symbolicName+":"+version); + } + } } /** @deprecated since it was introduced in 0.9.0; TBD where this should live */ @Deprecated public static void setDisabled(ManagementContext mgmt, String symbolicName, String version, boolean newValue) { CatalogItem item = mgmt.getCatalog().getCatalogItem(symbolicName, version); - Preconditions.checkNotNull(item, "No such item: "+symbolicName+" v "+version); - item.setDisabled(newValue); - mgmt.getCatalog().persist(item); + if (item!=null) { + item.setDisabled(newValue); + mgmt.getCatalog().persist(item); + } else { + RegisteredType type = mgmt.getTypeRegistry().get(symbolicName, version); + if (type!=null) { + RegisteredTypes.setDisabled(type, newValue); + } else { + throw new NoSuchElementException(symbolicName+":"+version); + } + } } - private static void addCatalogItemContext(ManagementContext managementContext, BrooklynClassLoadingContextSequential loader, String catalogItemId) { - RegisteredType item = managementContext.getTypeRegistry().get(catalogItemId); - + private static void addSearchItem(ManagementContext managementContext, BrooklynClassLoadingContextSequential loader, String itemId, boolean warnIfNotFound) { + OsgiManager osgi = ((ManagementContextInternal)managementContext).getOsgiManager().orNull(); + boolean didSomething = false; + if (osgi!=null) { + ManagedBundle bundle = osgi.getManagedBundle(VersionedName.fromString(itemId)); + if (bundle!=null) { + loader.add( newClassLoadingContext(managementContext, itemId, MutableSet.of(bundle)) ); + didSomething = true; + // but also load entities, if name is same as a bundle and libraries are set on entity + } + } + + RegisteredType item = managementContext.getTypeRegistry().get(itemId); if (item != null) { BrooklynClassLoadingContext itemLoader = newClassLoadingContext(managementContext, item); loader.add(itemLoader); - } else { - // TODO review what to do here - log.warn("Can't find catalog item " + catalogItemId); + didSomething = true; + } + + if (!didSomething) { + if (warnIfNotFound) { + log.warn("Can't find catalog item " + itemId+" when searching; a search path may be incomplete and other errors may follow"); + } else { + log.trace("Can't find catalog item " + itemId+" when searching; ignoring as this can be normal in setup/scans, " + + "but it can also mean a search path may be incomplete and other errors may follow"); + } } } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java index f320e5d0e0..9d95542648 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java @@ -32,6 +32,7 @@ import org.apache.brooklyn.api.sensor.EnricherSpec; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContextSequential; +import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext; import org.apache.brooklyn.core.objs.BasicSpecParameter; import org.apache.brooklyn.core.plan.PlanToSpecTransformer; import org.apache.brooklyn.core.typereg.UnsupportedTypePlanException; @@ -77,11 +78,6 @@ public > SpecT c @SuppressWarnings("deprecation") String javaType = item.getJavaType(); if (javaType != null) { - // TODO This log.warn previously said "Using old-style xml catalog items with java type attribute". - // However, nothing in this code is specific to catalog.xml. Perhaps that is the only way this code - // can be reached? We should investigate further when we have time - if we can confirm it was only - // used for catalog.xml, then this can be deleted. - log.warn("Deprecated functionality (since 0.9.0). Using old-style java type attribute for " + item); Class type; try { // java types were deprecated before we added osgi support so this isn't necessary, @@ -90,6 +86,7 @@ public > SpecT c final BrooklynClassLoadingContextSequential ctx = new BrooklynClassLoadingContextSequential(mgmt); ctx.add(CatalogUtils.newClassLoadingContextForCatalogItems(mgmt, item.getCatalogItemId(), item.getCatalogItemIdSearchPath())); + ctx.addSecondary(JavaBrooklynClassLoadingContext.create(mgmt)); type = ctx.loadClass(javaType); } catch (Exception e) { Exceptions.propagateIfFatal(e); @@ -117,7 +114,7 @@ public > SpecT c SpecT untypedSpc = (SpecT) spec; return untypedSpc; } else { - throw new UnsupportedTypePlanException(getClass().getName() + " parses only old-style catalog items containing javaType"); + throw new UnsupportedTypePlanException(getClass().getName() + " parses only old-style catalog items containing only 'type: JavaClass' or javaType in DTO"); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java b/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java index a7615133b9..b28e56047e 100644 --- a/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java +++ b/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java @@ -39,6 +39,7 @@ import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.config.StringConfigMap; import org.apache.brooklyn.core.config.ConfigPredicates; @@ -47,6 +48,7 @@ import org.apache.brooklyn.core.location.internal.LocationInternal; import org.apache.brooklyn.core.mgmt.internal.LocalLocationManager; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.location.localhost.LocalhostLocationResolver; import org.apache.brooklyn.util.collections.MutableList; @@ -71,74 +73,46 @@ /** * See {@link LocationRegistry} for general description. *

- * TODO The relationship between the catalog and the location registry is a bit messy. - * For all existing code, the location registry is the definitive way to resolve - * locations. + * This implementation unfortunately has to deal with location definitions coming from three places: + * + *

  • brooklyn.properties + *
  • {@link BrooklynCatalog} + *
  • {@link BrooklynTypeRegistry} + *

    + * The first is read on init in the call to {@link #updateDefinedLocations()}. + * The latter two are not loaded here but are treated as lookups to {@link BrooklynTypeRegistry}. *

    - * Any location item added to the catalog must therefore be registered here. - * Whenever an item is added to the catalog, it will automatically call - * {@link #updateDefinedLocation(RegisteredType)}. Similarly, when a location - * is deleted from the catalog it will call {@link #removeDefinedLocation(RegisteredType)}. + * (Prior to 0.12.0 items in {@link BrooklynCatalog} were scanned on init here, and then + * it updated this on additions and removals there. That is no longer done with type registry.) *

    - * However, the location item in the catalog has an unparsed blob of YAML, which contains - * important things like the type and the config of the location. This is only parsed when - * {@link BrooklynCatalog#createSpec(CatalogItem)} is called. We therefore jump through - * some hoops to wire together the catalog and the registry. + * We should consider deprecating this as a place where locations can be defined (switch to type registry only). *

    - * To add a location to the catalog, and then to resolve a location that is in the catalog, - * it goes through the following steps: - * - *

      - *
    1. Call {@link BrooklynCatalog#addItems(String)} - *
        - *
      1. This automatically calls {@link #updateDefinedLocation(RegisteredType)} - *
      2. A LocationDefinition is creating, using as its id the {@link RegisteredType#getSymbolicName()}. - * The definition's spec is {@code brooklyn.catalog::}, - *
      - *
    2. A blueprint can reference the catalog item using its symbolic name, - * such as the YAML {@code location: my-new-location}. - * (this feels similar to the "named locations"). - *
        - *
      1. This automatically calls {@link #getLocationManaged(String)}. - *
      2. The LocationDefinition is found by lookig up this name. - *
      3. The {@link LocationDefiniton.getSpec()} is retrieved; the right {@link LocationResolver} is - * found for it. - *
      4. This uses the {@link CatalogLocationResolver}, because the spec starts with {@code brooklyn.catalog:}. - *
      5. This resolver extracts from the spec the :, and looks up the - * location item using the {@link BrooklynTypeRegistry}. - *
      6. It then creates a {@link LocationSpec} by calling {@link BrooklynTypeRegistry#createSpec(RegisteredType)}. - *
          - *
        1. This first tries to use the type (that is in the YAML) as a simple Java class. - *
        2. If that fails, it will resolve the type using {@link #resolve(String, Boolean, Map)}, which - * returns an actual location object. - *
        3. It extracts from that location object the appropriate metadata to create a {@link LocationSpec}, - * returns the spec and discards the location object. - *
        - *
      7. The resolver creates the {@link Location} from the {@link LocationSpec} - *
      - *
    - * - * TODO we should change the registry to be a pass-through facade on top of the catalog, - * and shift to preferring catalog access mechanisms. - * this brings it in line with how we do other things; - * also this does not understand versions. - * to do this we will need to: - *
  • update the catalog on addition, setting a plan (ensuring serialization); - * in case of a definition CHANGED in brooklyn.properties give an error if it means the plan has changed - * (user could then remove from brooklyn.properties, if persistence on, or apply the update in the catalog; - * ie similar semantics to defining an initial catalog via the CLI) - *
  • find and return the RegisteredType from the type-registry/catalog here + * However this is used for items from brooklyn.properties -- we could create a bundle to store those + * to help with migration, and then deprecate/block changes to those after the bundle is created. + * However properties are the only way currently to set default data used for various clouds; + * either we'd need to deprecate that also, or find a way to support that in a bundled world. + *

    + * This is also used in a few other places, such as clocker and the server pool entity. + * Things here are never persisted though, so it is those callers' responsibility to recreate. + * Alternatively (better) would be for them to add items to the catalog instead of here. + *

    + * Or we keep this lying around for now. Probably not worth the pain above. *

    - * Once done, update the UI use /v1/catalog/locations instead of /v1/locations - * (currently the latter is the only way to list locations known in the LocationRegistry - * ie those from brookln.properties.) + * Also note the location item in the catalog has an unparsed blob of YAML, which contains + * important things like the type and the config of the location. This is only parsed when + * {@link BrooklynTypeRegistry#createSpec(RegisteredType)} is called, so we do that for + * lookup possibly more often than one might expect. (If a time hole this could be cached.) + * We also need to make sure some of the resolvers here can be used to identify types, so + * when loading types, the spec creation routines are able to call this to + * {@link #resolve(String, Boolean, Map)} (that returns a {@link Location} not a spec, + * but the {@link Location} instance is discarded). + *

    + * Finally note /v1/catalog/locations does not list items stored here; + * only /v1/locations does that. */ @SuppressWarnings({"rawtypes","unchecked"}) public class BasicLocationRegistry implements LocationRegistry { - // TODO save / serialize - // (we persist live locations, ie those in the LocationManager, but not "catalog" locations, ie those in this Registry) - public static final Logger log = LoggerFactory.getLogger(BasicLocationRegistry.class); /** @@ -194,29 +168,71 @@ public boolean registerResolver(LocationResolver r) { } @Override - public Map getDefinedLocations() { + public Map getDefinedLocations(boolean includeThingsWeAreFacadeFor) { + Map result = MutableMap.of(); synchronized (definedLocations) { - return ImmutableMap.copyOf(definedLocations); + result.putAll(definedLocations); + } + for (RegisteredType rt: mgmt.getTypeRegistry().getMatching(RegisteredTypePredicates.IS_LOCATION)) { + result.put(rt.getId(), newDefinedLocation(rt)); } + return result; + } + + @Override @Deprecated + public Map getDefinedLocations() { + return getDefinedLocations(true); } @Override public LocationDefinition getDefinedLocationById(String id) { - return definedLocations.get(id); + return getDefinedLocation(id, true); } @Override public LocationDefinition getDefinedLocationByName(String name) { + return getDefinedLocation(name, false); + } + + private LocationDefinition getDefinedLocation(String nameOrId, boolean isId) { + RegisteredType lt = mgmt.getTypeRegistry().get(nameOrId, RegisteredTypeLoadingContexts.withSpecSuperType(null, LocationSpec.class)); + if (lt!=null) { + return newDefinedLocation(lt); + } + synchronized (definedLocations) { - for (LocationDefinition l: definedLocations.values()) { - if (l.getName().equals(name)) return l; + if (isId) { + LocationDefinition ld = definedLocations.get(nameOrId); + if (ld!=null) { + return ld; + } + } else { + for (LocationDefinition l: definedLocations.values()) { + if (l.getName().equals(nameOrId)) return l; + } + } + } + + // fall back to ignoring supertypes, in case they weren't set + lt = mgmt.getTypeRegistry().get(nameOrId); + if (lt!=null && lt.getSuperTypes().isEmpty()) { + if (lt.getKind()!=RegisteredTypeKind.UNRESOLVED) { + // don't warn when still trying to resolve + log.warn("Location registry only found "+nameOrId+" when ignoring supertypes; check it is correctly resolved."); } - return null; + return newDefinedLocation(lt); } + + return null; } - @Override + @Override @Deprecated public void updateDefinedLocation(LocationDefinition l) { + updateDefinedLocationNonPersisted(l); + } + + @Override + public void updateDefinedLocationNonPersisted(LocationDefinition l) { synchronized (definedLocations) { definedLocations.put(l.getId(), l); } @@ -226,7 +242,9 @@ public void updateDefinedLocation(LocationDefinition l) { * Converts the given item from the catalog into a LocationDefinition, and adds it * to the registry (overwriting anything already registered with the id * {@link CatalogItem#getCatalogItemId()}. + * @deprecated since 0.12.0 this class is a facade so method no longer wanted */ + @Deprecated // not used public void updateDefinedLocation(CatalogItem> item) { String id = item.getCatalogItemId(); String symbolicName = item.getSymbolicName(); @@ -241,17 +259,22 @@ public void updateDefinedLocation(CatalogItem> item) { * Converts the given item from the catalog into a LocationDefinition, and adds it * to the registry (overwriting anything already registered with the id * {@link RegisteredType#getId()}. + * @deprecated since 0.12.0 this class is a facade so method no longer wanted */ + @Deprecated // not used public void updateDefinedLocation(RegisteredType item) { + BasicLocationDefinition locDefinition = newDefinedLocation(item); + updateDefinedLocation(locDefinition); + } + + protected BasicLocationDefinition newDefinedLocation(RegisteredType item) { String id = item.getId(); - String symbolicName = item.getSymbolicName(); String spec = CatalogLocationResolver.createLegacyWrappedReference(id); - Map config = ImmutableMap.of(); - BasicLocationDefinition locDefinition = new BasicLocationDefinition(symbolicName, symbolicName, spec, config); - - updateDefinedLocation(locDefinition); + return new BasicLocationDefinition(id, item.getSymbolicName(), spec, ImmutableMap.of()); } + /** @deprecated since 0.12.0 this class is a facade so method no longer wanted */ + @Deprecated // not used public void removeDefinedLocation(CatalogItem> item) { removeDefinedLocation(item.getSymbolicName()); } @@ -290,11 +313,6 @@ public void updateDefinedLocations() { } if (log.isDebugEnabled()) log.debug("Found "+count+" defined locations from properties (*.named.* syntax): "+definedLocations.values()); - - for (RegisteredType item: mgmt.getTypeRegistry().getMatching(RegisteredTypePredicates.IS_LOCATION)) { - updateDefinedLocation(item); - count++; - } } } diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java index c8248f12d8..001d8193f0 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java @@ -39,6 +39,9 @@ public class BrooklynTags { public static final String NOTES_KIND = "notes"; public static final String OWNER_ENTITY_ID = "owner_entity_id"; public static final String ICON_URL = "icon_url"; + /** tag on a registered type indicating that an item is intended to be used as a template, + * and does not have to resolve */ + public static final Object CATALOG_TEMPLATE = "catalog_template"; public static class NamedStringTag implements Serializable { private static final long serialVersionUID = 7932098757009051348L; 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 e4ffb5490d..bd5176771a 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 @@ -23,17 +23,23 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult.ResultCode; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.typereg.BasicManagedBundle; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.core.osgi.BundleMaker; import org.apache.brooklyn.util.core.osgi.Osgis; @@ -53,16 +59,13 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Objects; +import com.google.common.collect.Iterables; // package-private so we can move this one if/when we move OsgiManager class OsgiArchiveInstaller { private static final Logger log = LoggerFactory.getLogger(OsgiArchiveInstaller.class); - // must be 1.0; see bottom of - // http://www.eclipse.org/virgo/documentation/virgo-documentation-3.7.0.M01/docs/virgo-user-guide/html/ch02s02.html - private static final String OSGI_MANIFEST_VERSION_VALUE = "1.0"; - final private OsgiManager osgiManager; private ManagedBundle suppliedKnownBundleMetadata; private InputStream zipIn; @@ -71,6 +74,7 @@ class OsgiArchiveInstaller { private boolean loadCatalogBom = true; private boolean force = false; private boolean deferredStart = false; + private boolean validateTypes = true; private File zipFile; private Manifest discoveredManifest; @@ -101,7 +105,11 @@ public void setForce(boolean force) { public void setDeferredStart(boolean deferredStart) { this.deferredStart = deferredStart; - } + } + + public void setValidateTypes(boolean validateTypes) { + this.validateTypes = validateTypes; + } private ManagementContextInternal mgmt() { return (ManagementContextInternal) osgiManager.mgmt; @@ -185,9 +193,9 @@ private void discoverManifestFromCatalogBom(boolean isCatalogBomRequired) { } catch (IOException e) { throw new IllegalArgumentException("Invalid ZIP/JAR archive: "+e); } - ZipEntry bom = zf.getEntry("catalog.bom"); + ZipEntry bom = zf.getEntry(BasicBrooklynCatalog.CATALOG_BOM); if (bom==null) { - bom = zf.getEntry("/catalog.bom"); + bom = zf.getEntry("/"+BasicBrooklynCatalog.CATALOG_BOM); } if (bom==null) { if (isCatalogBomRequired) { @@ -231,7 +239,7 @@ private void updateManifestFromAllSourceInformation() { throw new IllegalArgumentException("Missing bundle version in BOM or MANIFEST"); } if (discoveredManifest.getMainAttributes().getValue(Attributes.Name.MANIFEST_VERSION)==null) { - discoveredManifest.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), OSGI_MANIFEST_VERSION_VALUE); + discoveredManifest.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), BasicBrooklynCatalog.OSGI_MANIFEST_VERSION_VALUE); manifestNeedsUpdating = true; } if (manifestNeedsUpdating) { @@ -275,6 +283,8 @@ public ReferenceWithError install() { updateManifestFromAllSourceInformation(); if (result.code!=null) return ReferenceWithError.newInstanceWithoutError(result); assert inferredMetadata.isNameResolved() : "Should have resolved "+inferredMetadata; + assert inferredMetadata instanceof BasicManagedBundle : "Only BasicManagedBundles supported"; + ((BasicManagedBundle)inferredMetadata).setChecksum(getChecksum(new ZipFile(zipFile))); final boolean updating; result.metadata = osgiManager.getManagedBundle(inferredMetadata.getVersionedName()); @@ -296,10 +306,18 @@ public ReferenceWithError install() { if (canUpdate()) { result.bundle = osgiManager.framework.getBundleContext().getBundle(result.getMetadata().getOsgiUniqueUrl()); if (result.getBundle()==null) { - throw new IllegalStateException("Detected already managing bundle "+result.getMetadata().getVersionedName()+" but framework cannot find it"); + log.warn("Brooklyn thought is was already managing bundle "+result.getMetadata().getVersionedName()+" but it's not installed to framework; reinstalling it"); + updating = false; + } else { + updating = true; } - updating = true; } else { + if (result.getMetadata().getChecksum()==null || inferredMetadata.getChecksum()==null) { + log.warn("Missing bundle checksum data for "+result+"; assuming bundle replacement is permitted"); + } else if (!Objects.equal(result.getMetadata().getChecksum(), inferredMetadata.getChecksum())) { + throw new IllegalArgumentException("Bundle "+result.getMetadata().getVersionedName()+" already installed; " + + "cannot install a different bundle at a same non-snapshot version"); + } result.setIgnoringAlreadyInstalled(); return ReferenceWithError.newInstanceWithoutError(result); } @@ -308,11 +326,16 @@ public ReferenceWithError install() { // no such managed bundle Maybe b = Osgis.bundleFinder(osgiManager.framework).symbolicName(result.getMetadata().getSymbolicName()).version(result.getMetadata().getSuppliedVersionString()).find(); if (b.isPresent()) { - // if it's non-brooklyn installed then fail - // (e.g. someone trying to install brooklyn or guice through this mechanism!) - result.bundle = b.get(); - result.code = OsgiBundleInstallationResult.ResultCode.ERROR_INSTALLING_BUNDLE; - throw new IllegalStateException("Bundle "+result.getMetadata().getVersionedName()+" already installed in framework but not managed by Brooklyn; cannot install or update through Brooklyn"); + // bundle already installed to OSGi subsystem but brooklyn not aware of it; + // this will often happen on a karaf restart so don't be too strict! + // in this case let's uninstall it to make sure we have the right bundle and checksum + // (in case where user has replaced a JAR file in persisted state, + // or where they osgi installed something and are now uploading it or something else) + // but let's just assume it's the same; worst case if not user will + // have to uninstall it then reinstall it to do the replacement + // (means you can't just replace a JAR in persisted state however) + log.debug("Brooklyn install of "+result.getMetadata().getVersionedName()+" detected already loaded in OSGi; uninstalling that to reinstall as Brooklyn-managed"); + b.get().uninstall(); } // normal install updating = false; @@ -321,7 +344,6 @@ public ReferenceWithError install() { startedInstallation = true; try (InputStream fin = new FileInputStream(zipFile)) { if (!updating) { - // install new assert result.getBundle()==null; result.bundle = osgiManager.framework.getBundleContext().installBundle(result.getMetadata().getOsgiUniqueUrl(), fin); } else { @@ -336,14 +358,14 @@ public ReferenceWithError install() { if (!updating) { osgiManager.managedBundlesRecord.addManagedBundle(result); result.code = OsgiBundleInstallationResult.ResultCode.INSTALLED_NEW_BUNDLE; - result.message = "Installed "+result.getMetadata().getVersionedName()+" with ID "+result.getMetadata().getId(); + result.message = "Installed Brooklyn catalog bundle "+result.getMetadata().getVersionedName()+" with ID "+result.getMetadata().getId()+" ["+result.bundle.getBundleId()+"]"; mgmt().getRebindManager().getChangeListener().onManaged(result.getMetadata()); } else { result.code = OsgiBundleInstallationResult.ResultCode.UPDATED_EXISTING_BUNDLE; - result.message = "Updated "+result.getMetadata().getVersionedName()+" as existing ID "+result.getMetadata().getId(); + result.message = "Updated Brooklyn catalog bundle "+result.getMetadata().getVersionedName()+" as existing ID "+result.getMetadata().getId()+" ["+result.bundle.getBundleId()+"]"; mgmt().getRebindManager().getChangeListener().onChanged(result.getMetadata()); } - log.info(result.message); + log.debug(result.message + " (in osgi container)"); // setting the above before the code below means if there is a problem starting or loading catalog items // a user has to remove then add again, or forcibly reinstall; @@ -360,6 +382,7 @@ public ReferenceWithError install() { public void run() { if (start) { try { + log.debug("Starting bundle "+result.getVersionedName()); result.bundle.start(); } catch (BundleException e) { throw Exceptions.propagate(e); @@ -371,7 +394,10 @@ public void run() { osgiManager.uninstallCatalogItemsFromBundle( result.getVersionedName() ); // (ideally removal and addition would be atomic) } - for (CatalogItem ci: osgiManager.loadCatalogBom(result.bundle)) { + osgiManager.loadCatalogBom(result.bundle, force, validateTypes); + 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()); } } @@ -379,15 +405,29 @@ public void run() { }; if (deferredStart) { result.deferredStart = startRunnable; + log.debug(result.message+" (Brooklyn load deferred)"); } else { startRunnable.run(); + if (!result.catalogItemsInstalled.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)); + 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); + } + } else { + log.debug(result.message+" (into Brooklyn), with no catalog items"); + } } return ReferenceWithError.newInstanceWithoutError(result); } catch (Exception e) { Exceptions.propagateIfFatal(e); - result.code = startedInstallation ? OsgiBundleInstallationResult.ResultCode.ERROR_INSTALLING_BUNDLE : OsgiBundleInstallationResult.ResultCode.ERROR_PREPARING_BUNDLE; + result.code = startedInstallation ? OsgiBundleInstallationResult.ResultCode.ERROR_LAUNCHING_BUNDLE : OsgiBundleInstallationResult.ResultCode.ERROR_PREPARING_BUNDLE; result.message = "Bundle "+inferredMetadata+" failed "+ (startedInstallation ? "installation" : "preparation") + ": " + Exceptions.collapseText(e); return ReferenceWithError.newInstanceThrowingError(result, new IllegalStateException(result.message, e)); @@ -396,6 +436,21 @@ public void run() { } } + private static String getChecksum(ZipFile zf) { + // checksum should ignore time/date stamps on files - just look at entries and contents. also ignore order. + // (tests fail without time/date is one reason, but really if a person rebuilds a ZIP that is the same + // files we should treat it as identical) + try { + Map entriesToChecksum = MutableMap.of(); + for (ZipEntry ze: Collections.list(zf.entries())) { + entriesToChecksum.put(ze.getName(), Streams.getMd5Checksum(zf.getInputStream(ze))); + } + return Streams.getMd5Checksum(Streams.newInputStreamWithContents(new TreeMap<>(entriesToChecksum).toString())); + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + private boolean canUpdate() { // only update if forced, or it's a snapshot for which a byte stream is supplied // (IE don't update a snapshot verison every time its URL is referenced in a 'libraries' section) 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 c3a725a6bb..c611553241 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 @@ -37,11 +37,23 @@ public class OsgiBundleInstallationResult { Runnable deferredStart; public enum ResultCode { - INSTALLED_NEW_BUNDLE, - UPDATED_EXISTING_BUNDLE, - IGNORING_BUNDLE_AREADY_INSTALLED, - ERROR_PREPARING_BUNDLE, - ERROR_INSTALLING_BUNDLE + INSTALLED_NEW_BUNDLE(false), + UPDATED_EXISTING_BUNDLE(false), + /** Bundle is already installed at exact same version and same contents; safely ignoring + * (safe in that behaviour won't be different or dangerous; + * could potentially be surprising, but ability to idempotently install things is nicer) */ + IGNORING_BUNDLE_AREADY_INSTALLED(false), + /** bundle could not be made insto a state where it could be installed; bundle is not installed, even if forced */ + ERROR_PREPARING_BUNDLE(true), + /** 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); + + final boolean isError; + ResultCode(boolean isError) { this.isError = isError; } + + public boolean isError() { return isError; } } final List catalogItemsInstalled = MutableList.of(); 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 dcabe00144..98c962e6e8 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 @@ -22,6 +22,7 @@ import java.io.InputStream; import java.net.URL; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.List; @@ -40,12 +41,12 @@ import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.config.ConfigKey; -import org.apache.brooklyn.core.BrooklynFeatureEnablement; 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.server.BrooklynServerConfig; import org.apache.brooklyn.core.server.BrooklynServerPaths; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; @@ -72,8 +73,6 @@ import com.google.common.annotations.Beta; import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; @@ -103,6 +102,7 @@ public class OsgiManager { private Set bundlesAtStartup; private File osgiCacheDir; final ManagedBundlesRecord managedBundlesRecord = new ManagedBundlesRecord(); + final Map wrapperBundles = MutableMap.of(); static class ManagedBundlesRecord { private Map managedBundlesByUid = MutableMap.of(); @@ -251,10 +251,12 @@ public Boolean call() { framework = null; } + /** Map of bundles by UID */ public Map getManagedBundles() { return managedBundlesRecord.getManagedBundles(); } + /** Gets UID given a name, or null */ public String getManagedBundleId(VersionedName vn) { return managedBundlesRecord.getManagedBundleId(vn); } @@ -274,9 +276,10 @@ public ReferenceWithError install(InputStream zipI } /** See {@link OsgiArchiveInstaller#install()}, but deferring the start and catalog load */ - public ReferenceWithError installDeferredStart(@Nullable ManagedBundle knownBundleMetadata, @Nullable InputStream zipIn) { + public ReferenceWithError installDeferredStart(@Nullable ManagedBundle knownBundleMetadata, @Nullable InputStream zipIn, boolean validateTypes) { OsgiArchiveInstaller installer = new OsgiArchiveInstaller(this, knownBundleMetadata, zipIn); installer.setDeferredStart(true); + installer.setValidateTypes(validateTypes); return installer.install(); } @@ -286,6 +289,8 @@ public ReferenceWithError installDeferredStart(@Nu public ReferenceWithError install(@Nullable ManagedBundle knownBundleMetadata, @Nullable InputStream zipIn, boolean start, boolean loadCatalogBom, boolean forceUpdateOfNonSnapshots) { + log.debug("Installing bundle from stream - known details: "+knownBundleMetadata); + OsgiArchiveInstaller installer = new OsgiArchiveInstaller(this, knownBundleMetadata, zipIn); installer.setStart(start); installer.setLoadCatalogBom(loadCatalogBom); @@ -313,6 +318,7 @@ public void uninstallUploadedBundle(ManagedBundle bundleMetadata) { } managedBundlesRecord.managedBundlesUidByVersionedName.remove(bundleMetadata.getVersionedName()); managedBundlesRecord.managedBundlesUidByUrl.remove(bundleMetadata.getUrl()); + removeInstalledWrapperBundle(bundleMetadata); } mgmt.getRebindManager().getChangeListener().onUnmanaged(bundleMetadata); @@ -333,19 +339,15 @@ public void uninstallUploadedBundle(ManagedBundle bundleMetadata) { @Beta public void uninstallCatalogItemsFromBundle(VersionedName bundle) { List thingsFromHere = ImmutableList.copyOf(getTypesFromBundle( bundle )); + log.debug("Uninstalling items from bundle "+bundle+": "+thingsFromHere); for (RegisteredType t: thingsFromHere) { mgmt.getCatalog().deleteCatalogItem(t.getSymbolicName(), t.getVersion()); } } - protected Iterable getTypesFromBundle(final VersionedName vn) { - final String bundleId = vn.toString(); - return mgmt.getTypeRegistry().getMatching(new Predicate() { - @Override - public boolean apply(RegisteredType input) { - return bundleId.equals(input.getContainingBundle()); - } - }); + @Beta + public Iterable getTypesFromBundle(final VersionedName vn) { + return mgmt.getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(vn)); } /** @deprecated since 0.12.0 use {@link #install(ManagedBundle, InputStream, boolean, boolean)} */ @@ -367,38 +369,32 @@ public synchronized Bundle registerBundle(CatalogBundle bundleMetadata) { } } + // since 0.12.0 no longer returns items; it installs non-persisted RegisteredTypes to the type registry instead @Beta - // TODO this is designed to work if the FEATURE_LOAD_BUNDLE_CATALOG_BOM is disabled, the default, but unintuitive here - // it probably works even if that is true, but we should consider what to do; - // possibly remove that other capability, so that bundles with BOMs _have_ to be installed via this method. - // (load order gets confusing with auto-scanning...) - public List> loadCatalogBom(Bundle bundle) { - return MutableList.copyOf(loadCatalogBom(mgmt, bundle)); + public void loadCatalogBom(Bundle bundle, boolean force, boolean validate) { + loadCatalogBomInternal(mgmt, bundle, force, validate); } - private static Iterable> loadCatalogBom(ManagementContext mgmt, Bundle bundle) { + private static Iterable> loadCatalogBomInternal(ManagementContext mgmt, Bundle bundle, boolean force, boolean validate) { Iterable> catalogItems = MutableList.of(); - if (!BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_LOAD_BUNDLE_CATALOG_BOM)) { - // if the above feature is not enabled, let's do it manually (as a contract of this method) - try { - // TODO improve on this - it ignores the configuration of whitelists, see CatalogBomScanner. - // One way would be to add the CatalogBomScanner to the new Scratchpad area, then retrieving the singleton - // here to get back the predicate from it. - final Predicate applicationsPermitted = Predicates.alwaysTrue(); - - catalogItems = new CatalogBundleLoader(applicationsPermitted, mgmt).scanForCatalog(bundle); - } catch (RuntimeException ex) { - // TODO confirm -- as of May 2017 we no longer uninstall the bundle if install of catalog items fails; - // caller needs to upgrade, or uninstall then reinstall - // (this uninstall wouldn't have unmanaged it in brooklyn in any case) + + try { + CatalogBundleLoader cl = new CatalogBundleLoader(mgmt); + cl.scanForCatalog(bundle, force, validate); + catalogItems = null; + + } catch (RuntimeException ex) { + // TODO confirm -- as of May 2017 we no longer uninstall the bundle if install of catalog items fails; + // caller needs to upgrade, or uninstall then reinstall + // (this uninstall wouldn't have unmanaged it in brooklyn in any case) // try { // bundle.uninstall(); // } catch (BundleException e) { // log.error("Cannot uninstall bundle " + bundle.getSymbolicName() + ":" + bundle.getVersion()+" (after error installing catalog items)", e); // } - throw new IllegalArgumentException("Error installing catalog items", ex); - } + throw new IllegalArgumentException("Error installing catalog items", ex); } + return catalogItems; } @@ -597,4 +593,22 @@ public Iterable getResources(String name, Iterable getInstalledWrapperBundles() { + synchronized (wrapperBundles) { + return MutableSet.copyOf(wrapperBundles.values()); + } + } + } diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/FileBasedStoreObjectAccessor.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/FileBasedStoreObjectAccessor.java index 2028c4383a..a8d7cf155a 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/FileBasedStoreObjectAccessor.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/FileBasedStoreObjectAccessor.java @@ -58,7 +58,7 @@ public String get() { if (!exists()) return null; return Files.asCharSource(file, Charsets.UTF_8).read(); } catch (IOException e) { - throw Exceptions.propagate("Problem reading String contents of file "+file, e); + throw Exceptions.propagateAnnotated("Problem reading String contents of file "+file, e); } } @@ -68,7 +68,7 @@ public byte[] getBytes() { if (!exists()) return null; return Files.asByteSource(file).read(); } catch (IOException e) { - throw Exceptions.propagate("Problem reading bytes of file "+file, e); + throw Exceptions.propagateAnnotated("Problem reading bytes of file "+file, e); } } @@ -90,7 +90,7 @@ public void put(ByteSource bytes) { Streams.copyClose(bytes.openStream(), new FileOutputStream(tmpFile)); FileBasedObjectStore.moveFile(tmpFile, file); } catch (IOException e) { - throw Exceptions.propagate("Problem writing data to file "+file+" (via temporary file "+tmpFile+")", e); + throw Exceptions.propagateAnnotated("Problem writing data to file "+file+" (via temporary file "+tmpFile+")", e); } catch (InterruptedException e) { throw Exceptions.propagate(e); } @@ -105,7 +105,7 @@ public void append(String val) { Files.append(val, file, Charsets.UTF_8); } catch (IOException e) { - throw Exceptions.propagate("Problem appending to file "+file, e); + throw Exceptions.propagateAnnotated("Problem appending to file "+file, e); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/BasicCatalogItemRebindSupport.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/BasicCatalogItemRebindSupport.java index 614b08554c..8cea7b21f5 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/BasicCatalogItemRebindSupport.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/BasicCatalogItemRebindSupport.java @@ -43,6 +43,7 @@ public void reconstruct(RebindContext rebindContext, CatalogItemMemento memento) super.reconstruct(rebindContext, memento); FlagUtils.setFieldsFromFlags(MutableMap.builder() .put("symbolicName", memento.getSymbolicName()) + .put("containingBundle", memento.getContainingBundle()) .put("javaType", memento.getJavaType()) .put("displayName", memento.getDisplayName()) .put("description", memento.getDescription()) diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextImpl.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextImpl.java index 6bea476c38..17f0349d70 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextImpl.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextImpl.java @@ -91,10 +91,11 @@ public void registerCatalogItem(String id, CatalogItem catalogItem) { catalogItems.put(id, catalogItem); } - // we don't track register/unregister of bundles; it isn't needed as it happens so early - // but we do need to know which ones to start subsequently + /** install the bundles into brooklyn and osgi, but do not start nor validate; + * caller (rebind) will do that manually, doing each step across all bundles before proceeding + * to prevent reference errors */ public OsgiBundleInstallationResult installBundle(ManagedBundle bundle, InputStream zipInput) { - return ((ManagementContextInternal)mgmt).getOsgiManager().get().installDeferredStart(bundle, zipInput).get(); + return ((ManagementContextInternal)mgmt).getOsgiManager().get().installDeferredStart(bundle, zipInput, false).get(); } public void startBundle(OsgiBundleInstallationResult br) throws BundleException { if (br.getDeferredStart()!=null) { diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextLookupContext.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextLookupContext.java index f463047226..c1ebc5eafb 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextLookupContext.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextLookupContext.java @@ -116,14 +116,17 @@ public RebindContextLookupContext(ManagementContext managementContext, RebindCon @SuppressWarnings("deprecation") @Override + // only used for persisted xml catalog items; not used for registered types public CatalogItem lookupCatalogItem(String id) { CatalogItem result = rebindContext.getCatalogItem(id); if (result == null) { - // TODO-type-registry -// result = managementContext.getTypeRegistry().get(id, null, null); result = CatalogUtils.getCatalogItemOptionalVersion(managementContext, id); } if (result == null) { + if (managementContext.getTypeRegistry().get(id)!=null) { + // don't treat as dangling; caller should now recognise null as meaning it's known in the type registry + return null; + } result = exceptionHandler.onDanglingCatalogItemRef(id); } return result; diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java index a80b14a00e..0bde0d1d0c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java @@ -100,13 +100,16 @@ import org.apache.brooklyn.core.policy.AbstractPolicy; import org.apache.brooklyn.core.typereg.BasicManagedBundle; import org.apache.brooklyn.core.typereg.RegisteredTypeNaming; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; 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.ClassLoaderUtils; import org.apache.brooklyn.util.core.flags.FlagUtils; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; +import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.time.Time; @@ -117,6 +120,7 @@ import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -337,13 +341,40 @@ protected void installBundlesAndRebuildCatalog() { } } // Start them all after we've installed them + Set installedTypes = MutableSet.of(); for (OsgiBundleInstallationResult br: installs) { try { rebindContext.startBundle(br); + Iterables.addAll(installedTypes, managementContext.getTypeRegistry().getMatching( + RegisteredTypePredicates.containingBundle(br.getVersionedName()))); } catch (Exception e) { exceptionHandler.onCreateFailed(BrooklynObjectType.MANAGED_BUNDLE, br.getMetadata().getId(), br.getMetadata().getSymbolicName(), e); } } + // Now validate all types + Map> validationErrors = this.managementContext.getCatalog().validateTypes( installedTypes ); + if (!validationErrors.isEmpty()) { + Map>> errorsByBundle = MutableMap.of(); + for (RegisteredType t: validationErrors.keySet()) { + VersionedName vn = VersionedName.fromString(t.getContainingBundle()); + Map> errorsInBundle = errorsByBundle.get(vn); + if (errorsInBundle==null) { + errorsInBundle = MutableMap.of(); + errorsByBundle.put(vn, errorsInBundle); + } + errorsInBundle.put(t, validationErrors.get(t)); + } + for (VersionedName vn: errorsByBundle.keySet()) { + Map> errorsInBundle = errorsByBundle.get(vn); + ManagedBundle b = managementContext.getOsgiManager().get().getManagedBundle(vn); + exceptionHandler.onCreateFailed(BrooklynObjectType.MANAGED_BUNDLE, + b!=null ? b.getId() : /* just in case it was uninstalled concurrently somehow */ vn.toString(), + vn.getSymbolicName(), + Exceptions.create("Failed to install "+vn+", types "+errorsInBundle.keySet()+" gave errors", + Iterables.concat(errorsInBundle.values()))); + } + } + } else { logRebindingDebug("Not rebinding bundles; feature disabled: {}", mementoManifest.getBundleIds()); } @@ -1021,20 +1052,37 @@ protected LoadedClass load(Class bTyp List reboundSearchPath = MutableList.of(); if (searchPath != null && !searchPath.isEmpty()) { for (String searchItemId : searchPath) { - CatalogItem searchItem = findCatalogItemInReboundCatalog(bType, searchItemId, contextSuchAsId); - if (searchItem != null) { - reboundSearchPath.add(searchItem.getCatalogItemId()); + String fixedSearchItemId = null; + RegisteredType t1 = managementContext.getTypeRegistry().get(searchItemId); + if (t1!=null) fixedSearchItemId = t1.getId(); + if (fixedSearchItemId==null) { + CatalogItem ci = findCatalogItemInReboundCatalog(bType, searchItemId, contextSuchAsId); + if (ci!=null) fixedSearchItemId = ci.getCatalogItemId(); + } + if (fixedSearchItemId != null) { + reboundSearchPath.add(fixedSearchItemId); } else { LOG.warn("Unable to load catalog item "+ searchItemId - +" for "+contextSuchAsId + " (" + bType.getSimpleName()+"); attempting load nevertheless"); + + " for search path of "+contextSuchAsId + " (" + bType.getSimpleName()+"); attempting to load "+jType+" nevertheless"); } } } if (catalogItemId != null) { - CatalogItem catalogItem = findCatalogItemInReboundCatalog(bType, catalogItemId, contextSuchAsId); - if (catalogItem != null) { - String transformedCatalogItemId = catalogItem.getCatalogItemId(); + String transformedCatalogItemId = null; + + Maybe registeredType = managementContext.getTypeRegistry().getMaybe(catalogItemId, + // ignore bType; catalog item ID gives us the search path, but doesn't need to be of the requested type + null ); + if (registeredType.isPresent()) { + transformedCatalogItemId = registeredType.get().getId(); + } else { + CatalogItem catalogItem = findCatalogItemInReboundCatalog(bType, catalogItemId, contextSuchAsId); + if (catalogItem != null) { + transformedCatalogItemId = catalogItem.getCatalogItemId(); + } + } + if (transformedCatalogItemId!=null) { try { BrooklynClassLoadingContextSequential loader = new BrooklynClassLoadingContextSequential(managementContext); @@ -1043,10 +1091,10 @@ protected LoadedClass load(Class bTyp return new LoadedClass(loader.loadClass(jType, bType), transformedCatalogItemId, reboundSearchPath); } catch (Exception e) { Exceptions.propagateIfFatal(e); - LOG.warn("Unable to load "+jType+" using loader; will try reflections"); + LOG.warn("Unable to load class "+jType+" needed for "+catalogItemId+" for "+contextSuchAsId+", via "+transformedCatalogItemId+" loader (will try reflections)"); } } else { - LOG.warn("Unable to load catalog item "+catalogItemId+" for " + contextSuchAsId + + LOG.warn("Unable to load catalog item "+catalogItemId+" ("+bType+") for " + contextSuchAsId + " ("+bType.getSimpleName()+"); will try reflection"); } } @@ -1055,12 +1103,12 @@ protected LoadedClass load(Class bTyp return new LoadedClass((Class)loadClass(jType), catalogItemId, reboundSearchPath); } catch (Exception e) { Exceptions.propagateIfFatal(e); - LOG.warn("Unable to load "+jType+" using reflections; will try standard context"); + LOG.warn("Unable to load class "+jType+" needed for "+catalogItemId+" for "+contextSuchAsId+", via reflections (may try others, will throw if fails)"); } if (catalogItemId != null) { - throw new IllegalStateException("Unable to load catalog item " + catalogItemId + " for " + - contextSuchAsId + ", or load class from classpath"); + throw new IllegalStateException("Unable to load "+jType+" for catalog item " + catalogItemId + " for " + contextSuchAsId); + } else if (BrooklynFeatureEnablement.isEnabled(FEATURE_BACKWARDS_COMPATIBILITY_INFER_CATALOG_ITEM_ON_REBIND)) { //Try loading from whichever catalog bundle succeeds. BrooklynCatalog catalog = managementContext.getCatalog(); diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/BasicCatalogItemMemento.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/BasicCatalogItemMemento.java index 33406a4a12..72447a7117 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/BasicCatalogItemMemento.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/BasicCatalogItemMemento.java @@ -47,6 +47,7 @@ public static Builder builder() { public static class Builder extends AbstractMemento.Builder { protected String description; protected String symbolicName; + protected String containingBundle; protected String iconUrl; protected String javaType; protected String version; @@ -69,6 +70,11 @@ public Builder symbolicName(String symbolicName) { return self(); } + public Builder containingBundle(String containingBundle) { + this.containingBundle = containingBundle; + return self(); + } + public Builder iconUrl(String iconUrl) { this.iconUrl = iconUrl; return self(); @@ -128,6 +134,7 @@ public Builder from(CatalogItemMemento other) { super.from(other); description = other.getDescription(); symbolicName = other.getSymbolicName(); + containingBundle = other.getContainingBundle(); iconUrl = other.getIconUrl(); javaType = other.getJavaType(); version = other.getVersion(); @@ -149,6 +156,7 @@ public BasicCatalogItemMemento build() { private String description; private String symbolicName; + private String containingBundle; private String iconUrl; private String javaType; private String version; @@ -168,6 +176,7 @@ protected BasicCatalogItemMemento(Builder builder) { super(builder); this.description = builder.description; this.symbolicName = builder.symbolicName; + this.containingBundle = builder.containingBundle; this.iconUrl = builder.iconUrl; this.version = builder.version; this.planYaml = builder.planYaml; @@ -196,6 +205,11 @@ public String getSymbolicName() { return symbolicName; } + @Override + public String getContainingBundle() { + return containingBundle; + } + @Override public String getIconUrl() { return iconUrl; diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/MementosGenerators.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/MementosGenerators.java index 20179bc3b3..e3969cbdd6 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/MementosGenerators.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/MementosGenerators.java @@ -323,6 +323,7 @@ public static FeedMemento newFeedMemento(Feed feed) { return builder.build(); } + @SuppressWarnings("deprecation") private static CatalogItemMemento newCatalogItemMemento(CatalogItem catalogItem) { if (catalogItem instanceof CatalogItemDo) { catalogItem = ((CatalogItemDo)catalogItem).getDto(); @@ -331,6 +332,7 @@ private static CatalogItemMemento newCatalogItemMemento(CatalogItem catalo populateBrooklynObjectMementoBuilder(catalogItem, builder); builder.catalogItemJavaType(catalogItem.getCatalogItemJavaType()) .catalogItemType(catalogItem.getCatalogItemType()) + .containingBundle(catalogItem.getContainingBundle()) .description(catalogItem.getDescription()) .iconUrl(catalogItem.getIconUrl()) .javaType(catalogItem.getJavaType()) diff --git a/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalFactory.java b/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalFactory.java index 4af090096f..fec97f2ece 100644 --- a/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalFactory.java +++ b/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalFactory.java @@ -121,7 +121,7 @@ private T constructWithSpecial(Class uploadedBundles = MutableMap.of(); private Map localRegisteredTypes = MutableMap.of(); public BasicBrooklynTypeRegistry(ManagementContext mgmt) { @@ -95,7 +102,7 @@ private Maybe getSingle(String symbolicNameOrAliasIfNoVersion, f RegisteredTypeLoadingContext context = contextFinal; if (context==null) context = RegisteredTypeLoadingContexts.any(); String version = versionFinal; - if (version==null) version = BrooklynCatalog.DEFAULT_VERSION; + if (Strings.isBlank(version)) version = BrooklynCatalog.DEFAULT_VERSION; if (!BrooklynCatalog.DEFAULT_VERSION.equals(version)) { // normal code path when version is supplied @@ -137,7 +144,7 @@ private Maybe getSingle(String symbolicNameOrAliasIfNoVersion, f return Maybe.of( RegisteredTypes.CI_TO_RT.apply( item ) ); return Maybe.absent("No matches for "+symbolicNameOrAliasIfNoVersion+ - (versionFinal!=null ? ":"+versionFinal : "")+ + (Strings.isNonBlank(versionFinal) ? ":"+versionFinal : "")+ (contextFinal!=null ? " ("+contextFinal+")" : "") ); } @@ -176,7 +183,8 @@ public RegisteredType get(String symbolicNameWithOptionalVersion) { public > SpecT createSpec(RegisteredType type, @Nullable RegisteredTypeLoadingContext constraint, Class specSuperType) { Preconditions.checkNotNull(type, "type"); if (type.getKind()!=RegisteredTypeKind.SPEC) { - throw new IllegalStateException("Cannot create spec from type "+type+" (kind "+type.getKind()+")"); + if (type.getKind()==RegisteredTypeKind.UNRESOLVED) throw new ReferencedUnresolvedTypeException(type); + else throw new UnsupportedTypePlanException("Cannot create spec from type "+type+" (kind "+type.getKind()+")"); } return createSpec(type, type.getPlan(), type.getSymbolicName(), type.getVersion(), type.getSuperTypes(), constraint, specSuperType); } @@ -254,11 +262,12 @@ private > SpecT createSpec( public T createBean(RegisteredType type, RegisteredTypeLoadingContext constraint, Class optionalResultSuperType) { Preconditions.checkNotNull(type, "type"); if (type.getKind()!=RegisteredTypeKind.BEAN) { - throw new IllegalStateException("Cannot create bean from type "+type+" (kind "+type.getKind()+")"); + if (type.getKind()==RegisteredTypeKind.UNRESOLVED) throw new ReferencedUnresolvedTypeException(type); + else throw new UnsupportedTypePlanException("Cannot create bean from type "+type+" (kind "+type.getKind()+")"); } if (constraint!=null) { if (constraint.getExpectedKind()!=null && constraint.getExpectedKind()!=RegisteredTypeKind.SPEC) { - throw new IllegalStateException("Cannot create spec with constraint "+constraint); + throw new IllegalStateException("Cannot create bean with constraint "+constraint); } if (constraint.getAlreadyEncounteredTypes().contains(type.getSymbolicName())) { // avoid recursive cycle @@ -282,15 +291,12 @@ public T createBeanFromPlan(String planFormat, Object planData, RegisteredTy @Override public T create(RegisteredType type, RegisteredTypeLoadingContext constraint, Class optionalResultSuperType) { Preconditions.checkNotNull(type, "type"); - if (type.getKind()==RegisteredTypeKind.BEAN) { - return createBean(type, constraint, optionalResultSuperType); - } - if (type.getKind()==RegisteredTypeKind.SPEC) { + return new RegisteredTypeKindVisitor() { + @Override protected T visitBean() { return createBean(type, constraint, optionalResultSuperType); } @SuppressWarnings({ "unchecked", "rawtypes" }) - T result = (T) createSpec(type, constraint, (Class)optionalResultSuperType); - return result; - } - throw new IllegalArgumentException("Kind-agnostic create method can only be used when the registered type declares its kind, which "+type+" does not"); + @Override protected T visitSpec() { return (T) createSpec(type, constraint, (Class)optionalResultSuperType); } + @Override protected T visitUnresolved() { throw new IllegalArgumentException("Kind-agnostic create method can only be used when the registered type declares its kind, which "+type+" does not"); } + }.visit(type.getKind()); } @Override @@ -314,17 +320,130 @@ public void addToLocalUnpersistedTypeRegistry(RegisteredType type, boolean canFo Asserts.fail("Registered type "+type+" has ID / symname mismatch"); RegisteredType oldType = mgmt.getTypeRegistry().get(type.getId()); - if (oldType==null || canForce) { + if (oldType==null || canForce || BrooklynVersionSyntax.isSnapshot(oldType.getVersion())) { log.debug("Inserting "+type+" into "+this); localRegisteredTypes.put(type.getId(), type); } else { - if (oldType == type) { - // ignore if same instance - // (equals not yet implemented, so would be the same, but misleading) - return; - } + assertSameEnoughToAllowReplacing(oldType, type); + } + } + + /** + * Allow replacing even of non-SNAPSHOT versions if plans are "similar enough"; + * ie, forgiving some metadata changes. + *

    + * This is needed when replacing an unresolved item with a resolved one or vice versa; + * the {@link RegisteredType#equals(Object)} check is too strict. + */ + private boolean assertSameEnoughToAllowReplacing(RegisteredType oldType, RegisteredType type) { + /* The bundle checksum check prevents swapping a different bundle at the same name+version. + * For SNAPSHOT and forced-updates, this method doesn't apply, so we can assume here that + * either the bundle checksums are the same, + * or it is a different bundle declaring an item which is already installed. + *

    + * Thus if the containing bundle is the same, the items are necessarily the same, + * except for metadata we've mucked with (e.g. kind = unresolved). + *

    + * If the containing bundle is different, it's possible someone is doing something sneaky. + * If bundles aren't anonymous wrappers, then we should disallow -- + * e.g. a bundle BAR is declaring and item already declared by a bundle FOO. + * + * + * It is the latter case we have to check. + * + * In the latter case we want to fail unless the old item comes from a wrapper bundle. + * + * the only time this method + * applies where there might be differences in the item is if installing a bundle BAR which + * declares an item with same name and version as an item already installed by a bundle FOO. + * + * * uploading a bundle + * * uploading a BOM (no bundle) where the item it is defining is the same as one already defined + * + * (with the same non-SNAPSHOT version) bundle. So any changes to icons, plans, etc, should have already been caught; + * this only applies if the BOM/bundle is identical, and in that case the only field where the two types here + * could be different is the containing bundle metadata, viz. someone has uploaded an anonymous BOM twice. + */ + + if (!oldType.getVersionedName().equals(type.getVersionedName())) { + // different name - shouldn't even come here throw new IllegalStateException("Cannot add "+type+" to catalog; different "+oldType+" is already present"); } + if (Objects.equals(oldType.getContainingBundle(), type.getContainingBundle())) { + // if named bundles equal then contents must be the same (due to bundle checksum); bail out early + if (!oldType.getPlan().equals(type.getPlan())) { + // shouldn't come here, but check anyway (or maybe if item added twice in the same catalog?) + throw new IllegalStateException("Cannot add "+type+" to catalog; different plan in "+oldType+" from same bundle is already present"); + } + if (oldType.getKind()!=RegisteredTypeKind.UNRESOLVED && type.getKind()!=RegisteredTypeKind.UNRESOLVED && + !Objects.equals(oldType.getKind(), type.getKind())) { + throw new IllegalStateException("Cannot add "+type+" to catalog; different kind in "+oldType+" from same bundle is already present"); + } + return true; + } + + // different bundles, either anonymous or same item in two named bundles + if (!oldType.getPlan().equals(type.getPlan())) { + // if plan is different, fail + throw new IllegalStateException("Cannot add "+type+" in "+type.getContainingBundle()+" to catalog; different plan in "+oldType+" from bundle "+ + oldType.getContainingBundle()+" is already present"); + } + if (oldType.getKind()!=RegisteredTypeKind.UNRESOLVED && type.getKind()!=RegisteredTypeKind.UNRESOLVED && + !Objects.equals(oldType.getKind(), type.getKind())) { + // if kind is different and both resolved, fail + throw new IllegalStateException("Cannot add "+type+" in "+type.getContainingBundle()+" to catalog; different kind in "+oldType+" from bundle "+ + oldType.getContainingBundle()+" is already present"); + } + + // now if old is a wrapper bundle (or old, no bundle), allow it -- metadata may be different here + // but we'll allow that, probably the user is updating their catalog to the new format. + // icons might change, maybe a few other things (but any such errors can be worked around), + // and more useful to be able to upload the same BOM or replace an anonymous BOM with a named bundle. + // crucially if old is a wrapper bundle the containing bundle won't actually be needed for anything, + // so this is safe in terms of search paths etc. + if (oldType.getContainingBundle()==null) { + // if old type wasn't from a bundle, let it be replaced by a bundle + return true; + } + // bundle is changing; was old bundle a wrapper? + OsgiManager osgi = ((ManagementContextInternal)mgmt).getOsgiManager().orNull(); + if (osgi==null) { + // shouldn't happen, as we got a containing bundle, but just in case + return true; + } + if (BasicBrooklynCatalog.isNoBundleOrSimpleWrappingBundle(mgmt, + osgi.getManagedBundle(VersionedName.fromString(oldType.getContainingBundle())))) { + // old was a wrapper bundle; allow it + return true; + } + + throw new IllegalStateException("Cannot add "+type+" in "+type.getContainingBundle()+" to catalog; " + + "item is already present in different bundle "+oldType.getContainingBundle()); + } + + @Beta // API stabilising + public void delete(VersionedName type) { + RegisteredType registeredTypeRemoved = localRegisteredTypes.remove(type.toString()); + if (registeredTypeRemoved != null) { + return ; + } + + // legacy deletion (may call back to us, but max once) + mgmt.getCatalog().deleteCatalogItem(type.getSymbolicName(), type.getVersionString()); + // currently the above will succeed or throw; but if we delete that, we need to enable the code below +// if (Strings.isBlank(type.getVersionString()) || BrooklynCatalog.DEFAULT_VERSION.equals(type.getVersionString())) { +// throw new IllegalStateException("Deleting items with unspecified version (argument DEFAULT_VERSION) not supported."); +// } +// throw new NoSuchElementException("No catalog item found with id "+type); + } + + public void delete(RegisteredType type) { + delete(type.getVersionedName()); + } + + @Beta // API stabilising + public void delete(String id) { + delete(VersionedName.fromString(id)); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java index 350507cd89..19c16994d8 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java @@ -31,7 +31,6 @@ import org.apache.brooklyn.core.objs.BrooklynObjectInternal; import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.text.BrooklynVersionSyntax; -import org.osgi.framework.Version; import com.google.common.annotations.Beta; import com.google.common.base.MoreObjects; @@ -42,6 +41,7 @@ public class BasicManagedBundle extends AbstractBrooklynObject implements Manage private String symbolicName; private String version; + private String checksum; private String url; private transient File localFileWhenJustUploaded; @@ -55,8 +55,6 @@ public BasicManagedBundle(String name, String version, String url) { Preconditions.checkNotNull(name, "Either a URL or both name and version are required"); Preconditions.checkNotNull(version, "Either a URL or both name and version are required"); } - Version.parseVersion(version); - this.symbolicName = name; this.version = version; this.url = url; @@ -83,7 +81,7 @@ public String getSuppliedVersionString() { @Override public String getOsgiVersionString() { - return version==null ? version : BrooklynVersionSyntax.toValidOsgiVersion(version); + return version==null ? null : BrooklynVersionSyntax.toValidOsgiVersion(version); } public void setVersion(String version) { @@ -131,7 +129,8 @@ public String toString() { @Override public int hashCode() { - return Objects.hashCode(symbolicName, version, url); + // checksum deliberately omitted here to match with OsgiBundleWithUrl + return Objects.hashCode(symbolicName, getOsgiVersionString(), url); } @Override @@ -143,6 +142,12 @@ public boolean equals(Object obj) { if (!Objects.equal(symbolicName, other.getSymbolicName())) return false; if (!Objects.equal(getOsgiVersionString(), other.getOsgiVersionString())) return false; if (!Objects.equal(url, other.getUrl())) return false; + if (other instanceof ManagedBundle) { + // checksum compared if available, but not required; + // this makes equality with other OsgiBundleWithUrl items symmetric, + // but for two MB's we look additionally at checksum + if (!Objects.equal(checksum, ((ManagedBundle)other).getChecksum())) return false; + } return true; } @@ -182,6 +187,14 @@ public SubscriptionSupportInternal subscriptions() { public void setDisplayName(String newName) { throw new UnsupportedOperationException(); } + + @Override + public String getChecksum() { + return checksum; + } + public void setChecksum(String md5Checksum) { + this.checksum = md5Checksum; + } @Override protected BrooklynObjectInternal configure(Map flags) { diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicOsgiBundleWithUrl.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicOsgiBundleWithUrl.java index 264b96c8c9..9001ad46ec 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicOsgiBundleWithUrl.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicOsgiBundleWithUrl.java @@ -20,6 +20,7 @@ import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; +import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.text.BrooklynVersionSyntax; import com.google.common.base.MoreObjects; @@ -78,6 +79,12 @@ public String getOsgiVersionString() { return version==null ? version : BrooklynVersionSyntax.toValidOsgiVersion(version); } + @Override + public VersionedName getVersionedName() { + if (!isNameResolved()) return null; + return new VersionedName(getSymbolicName(), getSuppliedVersionString()); + } + @Override public String getUrl() { return url; diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java index f345ec9625..f76a9f4c87 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java @@ -29,8 +29,10 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.javalang.JavaClassNames; +import org.apache.brooklyn.util.osgi.VersionedName; import com.google.common.annotations.Beta; +import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; /** Instances are usually created by methods in {@link RegisteredTypes}. */ @@ -84,6 +86,11 @@ public String getVersion() { return version; } + @Override + public VersionedName getVersionedName() { + return new VersionedName(getSymbolicName(), getVersion()); + } + @Override public String getContainingBundle() { return containingBundle; @@ -153,4 +160,50 @@ public String toString() { (getPlan()!=null ? ";"+getPlan().getPlanFormat() : "")+ "]"; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((aliases == null) ? 0 : aliases.hashCode()); + result = prime * result + ((bundles == null) ? 0 : bundles.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 + ((implementationPlan == null) ? 0 : implementationPlan.hashCode()); + result = prime * result + ((kind == null) ? 0 : kind.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; + BasicRegisteredType other = (BasicRegisteredType) obj; + if (!Objects.equal(aliases, other.aliases)) return false; + if (!Objects.equal(bundles, other.bundles)) return false; + if (!Objects.equal(containingBundle, other.containingBundle)) return false; + if (!Objects.equal(deprecated, other.deprecated)) return false; + if (!Objects.equal(description, other.description)) return false; + if (!Objects.equal(disabled, other.disabled)) return false; + if (!Objects.equal(iconUrl, other.iconUrl)) return false; + if (!Objects.equal(implementationPlan, other.implementationPlan)) return false; + if (!Objects.equal(kind, other.kind)) return false; + if (!Objects.equal(superTypes, other.superTypes)) return false; + if (!Objects.equal(symbolicName, other.symbolicName)) return false; + if (!Objects.equal(tags, other.tags)) return false; + if (!Objects.equal(version, other.version)) return false; + + return true; + } + + } \ No newline at end of file diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicTypeImplementationPlan.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicTypeImplementationPlan.java index 7647323945..206698fb5c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicTypeImplementationPlan.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicTypeImplementationPlan.java @@ -20,6 +20,8 @@ import org.apache.brooklyn.api.typereg.RegisteredType.TypeImplementationPlan; +import com.google.common.base.Objects; + public class BasicTypeImplementationPlan implements TypeImplementationPlan { final String format; final Object data; @@ -38,4 +40,25 @@ public String getPlanFormat() { public Object getPlanData() { return data; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + result = prime * result + ((format == null) ? 0 : format.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; + BasicTypeImplementationPlan other = (BasicTypeImplementationPlan) obj; + if (!Objects.equal(format, other.format)) return false; + if (!Objects.equal(data, other.data)) return false; + return true; + } + } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/ReferencedUnresolvedTypeException.java b/core/src/main/java/org/apache/brooklyn/core/typereg/ReferencedUnresolvedTypeException.java new file mode 100644 index 0000000000..c82ed9b064 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/ReferencedUnresolvedTypeException.java @@ -0,0 +1,44 @@ +/* + * 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.core.typereg; + +import org.apache.brooklyn.api.typereg.RegisteredType; + +/** Indicates a type has requested to resolve another type which is not resolved or not resolvable */ +public class ReferencedUnresolvedTypeException extends UnsupportedTypePlanException { + + private static final long serialVersionUID = -5590108442839125317L; + + public ReferencedUnresolvedTypeException(String message, Throwable cause) { + super(message, cause); + } + + public ReferencedUnresolvedTypeException(String message) { + super(message); + } + + public ReferencedUnresolvedTypeException(RegisteredType t) { + this("Reference to "+t.getVersionedName()+" in plan but that type cannot be resolved (recursive plan or premature evaluation?)"); + } + + public ReferencedUnresolvedTypeException(Throwable cause) { + super(cause); + } + +} diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeKindVisitor.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeKindVisitor.java index 6f781fabd7..457828f700 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeKindVisitor.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeKindVisitor.java @@ -31,6 +31,7 @@ public T visit(RegisteredTypeKind kind) { switch (kind) { case SPEC: return visitSpec(); case BEAN: return visitBean(); + case UNRESOLVED: return visitUnresolved(); default: throw new IllegalStateException("Unexpected registered type kind: "+kind); } @@ -42,4 +43,6 @@ protected T visitNull() { protected abstract T visitSpec(); protected abstract T visitBean(); + protected abstract T visitUnresolved(); + } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeLoadingContexts.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeLoadingContexts.java index 0368ba3c96..f6ee347afe 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeLoadingContexts.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeLoadingContexts.java @@ -128,7 +128,8 @@ public static RegisteredTypeLoadingContext bean(Class javaSuperType) { public static RegisteredTypeLoadingContext spec(Class javaSuperType) { return of(RegisteredTypeKind.SPEC, javaSuperType); } - + + /** Adds the required supertype for beans, or supertype of the spec target (e.g. Entity not EntitySpec) for specs */ public static RegisteredTypeLoadingContext withBeanSuperType(@Nullable RegisteredTypeLoadingContext source, @Nullable Class beanSuperType) { Class superType = beanSuperType; BasicRegisteredTypeLoadingContext constraint = new BasicRegisteredTypeLoadingContext(source); 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 e480cd1d95..0bf3490fb6 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 @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.core.typereg; +import static com.google.common.base.Preconditions.checkNotNull; + import javax.annotation.Nullable; import org.apache.brooklyn.api.entity.Application; @@ -26,21 +28,28 @@ import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.policy.Policy; import org.apache.brooklyn.api.sensor.Enricher; +import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; import org.apache.brooklyn.util.collections.CollectionFunctionals; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.osgi.VersionedName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.google.common.annotations.Beta; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; public class RegisteredTypePredicates { + private static final Logger log = LoggerFactory.getLogger(RegisteredTypePredicates.class); + public static Predicate deprecated(final boolean deprecated) { return new DeprecatedEqualTo(deprecated); } - private static class DeprecatedEqualTo implements Predicate { private final boolean deprecated; @@ -56,7 +65,6 @@ public boolean apply(@Nullable RegisteredType item) { public static Predicate disabled(boolean disabled) { return new DisabledEqualTo(disabled); } - private static class DisabledEqualTo implements Predicate { private final boolean disabled; @@ -68,6 +76,21 @@ public boolean apply(@Nullable RegisteredType item) { return (item != null) && item.isDisabled() == disabled; } } + + public static Predicate template(final boolean template) { + return new TemplateTagPresent(template); + } + private static class TemplateTagPresent implements Predicate { + private final boolean present; + + public TemplateTagPresent(boolean present) { + this.present = present; + } + @Override + public boolean apply(@Nullable RegisteredType item) { + return (item != null) && RegisteredTypes.isTemplate(item) == present; + } + } public static final Function ID_OF_ITEM_TRANSFORMER = new IdOfItemTransformer(); @@ -256,4 +279,55 @@ public boolean apply(@Nullable RegisteredType item) { } } + public static Predicate containingBundle(VersionedName versionedName) { + return new ContainingBundle(versionedName); + } + public static Predicate containingBundle(OsgiBundleWithUrl bundle) { + return containingBundle(bundle.getVersionedName()); + } + public static Predicate containingBundle(String versionedName) { + return containingBundle(VersionedName.fromString(versionedName)); + } + private static class ContainingBundle implements Predicate { + private final VersionedName bundle; + + public ContainingBundle(VersionedName bundle) { + this.bundle = bundle; + } + @Override + public boolean apply(@Nullable RegisteredType item) { + return bundle.equalsOsgi(item.getContainingBundle()); + } + } + + @Beta // expensive way to compare everything; API likely to change to be clearer + public static Predicate stringRepresentationMatches(Predicate filter) { + return new StringRepresentationMatches<>(checkNotNull(filter, "filter")); + } + private static class StringRepresentationMatches implements Predicate { + private final Predicate filter; + StringRepresentationMatches(final Predicate filter) { + this.filter = filter; + } + @Override + public boolean apply(@Nullable RegisteredType item) { + try { + String thingToCompare = + item.getVersionedName().toString()+"\n"+ + item.getVersionedName().toOsgiString()+"\n"+ + item.getTags()+"\n"+ + item.getDisplayName()+"\n"+ + item.getAliases()+"\n"+ + item.getDescription()+"\n"+ + RegisteredTypes.getImplementationDataStringForSpec(item); + return filter.apply(thingToCompare); + } catch (Exception e) { + // If we propagated exceptions, then we'd risk aborting the checks for other catalog items. + // Play it safe, in case there's something messed up with just one catalog item. + Exceptions.propagateIfFatal(e); + log.warn("Problem producing string representation of "+item+"; assuming no match, and continuing", e); + return false; + } + } + } } \ No newline at end of file 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 72feeddedd..6519edfa76 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 @@ -19,8 +19,10 @@ package org.apache.brooklyn.core.typereg; import java.lang.reflect.Method; +import java.util.Collection; import java.util.Comparator; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; @@ -28,12 +30,16 @@ import javax.annotation.Nullable; import org.apache.brooklyn.api.catalog.CatalogItem; +import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.mgmt.rebind.RebindSupport; +import org.apache.brooklyn.api.mgmt.rebind.mementos.CatalogItemMemento; import org.apache.brooklyn.api.objs.BrooklynObject; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; 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.api.typereg.RegisteredType.TypeImplementationPlan; import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; @@ -59,6 +65,7 @@ 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; import com.google.common.reflect.TypeToken; @@ -112,11 +119,57 @@ public static RegisteredType of(CatalogItem item) { if (item.getLibraries()!=null) type.bundles.addAll(item.getLibraries()); // aliases aren't on item if (item.tags()!=null) type.tags.addAll(item.tags().getTags()); + if (item.getCatalogItemType()==CatalogItemType.TEMPLATE) { + type.tags.add(BrooklynTags.CATALOG_TEMPLATE); + } // these things from item we ignore: javaType, specType, registeredTypeName ... return type; } + /** @deprecated since introduced in 0.12.0; for backwards compatibility only, may be removed at any point. + * Returns a partially-populated CatalogItem. Many methods throw {@link UnsupportedOperationException} + * but the basic ones work. */ + @Deprecated + public static CatalogItem toPartialCatalogItem(RegisteredType t) { + return new CatalogItemFromRegisteredType(t); + } + private static class CatalogItemFromRegisteredType implements CatalogItem { + private final RegisteredType type; + public CatalogItemFromRegisteredType(RegisteredType type) { this.type = type; } + @Override public String getDisplayName() { return type.getDisplayName(); } + @Override public String getCatalogItemId() { return type.getVersionedName().toString(); } + @Override public String getId() { return type.getId(); } + @Override public String getName() { return type.getSymbolicName(); } + @Override public String getSymbolicName() { return type.getSymbolicName(); } + @Override public String getRegisteredTypeName() { return type.getSymbolicName(); } + @Override public String getDescription() { return type.getDescription(); } + @Override public String getIconUrl() { return type.getIconUrl(); } + @Override public String getContainingBundle() { return type.getContainingBundle(); } + @Override public String getVersion() { return type.getVersion(); } + + @Override public List getCatalogItemIdSearchPath() { throw new UnsupportedOperationException(); } + @Override public TagSupport tags() { throw new UnsupportedOperationException(); } + @Override public RelationSupport relations() { throw new UnsupportedOperationException(); } + @Override public T setConfig(ConfigKey key, T val) { throw new UnsupportedOperationException(); } + @Override public T getConfig(ConfigKey key) { throw new UnsupportedOperationException(); } + @Override public ConfigurationSupport config() { throw new UnsupportedOperationException(); } + @Override public SubscriptionSupport subscriptions() { throw new UnsupportedOperationException(); } + @Override public org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType getCatalogItemType() { throw new UnsupportedOperationException(); } + @Override public Class getCatalogItemJavaType() { throw new UnsupportedOperationException(); } + @Override public Class getSpecType() { throw new UnsupportedOperationException(); } + @Override public String getJavaType() { throw new UnsupportedOperationException(); } + @Override public Collection getLibraries() { + throw new UnsupportedOperationException(); + } + @Override public String getPlanYaml() { throw new UnsupportedOperationException(); } + @Override public RebindSupport getRebindSupport() { throw new UnsupportedOperationException(); } + @Override public void setDeprecated(boolean deprecated) { throw new UnsupportedOperationException(); } + @Override public void setDisabled(boolean disabled) { throw new UnsupportedOperationException(); } + @Override public boolean isDeprecated() { throw new UnsupportedOperationException(); } + @Override public boolean isDisabled() { throw new UnsupportedOperationException(); } + } + /** Preferred mechanism for defining a bean {@link RegisteredType}. * Callers should also {@link #addSuperTypes(RegisteredType, Iterable)} on the result.*/ public static RegisteredType bean(@Nonnull String symbolicName, @Nonnull String version, @Nonnull TypeImplementationPlan plan) { @@ -125,21 +178,52 @@ public static RegisteredType bean(@Nonnull String symbolicName, @Nonnull String } /** Convenience for {@link #bean(String, String, TypeImplementationPlan)} when there is a single known java signature/super type */ public static RegisteredType bean(@Nonnull String symbolicName, @Nonnull String version, @Nonnull TypeImplementationPlan plan, @Nonnull Class superType) { - if (superType==null) log.warn("Deprecated use of RegisteredTypes API passing null name/version", new Exception("Location of deprecated use, wrt "+symbolicName+":"+version+" "+plan)); + if (superType==null) log.warn("Deprecated use of RegisteredTypes API passing null supertype", new Exception("Location of deprecated use, wrt "+symbolicName+":"+version+" "+plan)); return addSuperType(bean(symbolicName, version, plan), superType); } /** Preferred mechanism for defining a spec {@link RegisteredType}. * Callers should also {@link #addSuperTypes(RegisteredType, Iterable)} on the result.*/ public static RegisteredType spec(@Nonnull String symbolicName, @Nonnull String version, @Nonnull TypeImplementationPlan plan) { - if (symbolicName==null || version==null) log.warn("Deprecated use of RegisteredTypes API passing null name/version", new Exception("Location of deprecated use, wrt "+plan)); + if (symbolicName==null || version==null) log.warn("Deprecated use of RegisteredTypes API passing null supertype", new Exception("Location of deprecated use, wrt "+plan)); return new BasicRegisteredType(RegisteredTypeKind.SPEC, symbolicName, version, plan); } - /** Convenience for {@link #cpec(String, String, TypeImplementationPlan)} when there is a single known java signature/super type */ + /** Convenience for {@link #spec(String, String, TypeImplementationPlan)} when there is a single known java signature/super type */ public static RegisteredType spec(@Nonnull String symbolicName, @Nonnull String version, @Nonnull TypeImplementationPlan plan, @Nonnull Class superType) { - if (superType==null) log.warn("Deprecated use of RegisteredTypes API passing null name/version", new Exception("Location of deprecated use, wrt "+symbolicName+":"+version+" "+plan)); + if (superType==null) log.warn("Deprecated use of RegisteredTypes API passing null supertype", new Exception("Location of deprecated use, wrt "+symbolicName+":"+version+" "+plan)); return addSuperType(spec(symbolicName, version, plan), superType); } + public static RegisteredType newInstance(@Nonnull RegisteredTypeKind kind, @Nonnull String symbolicName, @Nonnull String version, + @Nonnull TypeImplementationPlan plan, @Nonnull Iterable superTypes, + Iterable aliases, Iterable tags, + String containingBundle, Iterable libraryBundles, + String displayName, String description, String catalogIconUrl, + Boolean catalogDeprecated, Boolean catalogDisabled) { + BasicRegisteredType result = new BasicRegisteredType(kind, symbolicName, version, plan); + addSuperTypes(result, superTypes); + addAliases(result, aliases); + addTags(result, tags); + result.containingBundle = containingBundle; + Iterables.addAll(result.bundles, libraryBundles); + result.displayName = displayName; + result.description = description; + result.iconUrl = catalogIconUrl; + if (catalogDeprecated!=null) result.deprecated = catalogDeprecated; + if (catalogDisabled!=null) result.disabled = catalogDisabled; + return result; + } + public static RegisteredType copy(RegisteredType t) { + return copyResolved(t.getKind(), t); + } + @Beta + public static RegisteredType copyResolved(RegisteredTypeKind kind, RegisteredType t) { + if (t.getKind()!=null && t.getKind()!=RegisteredTypeKind.UNRESOLVED && t.getKind()!=kind) { + throw new IllegalStateException("Cannot copy resolve "+t+" ("+t.getKind()+") as "+kind); + } + return newInstance(kind, t.getSymbolicName(), t.getVersion(), t.getPlan(), + t.getSuperTypes(), t.getAliases(), t.getTags(), t.getContainingBundle(), t.getLibraries(), + t.getDisplayName(), t.getDescription(), t.getIconUrl(), t.isDeprecated(), t.isDisabled()); + } /** Creates an anonymous {@link RegisteredType} for plan-instantiation-only use. */ @Beta @@ -176,6 +260,18 @@ public static RegisteredType setContainingBundle(RegisteredType type, @Nullable return type; } + @Beta + public static RegisteredType setDeprecated(RegisteredType type, boolean deprecated) { + ((BasicRegisteredType)type).deprecated = deprecated; + return type; + } + + @Beta + public static RegisteredType setDisabled(RegisteredType type, boolean disabled) { + ((BasicRegisteredType)type).disabled = disabled; + return type; + } + @Beta public static RegisteredType addSuperType(RegisteredType type, @Nullable Class superType) { if (superType!=null) { @@ -246,6 +342,7 @@ public static RegisteredType addTags(RegisteredType type, Iterable tags) { public static String getImplementationDataStringForSpec(RegisteredType item) { if (item==null || item.getPlan()==null) return null; Object data = item.getPlan().getPlanData(); + if (data==null) throw new IllegalStateException("No plan data for "+item); if (!(data instanceof String)) throw new IllegalStateException("Expected plan data for "+item+" to be a string"); return (String)data; } @@ -306,6 +403,7 @@ public static boolean isSubtypeOf(RegisteredType type, Class superType) { * Queries recursively the given types (either {@link Class} or {@link RegisteredType}) * 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)); } @@ -362,23 +460,39 @@ private static boolean isSubtypeOf(Class candidate, RegisteredType type) { public static RegisteredType getBestVersion(Iterable types) { if (types==null || !types.iterator().hasNext()) return null; - return Ordering.from(RegisteredTypeComparator.INSTANCE).max(types); + return Ordering.from(RegisteredTypeNameThenBestFirstComparator.INSTANCE).min(types); } - public static class RegisteredTypeComparator implements Comparator { - public static Comparator INSTANCE = new RegisteredTypeComparator(); - private RegisteredTypeComparator() {} + /** by name, then with disabled, deprecated first, then by increasing version */ + public static class RegisteredTypeNameThenWorstFirstComparator implements Comparator { + public static Comparator INSTANCE = new RegisteredTypeNameThenWorstFirstComparator(); + private RegisteredTypeNameThenWorstFirstComparator() {} @Override public int compare(RegisteredType o1, RegisteredType o2) { return ComparisonChain.start() + .compare(o1.getSymbolicName(), o2.getSymbolicName(), NaturalOrderComparator.INSTANCE) .compareTrueFirst(o1.isDisabled(), o2.isDisabled()) .compareTrueFirst(o1.isDeprecated(), o2.isDeprecated()) - .compare(o1.getSymbolicName(), o2.getSymbolicName(), NaturalOrderComparator.INSTANCE) .compare(o1.getVersion(), o2.getVersion(), VersionComparator.INSTANCE) .result(); } } + /** by name, then with disabled, deprecated first, then by increasing version */ + public static class RegisteredTypeNameThenBestFirstComparator implements Comparator { + public static Comparator INSTANCE = new RegisteredTypeNameThenBestFirstComparator(); + private RegisteredTypeNameThenBestFirstComparator() {} + @Override + public int compare(RegisteredType o1, RegisteredType o2) { + return ComparisonChain.start() + .compare(o1.getSymbolicName(), o2.getSymbolicName(), NaturalOrderComparator.INSTANCE) + .compareFalseFirst(o1.isDisabled(), o2.isDisabled()) + .compareFalseFirst(o1.isDeprecated(), o2.isDeprecated()) + .compare(o2.getVersion(), o1.getVersion(), VersionComparator.INSTANCE) + .result(); + } + } + /** validates that the given object (required) satisfies the constraints implied by the given * type and context object, using {@link Maybe} as the result set absent containing the error(s) * if not satisfied. returns an {@link Absent} if failed with details of the error, @@ -401,6 +515,10 @@ protected Maybe visitSpec() { protected Maybe visitBean() { return tryValidateBean(object, type, context); } + + protected Maybe visitUnresolved() { + return Maybe.absent(object+" is not yet resolved"); + } }.visit(kind); } @@ -480,4 +598,15 @@ public static String getIconUrl(BrooklynObject object) { if (item==null) return null; return item.getIconUrl(); } + + public static RegisteredType changePlan(RegisteredType type, TypeImplementationPlan plan) { + ((BasicRegisteredType)type).implementationPlan = plan; + return type; + } + + public static boolean isTemplate(RegisteredType type) { + if (type==null) return false; + return type.getTags().contains(BrooklynTags.CATALOG_TEMPLATE); + } + } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java b/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java index ba81e07fc7..cc4845c286 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java @@ -125,6 +125,7 @@ public static Maybe transform(ManagementContext mgmt, RegisteredType typ (Strings.isNonBlank(e.getMessage()) ? " ("+e.getMessage()+")" : "")); } catch (Throwable e) { Exceptions.propagateIfFatal(e); + log.debug("Transformer for "+t.getFormatCode()+" gave an error creating this plan (retrying with others): "+e, e); failuresFromTransformers.add(new PropagatedRuntimeException( (type.getSymbolicName()!=null ? "Error in definition of "+type.getId() : "Transformer for "+t.getFormatCode()+" gave an error creating this plan") + ": "+ diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/UnsupportedTypePlanException.java b/core/src/main/java/org/apache/brooklyn/core/typereg/UnsupportedTypePlanException.java index 98cbd7a8c6..696a5c2dbc 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/UnsupportedTypePlanException.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/UnsupportedTypePlanException.java @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.core.typereg; +/** Used by {@link BrooklynTypePlanTransformer} for a transformer to indicate an error resolving a plan. */ +// TODO add another hierarchy RecognizedTypePlanException or a score to indicate priority of errors for reporting? public class UnsupportedTypePlanException extends RuntimeException { private static final long serialVersionUID = -5590108442839125317L; diff --git a/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java b/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java index 8d5cf0f5fd..c3e75be3ee 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java @@ -27,20 +27,21 @@ import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.BrooklynVersion; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContextSequential; import org.apache.brooklyn.core.mgmt.ha.OsgiManager; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.core.LoaderDispatcher.ClassLoaderDispatcher; import org.apache.brooklyn.util.core.LoaderDispatcher.MultipleResourceLoaderDispatcher; import org.apache.brooklyn.util.core.LoaderDispatcher.ResourceLoaderDispatcher; import org.apache.brooklyn.util.core.osgi.Osgis; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.osgi.OsgiUtils; -import org.apache.brooklyn.util.text.BrooklynVersionSyntax; import org.apache.brooklyn.util.text.Strings; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; @@ -253,18 +254,31 @@ private Maybe loadClass(String name, LoaderDispatcher dispatcher, Stri Maybe cls; if (entity != null && mgmt != null) { String catalogItemId = entity.getCatalogItemId(); + if (catalogItemId != null) { -// RegisteredType type = mgmt.getTypeRegistry().get(catalogItemId); -// if (type != null) { -// BrooklynClassLoadingContextSequential loader = new BrooklynClassLoadingContextSequential(mgmt); -// loader.add(newClassLoadingContextForCatalogItems(mgmt, type.getId(), -// type.getContainingBundle() + type.getLibraries() ?); -// cls = dispatcher.tryLoadFrom(loader, className); -// if (cls.isPresent()) { -// return cls; -// } - // TODO prefer above to below, but need to reconcile item.searchPath with RegisteredType? - // or use entity search path ? + { + BrooklynClassLoadingContextSequential loader = new BrooklynClassLoadingContextSequential(mgmt); + loader.add( newClassLoadingContextForCatalogItems(mgmt, catalogItemId, + entity.getCatalogItemIdSearchPath()) ); + cls = dispatcher.tryLoadFrom(loader, className); + if (cls.isPresent()) { + return cls; + } + } + // the above (entity) should be sufficient? + + RegisteredType type = mgmt.getTypeRegistry().get(catalogItemId); + if (type != null) { + BrooklynClassLoadingContextSequential loader = new BrooklynClassLoadingContextSequential(mgmt); + List libs = MutableList.of(); + for (OsgiBundleWithUrl o: type.getLibraries()) libs.add(o.getVersionedName().toString()); + loader.add( newClassLoadingContextForCatalogItems(mgmt, type.getContainingBundle(), libs) ); + cls = dispatcher.tryLoadFrom(loader, className); + if (cls.isPresent()) { + return cls; + } + } + CatalogItem item = CatalogUtils.getCatalogItemOptionalVersion(mgmt, catalogItemId); if (item != null) { BrooklynClassLoadingContextSequential loader = new BrooklynClassLoadingContextSequential(mgmt); diff --git a/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java b/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java index 8fffa9d556..54af77bde3 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java @@ -54,7 +54,7 @@ public Maybe> tryLoadFrom(Bundle bundle, String className) { @Override public Maybe> tryLoadFrom(BrooklynClassLoadingContext loader, String className) { try { - return Maybe.>of(loader.loadClass(className)); + return loader.tryLoadClass(className); } catch (IllegalStateException e) { propagateIfCauseNotClassNotFound(e); return Maybe.absent("Failed to load class " + className + " from loader " + loader, e); diff --git a/core/src/main/java/org/apache/brooklyn/util/core/ResourceUtils.java b/core/src/main/java/org/apache/brooklyn/util/core/ResourceUtils.java index 813bae2a74..88bb4adf43 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/ResourceUtils.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/ResourceUtils.java @@ -293,7 +293,7 @@ public InputStream getResourceFromUrl(String url) { throw new IOException("'"+orig+"' not found on classpath or filesystem"); } catch (Exception e) { if (context!=null) { - throw Exceptions.propagate("Error getting resource '"+url+"' for "+context, e); + throw Exceptions.propagateAnnotated("Error getting resource '"+url+"' for "+context, e); } else { throw Exceptions.propagate(e); } diff --git a/core/src/main/java/org/apache/brooklyn/util/core/javalang/ReflectionScanner.java b/core/src/main/java/org/apache/brooklyn/util/core/javalang/ReflectionScanner.java index bb46376cdb..b5ca2c9e90 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/javalang/ReflectionScanner.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/javalang/ReflectionScanner.java @@ -72,6 +72,9 @@ public ReflectionScanner( final String optionalPrefix, final Predicate filter, final ClassLoader ...classLoaders) { + if (Reflections.log==null) { + Reflections.log = log; + } reflections = new Reflections(new ConfigurationBuilder() { { if (urlsToScan!=null) diff --git a/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java b/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java index b590cee356..9116c0e491 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java @@ -114,7 +114,7 @@ public File createJarFromClasspathDir(String path) { return f; } catch (Exception e) { - throw Exceptions.propagate("Error creating ZIP from classpath spec "+path, e); + throw Exceptions.propagateAnnotated("Error creating ZIP from classpath spec "+path, e); } finally { Streams.closeQuietly(zout); @@ -136,7 +136,7 @@ public Manifest getManifest(File f) { jf = new JarFile(f); return jf.getManifest(); } catch (IOException e) { - throw Exceptions.propagate("Unable to read "+f+" when looking for manifest", e); + throw Exceptions.propagateAnnotated("Unable to read "+f+" when looking for manifest", e); } finally { Streams.closeQuietly(jf); } @@ -144,11 +144,15 @@ public Manifest getManifest(File f) { /** as {@link #copyAddingManifest(File, Manifest)} but taking manifest entries as a map for convenience */ public File copyAddingManifest(File f, Map attrs) { + return copyAddingManifest(f, manifestOf(attrs)); + } + + protected Manifest manifestOf(Map attrs) { Manifest mf = new Manifest(); for (Map.Entry attr: attrs.entrySet()) { mf.getMainAttributes().putValue(attr.getKey(), attr.getValue()); } - return copyAddingManifest(f, mf); + return mf; } /** create a copy of the given ZIP as a JAR with the given manifest, returning the new temp file */ @@ -160,7 +164,7 @@ public File copyAddingManifest(File f, Manifest mf) { zout = new JarOutputStream(new FileOutputStream(f2), mf); writeZipEntriesFromFile(zout, f, Predicates.not(Predicates.equalTo(MANIFEST_PATH))); } catch (IOException e) { - throw Exceptions.propagate("Unable to read "+f+" when looking for manifest", e); + throw Exceptions.propagateAnnotated("Unable to read "+f+" when looking for manifest", e); } finally { Streams.closeQuietly(zf); Streams.closeQuietly(zout); @@ -222,7 +226,7 @@ public boolean apply(String input) { return f2; } catch (IOException e) { - throw Exceptions.propagate("Unable to read "+f+" when looking for manifest", e); + throw Exceptions.propagateAnnotated("Unable to read "+f+" when looking for manifest", e); } finally { Streams.closeQuietly(zf); Streams.closeQuietly(zout); @@ -281,7 +285,7 @@ public Bundle installBundle(File f, boolean start) { return b; } catch (Exception e) { - throw Exceptions.propagate("Error starting bundle from "+f, e); + throw Exceptions.propagateAnnotated("Error starting bundle from "+f, e); } } @@ -350,5 +354,30 @@ private void addUrlFileToZip(ZipOutputStream zout, String root, String item, Pre Streams.closeQuietly(itemFound); zout.closeEntry(); } + + /** Creates a temporary file with the given metadata */ + public File createTempBundle(String nameHint, Manifest mf, Map files) { + File f2 = Os.newTempFile(nameHint, "zip"); + ZipOutputStream zout = null; + ZipFile zf = null; + try { + zout = mf!=null ? new JarOutputStream(new FileOutputStream(f2), mf) : new ZipOutputStream(new FileOutputStream(f2)); + writeZipEntries(zout, files); + } catch (IOException e) { + throw Exceptions.propagate("Unable to read/write for "+nameHint, e); + } finally { + Streams.closeQuietly(zf); + Streams.closeQuietly(zout); + } + return f2; + } + + public File createTempBundle(String nameHint, Map mf, Map files) { + return createTempBundle(nameHint, manifestOf(mf), files); + } + + public File createTempZip(String nameHint, Map files) { + return createTempBundle(nameHint, (Manifest)null, files); + } } diff --git a/core/src/main/java/org/apache/brooklyn/util/core/osgi/Osgis.java b/core/src/main/java/org/apache/brooklyn/util/core/osgi/Osgis.java index c6f66a74ac..96a30c8ea3 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/osgi/Osgis.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/osgi/Osgis.java @@ -442,4 +442,5 @@ public static Optional getBundleOf(Class clazz) { Bundle bundle = org.osgi.framework.FrameworkUtil.getBundle(clazz); return Optional.fromNullable(bundle); } + } diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/Tasks.java b/core/src/main/java/org/apache/brooklyn/util/core/task/Tasks.java index 09116af7e4..ac07c35e22 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/task/Tasks.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/task/Tasks.java @@ -33,13 +33,14 @@ import org.apache.brooklyn.api.mgmt.TaskAdaptable; import org.apache.brooklyn.api.mgmt.TaskFactory; import org.apache.brooklyn.api.mgmt.TaskQueueingContext; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.ReferenceWithError; import org.apache.brooklyn.util.repeat.Repeater; import org.apache.brooklyn.util.time.CountdownTimer; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.time.Time; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -133,6 +134,11 @@ public static ValueResolver resolving(Object v, Class type) { public static ValueResolver.ResolverBuilderPretype resolving(Object v) { return new ValueResolver.ResolverBuilderPretype(v); } + + @SuppressWarnings("unchecked") + public static ValueResolver resolving(ConfigBag config, ConfigKey key) { + return new ValueResolver.ResolverBuilderPretype(config.getStringKey(key.getName())).as((Class)key.getType()); + } /** @see #resolveValue(Object, Class, ExecutionContext, String) */ public static T resolveValue(Object v, Class type, @Nullable ExecutionContext exec) throws ExecutionException, InterruptedException { diff --git a/core/src/main/resources/brooklyn/empty.catalog.bom b/core/src/main/resources/brooklyn/empty.catalog.bom index 58cfb1bba9..a256754352 100644 --- a/core/src/main/resources/brooklyn/empty.catalog.bom +++ b/core/src/main/resources/brooklyn/empty.catalog.bom @@ -15,4 +15,6 @@ # specific language governing permissions and limitations # under the License. # -brooklyn.catalog: {} \ No newline at end of file +brooklyn.catalog: + bundle: brooklyn-empty-catalog + version: 0.0.0-SNAPSHOT diff --git a/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties b/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties index 8a6a7a49d2..c0dc264ac0 100644 --- a/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties +++ b/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties @@ -1450,3 +1450,5 @@ brooklyn.networking.vclouddirector.PortForwardingConfig org.apache.brooklyn.feed.ssh.SshFeed$SshPollIdentifier : org.apache.brooklyn.feed.AbstractCommandFeed$CommandPollIdentifier org.apache.brooklyn.feed.ssh.SshPollConfig$CombiningEnvSupplier : org.apache.brooklyn.feed.CommandPollConfig$CombiningEnvSupplier + +com.usharesoft.cloudsoft.SoftwareFromStack : com.usharesoft.cloudsoft.entities-from-uforge:com.usharesoft.cloudsoft.SoftwareFromStack diff --git a/core/src/test/java/org/apache/brooklyn/core/location/LocationManagementTest.java b/core/src/test/java/org/apache/brooklyn/core/location/LocationManagementTest.java index ed6758a73a..da85a9ee2f 100644 --- a/core/src/test/java/org/apache/brooklyn/core/location/LocationManagementTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/location/LocationManagementTest.java @@ -106,23 +106,23 @@ public void testManagedLocationsSimpleCreateAndCleanup() { @Test public void testManagedLocationsNamedCreateAndCleanup() { - Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations().keySet(), CollectionFunctionals.sizeEquals(0)); + Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations(true).keySet(), CollectionFunctionals.sizeEquals(0)); Asserts.assertThat(mgmt.getCatalog().getCatalogItems(), CollectionFunctionals.sizeEquals(0)); Asserts.assertThat(locationManager.getLocations(), CollectionFunctionals.sizeEquals(0)); - mgmt.getLocationRegistry().updateDefinedLocation( new BasicLocationDefinition("lh1", "localhost", null) ); + mgmt.getLocationRegistry().updateDefinedLocationNonPersisted( new BasicLocationDefinition("lh1", "localhost", null) ); - Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations().keySet(), CollectionFunctionals.sizeEquals(1)); + Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations(true).keySet(), CollectionFunctionals.sizeEquals(1)); Asserts.assertThat(locationManager.getLocations(), CollectionFunctionals.sizeEquals(0)); // currently such defined locations do NOT appear in catalog -- see CatalogYamlLocationTest Asserts.assertThat(mgmt.getCatalog().getCatalogItems(), CollectionFunctionals.sizeEquals(0)); Location loc = mgmt.getLocationRegistry().getLocationManaged("lh1"); - Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations().keySet(), CollectionFunctionals.sizeEquals(1)); + Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations(true).keySet(), CollectionFunctionals.sizeEquals(1)); Asserts.assertThat(locationManager.getLocations(), CollectionFunctionals.sizeEquals(1)); mgmt.getLocationManager().unmanage(loc); - Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations().keySet(), CollectionFunctionals.sizeEquals(1)); + Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations(true).keySet(), CollectionFunctionals.sizeEquals(1)); Asserts.assertThat(locationManager.getLocations(), CollectionFunctionals.sizeEquals(0)); } diff --git a/core/src/test/java/org/apache/brooklyn/core/location/LocationRegistryTest.java b/core/src/test/java/org/apache/brooklyn/core/location/LocationRegistryTest.java index bd27f814ab..b9c8d5ac95 100644 --- a/core/src/test/java/org/apache/brooklyn/core/location/LocationRegistryTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/location/LocationRegistryTest.java @@ -53,9 +53,9 @@ public void testNamedLocationsPropertyDefinedLocations() { properties.put("brooklyn.location.named.foo", "byon:(hosts=\"root@192.168.1.{1,2,3,4}\")"); properties.put("brooklyn.location.named.foo.privateKeyFile", "~/.ssh/foo.id_rsa"); mgmt = LocalManagementContextForTests.newInstance(properties); - log.info("foo properties gave defined locations: "+mgmt.getLocationRegistry().getDefinedLocations()); + log.info("foo properties gave defined locations: "+mgmt.getLocationRegistry().getDefinedLocations(true)); locdef = mgmt.getLocationRegistry().getDefinedLocationByName("foo"); - Assert.assertNotNull(locdef, "Expected 'foo' location; but had "+mgmt.getLocationRegistry().getDefinedLocations()); + Assert.assertNotNull(locdef, "Expected 'foo' location; but had "+mgmt.getLocationRegistry().getDefinedLocations(true)); Assert.assertEquals(locdef.getConfig().get("privateKeyFile"), "~/.ssh/foo.id_rsa"); } @@ -67,7 +67,7 @@ public void testResolvesByNamedAndId() { mgmt = LocalManagementContextForTests.newInstance(properties); locdef = mgmt.getLocationRegistry().getDefinedLocationByName("foo"); - log.info("testResovlesBy has defined locations: "+mgmt.getLocationRegistry().getDefinedLocations()); + log.info("testResovlesBy has defined locations: "+mgmt.getLocationRegistry().getDefinedLocations(true)); LocationSpec ls = mgmt.getLocationRegistry().getLocationSpec("named:foo").get(); Location l = mgmt.getLocationManager().createLocation(ls); @@ -127,7 +127,7 @@ public void testCircularReference() { BrooklynProperties properties = BrooklynProperties.Factory.newEmpty(); properties.put("brooklyn.location.named.bar", "named:bar"); mgmt = LocalManagementContextForTests.newInstance(properties); - log.info("bar properties gave defined locations: "+mgmt.getLocationRegistry().getDefinedLocations()); + log.info("bar properties gave defined locations: "+mgmt.getLocationRegistry().getDefinedLocations(true)); try { mgmt.getLocationRegistry().getLocationSpec("bar").get(); Asserts.shouldHaveFailedPreviously("Circular reference gave a location"); @@ -137,7 +137,7 @@ public void testCircularReference() { } protected boolean findLocationMatching(String regex) { - for (LocationDefinition d: mgmt.getLocationRegistry().getDefinedLocations().values()) { + for (LocationDefinition d: mgmt.getLocationRegistry().getDefinedLocations(true).values()) { if (d.getName()!=null && d.getName().matches(regex)) return true; } return false; @@ -159,8 +159,7 @@ public void testLocalhostNotPresentByDefault() { properties.put(LocalhostLocationResolver.LOCALHOST_ENABLED.getName(), false); mgmt = LocalManagementContextForTests.newInstance(properties); - log.info("RESOLVERS: "+mgmt.getLocationRegistry().getDefinedLocations()); - log.info("DEFINED LOCATIONS: "+mgmt.getLocationRegistry().getDefinedLocations()); + log.info("DEFINED LOCATIONS: "+mgmt.getLocationRegistry().getDefinedLocations(true)); Assert.assertFalse( findLocationMatching("localhost") ); } diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindCatalogItemTest.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindCatalogItemTest.java index 2418a3343d..77d8919832 100644 --- a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindCatalogItemTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindCatalogItemTest.java @@ -26,7 +26,9 @@ import java.io.File; import java.util.Map; +import java.util.Set; +import org.apache.brooklyn.api.catalog.BrooklynCatalog; import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType; import org.apache.brooklyn.api.entity.EntitySpec; @@ -40,6 +42,8 @@ import org.apache.brooklyn.core.catalog.internal.CatalogItemBuilder; import org.apache.brooklyn.core.catalog.internal.CatalogLocationItemDto; import org.apache.brooklyn.core.catalog.internal.CatalogPolicyItemDto; +import org.apache.brooklyn.core.catalog.internal.CatalogUtils; +import org.apache.brooklyn.core.entity.EntityFunctions; import org.apache.brooklyn.core.internal.BrooklynProperties; import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; import org.apache.brooklyn.core.policy.AbstractPolicy; @@ -58,7 +62,9 @@ import com.google.common.base.Optional; import com.google.common.base.Predicates; import com.google.common.base.Throwables; +import com.google.common.collect.FluentIterable; import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; public class RebindCatalogItemTest extends RebindTestFixtureWithApp { @@ -274,7 +280,7 @@ public void testDeletedCatalogItemIsNotPersisted() { assertFalse(itemPostRebind.isPresent(), "item="+itemPostRebind); } - @Test(invocationCount = 3) + @Test(invocationCount = 3) // why three times? public void testCanTagCatalogItemAfterRebind() { String symbolicName = "rebind-yaml-catalog-item-test"; String yaml = Joiner.on("\n").join( @@ -292,9 +298,13 @@ public void testCanTagCatalogItemAfterRebind() { .build(); origManagementContext.getCatalog().addItem(item); LOG.info("Added item to catalog: {}, id={}", item, item.getId()); - + + assertEquals(Iterables.size(origManagementContext.getTypeRegistry().getAll()), 1); assertEquals(Iterables.size(origManagementContext.getCatalog().getCatalogItems()), 1); + // in OSGi/type registry you cannot edit tags directly (they won't be persisted as they come from BOM) + // RegisteredType origItem = origManagementContext.getTypeRegistry().get(symbolicName, TEST_VERSION); CatalogItem origItem = origManagementContext.getCatalog().getCatalogItem(symbolicName, TEST_VERSION); + assertNotNull(origItem, "Item not in catalog (might be using new type registry where adding tags to items isn't supported as items come from BOM)"); final String tag = "tag1"; origItem.tags().addTag(tag); assertTrue(origItem.tags().containsTag(tag)); @@ -374,4 +384,37 @@ private void rebindAndAssertCatalogsAreEqual() { } assertCatalogsEqual(newManagementContext.getCatalog(), origManagementContext.getCatalog()); } + + protected void assertCatalogsEqual(BrooklynCatalog actual, BrooklynCatalog expected) { + Set actualIds = getCatalogItemIds(actual.getCatalogItems()); + Set expectedIds = getCatalogItemIds(expected.getCatalogItems()); + assertEquals(actualIds.size(), Iterables.size(actual.getCatalogItems()), "id keyset size != size of catalog. Are there duplicates in the catalog?"); + assertEquals(actualIds, expectedIds); + for (String versionedId : actualIds) { + String id = CatalogUtils.getSymbolicNameFromVersionedId(versionedId); + String version = CatalogUtils.getVersionFromVersionedId(versionedId); + assertCatalogItemsEqual(actual.getCatalogItem(id, version), expected.getCatalogItem(id, version)); + } + } + + private Set getCatalogItemIds(Iterable> catalogItems) { + return FluentIterable.from(catalogItems) + .transform(EntityFunctions.id()) + .copyInto(Sets.newHashSet()); + } + + protected void assertCatalogItemsEqual(CatalogItem actual, CatalogItem expected) { + assertEquals(actual.getClass(), expected.getClass()); + assertEquals(actual.getId(), expected.getId()); + assertEquals(actual.getDisplayName(), expected.getDisplayName()); + assertEquals(actual.getVersion(), expected.getVersion()); + assertEquals(actual.getDescription(), expected.getDescription()); + assertEquals(actual.getIconUrl(), expected.getIconUrl()); + assertEquals(actual.getVersion(), expected.getVersion()); + assertEquals(actual.getCatalogItemJavaType(), expected.getCatalogItemJavaType()); + assertEquals(actual.getCatalogItemType(), expected.getCatalogItemType()); + assertEquals(actual.getSpecType(), expected.getSpecType()); + assertEquals(actual.getSymbolicName(), expected.getSymbolicName()); + assertEquals(actual.getLibraries(), expected.getLibraries()); + } } diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java index 614d6303b7..c7d97a96c2 100644 --- a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java +++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java @@ -46,7 +46,6 @@ import org.apache.brooklyn.core.mgmt.persist.BrooklynMementoPersisterToObjectStore; import org.apache.brooklyn.core.mgmt.persist.FileBasedObjectStore; import org.apache.brooklyn.core.mgmt.persist.PersistMode; -import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; import org.apache.brooklyn.util.core.task.BasicExecutionManager; import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.repeat.Repeater; @@ -83,6 +82,10 @@ public void setUp() throws Exception { File mementoDirParent = mementoDir.getParentFile(); mementoDirBackup = new File(mementoDirParent, mementoDir.getName()+"."+Identifiers.makeRandomId(4)+".bak"); + origApp = null; + newApp = null; + newManagementContext = null; + origManagementContext = createOrigManagementContext(); origApp = createApp(); @@ -99,8 +102,6 @@ protected BrooklynProperties createBrooklynProperties() { /** @return A started management context */ protected LocalManagementContext createOrigManagementContext() { - LocalManagementContextForTests.newInstance(); - return RebindTestUtils.managementContextBuilder(mementoDir, classLoader) .persistPeriodMillis(getPersistPeriodMillis()) .haMode(getHaMode()) @@ -360,39 +361,6 @@ protected BrooklynMementoManifest loadMementoManifest() throws Exception { persister.stop(false); return mementoManifest; } - - protected void assertCatalogsEqual(BrooklynCatalog actual, BrooklynCatalog expected) { - Set actualIds = getCatalogItemIds(actual.getCatalogItems()); - Set expectedIds = getCatalogItemIds(expected.getCatalogItems()); - assertEquals(actualIds.size(), Iterables.size(actual.getCatalogItems()), "id keyset size != size of catalog. Are there duplicates in the catalog?"); - assertEquals(actualIds, expectedIds); - for (String versionedId : actualIds) { - String id = CatalogUtils.getSymbolicNameFromVersionedId(versionedId); - String version = CatalogUtils.getVersionFromVersionedId(versionedId); - assertCatalogItemsEqual(actual.getCatalogItem(id, version), expected.getCatalogItem(id, version)); - } - } - - private Set getCatalogItemIds(Iterable> catalogItems) { - return FluentIterable.from(catalogItems) - .transform(EntityFunctions.id()) - .copyInto(Sets.newHashSet()); - } - - protected void assertCatalogItemsEqual(CatalogItem actual, CatalogItem expected) { - assertEquals(actual.getClass(), expected.getClass()); - assertEquals(actual.getId(), expected.getId()); - assertEquals(actual.getDisplayName(), expected.getDisplayName()); - assertEquals(actual.getVersion(), expected.getVersion()); - assertEquals(actual.getDescription(), expected.getDescription()); - assertEquals(actual.getIconUrl(), expected.getIconUrl()); - assertEquals(actual.getVersion(), expected.getVersion()); - assertEquals(actual.getCatalogItemJavaType(), expected.getCatalogItemJavaType()); - assertEquals(actual.getCatalogItemType(), expected.getCatalogItemType()); - assertEquals(actual.getSpecType(), expected.getSpecType()); - assertEquals(actual.getSymbolicName(), expected.getSymbolicName()); - assertEquals(actual.getLibraries(), expected.getLibraries()); - } // protected void assertCatalogContains(BrooklynCatalog catalog, CatalogItem item) { // CatalogItem found = catalog.getCatalogItem(item.getSymbolicName(), item.getVersion()); diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestUtils.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestUtils.java index 912f7ef348..ba0ece2558 100644 --- a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestUtils.java +++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestUtils.java @@ -379,8 +379,20 @@ public static Application rebind(ManagementContext newManagementContext, File me } public static Application rebind(RebindOptions options) throws Exception { + boolean hadApps = true; + if (options!=null && options.origManagementContext!=null && options.origManagementContext.getApplications().isEmpty()) { + // clearly had no apps before, so don't treat as an error + hadApps = false; + } Collection newApps = rebindAll(options); - if (newApps.isEmpty()) throw new IllegalStateException("Application could not be found after rebind; serialization probably failed"); + if (newApps.isEmpty()) { + if (hadApps) { + throw new IllegalStateException("Application could not be found after rebind; serialization probably failed"); + } else { + // no apps before; probably testing catalog + return null; + } + } Function, Application> chooser = options.applicationChooserOnRebind; if (chooser != null) { return chooser.apply(newApps); diff --git a/core/src/test/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistryTest.java b/core/src/test/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistryTest.java index 6f2f573dc0..1c78684b86 100644 --- a/core/src/test/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistryTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistryTest.java @@ -63,15 +63,16 @@ public void testAddAndGet() { @Test public void testCantAddSameIdUnlessSameInstanceOrForced() { add(SAMPLE_TYPE); - RegisteredType sampleTypeClone = RegisteredTypes.bean("item.A", "1", new BasicTypeImplementationPlan("ignore", null), String.class); - add(sampleTypeClone, true); - Assert.assertNotEquals( registry().get(SAMPLE_TYPE.getId()), SAMPLE_TYPE ); + RegisteredType sampleTypeDifferent = RegisteredTypes.bean("item.A", "1", new BasicTypeImplementationPlan("ignore2", null), String.class); + add(sampleTypeDifferent, true); + Assert.assertSame( registry().get(SAMPLE_TYPE.getId()), sampleTypeDifferent ); + Assert.assertNotSame( registry().get(SAMPLE_TYPE.getId()), SAMPLE_TYPE ); add(SAMPLE_TYPE, true); - Assert.assertEquals( registry().get(SAMPLE_TYPE.getId()), SAMPLE_TYPE ); + Assert.assertSame( registry().get(SAMPLE_TYPE.getId()), SAMPLE_TYPE ); try { - add(sampleTypeClone); + add(sampleTypeDifferent); Asserts.shouldHaveFailedPreviously(); } catch (Exception e) { Asserts.expectedFailureContains(e, SAMPLE_TYPE.getSymbolicName()); diff --git a/core/src/test/java/org/apache/brooklyn/util/core/osgi/BundleMakerTest.java b/core/src/test/java/org/apache/brooklyn/util/core/osgi/BundleMakerTest.java index 515ab2c7de..f9c17370e5 100644 --- a/core/src/test/java/org/apache/brooklyn/util/core/osgi/BundleMakerTest.java +++ b/core/src/test/java/org/apache/brooklyn/util/core/osgi/BundleMakerTest.java @@ -26,8 +26,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; -import java.util.Enumeration; -import java.util.List; +import java.util.Collections; import java.util.Map; import java.util.jar.Attributes; import java.util.jar.JarFile; @@ -38,6 +37,8 @@ import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport; import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.stream.Streams; import org.osgi.framework.Bundle; @@ -50,7 +51,6 @@ import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; public class BundleMakerTest extends BrooklynMgmtUnitTestSupport { @@ -81,21 +81,21 @@ public void tearDown() throws Exception { @Test public void testCopyAdding() throws Exception { generatedJar = bundleMaker.copyAdding(emptyJar, ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()))); - assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext")); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext"), false); } @Test public void testCopyAddingToNonEmpty() throws Exception { tempJar = bundleMaker.copyAdding(emptyJar, ImmutableMap.of(new ZipEntry("preExisting.txt"), new ByteArrayInputStream("myPreExisting".getBytes()))); generatedJar = bundleMaker.copyAdding(tempJar, ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()))); - assertJarContents(generatedJar, ImmutableMap.of("preExisting.txt", "myPreExisting", "myfile.txt", "mytext")); + assertJarContents(generatedJar, ImmutableMap.of("preExisting.txt", "myPreExisting", "myfile.txt", "mytext"), false); } @Test public void testCopyAddingOverwritesEntry() throws Exception { tempJar = bundleMaker.copyAdding(emptyJar, ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("myPreExisting".getBytes()))); generatedJar = bundleMaker.copyAdding(tempJar, ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()))); - assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext")); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext"), false); } @Test @@ -109,7 +109,7 @@ public void testCopyAddingManifest() throws Exception { "Manifest-Version: 1.2.3\r\n" + "mykey: myval\r\n" + "\r\n"; - assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest)); + assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest), false); } @Test @@ -121,7 +121,7 @@ public void testCopyAddingManifestByMap() throws Exception { "Manifest-Version: 1.2.3\r\n" + "mykey: myval\r\n" + "\r\n"; - assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest)); + assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest), false); } @Test @@ -138,7 +138,7 @@ public void testCopyAddingManifestOverwritesExisting() throws Exception { "Manifest-Version: 1.2.3\r\n" + "mykey: myval\r\n" + "\r\n"; - assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest)); + assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest), false); } @Test @@ -147,7 +147,7 @@ public void testCopyRemovingPredicate() throws Exception { new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()), new ZipEntry("myfile2.txt"), new ByteArrayInputStream("mytext2".getBytes()))); generatedJar = bundleMaker.copyRemoving(tempJar, Predicates.equalTo("myfile.txt")); - assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext")); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext"), false); } @Test @@ -156,7 +156,7 @@ public void testCopyRemovingItems() throws Exception { new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()), new ZipEntry("myfile2.txt"), new ByteArrayInputStream("mytext2".getBytes()))); generatedJar = bundleMaker.copyRemoving(tempJar, ImmutableSet.of("myfile.txt")); - assertJarContents(generatedJar, ImmutableMap.of("myfile2.txt", "mytext2")); + assertJarContents(generatedJar, ImmutableMap.of("myfile2.txt", "mytext2"), false); } // TODO Not supported - can't remove an entire directory like this @@ -166,14 +166,14 @@ public void testCopyRemovingItemDir() throws Exception { new ZipEntry("mydir/myfile.txt"), new ByteArrayInputStream("mytext".getBytes()), new ZipEntry("mydir2/myfile2.txt"), new ByteArrayInputStream("mytext2".getBytes()))); generatedJar = bundleMaker.copyRemoving(tempJar, ImmutableSet.of("mydir")); - assertJarContents(generatedJar, ImmutableMap.of("mydir2/myfile2.txt", "mytext2")); + assertJarContents(generatedJar, ImmutableMap.of("mydir2/myfile2.txt", "mytext2"), false); } @Test public void testCopyRemovingItemsUnmatched() throws Exception { tempJar = bundleMaker.copyAdding(emptyJar, ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()))); generatedJar = bundleMaker.copyRemoving(tempJar, ImmutableSet.of("wrong.txt")); - assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext")); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext"), false); } @Test @@ -198,7 +198,7 @@ public void testHasOsgiManifestWhenValidManifest() throws Exception { @Test public void testCreateJarFromClasspathDirNoManifest() throws Exception { generatedJar = bundleMaker.createJarFromClasspathDir("/org/apache/brooklyn/util/core/osgi/test/bundlemaker/nomanifest"); - assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext", "subdir/myfile2.txt", "mytext2")); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext", "subdir/myfile2.txt", "mytext2"), false); } @Test @@ -209,7 +209,7 @@ public void testCreateJarFromClasspathDirWithManifest() throws Exception { "Manifest-Version: 1.2.3\r\n" + "mykey: myval\r\n" + "\r\n"; - assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest, "myfile.txt", "mytext", "subdir/myfile2.txt", "mytext2")); + assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest, "myfile.txt", "mytext", "subdir/myfile2.txt", "mytext2"), false); } @SuppressWarnings("deprecation") @@ -233,6 +233,23 @@ public void testInstallBundle() throws Exception { .get(); assertEquals(bundle2, bundle); } + + @Test + public void testCreate() throws Exception { + Map manifest = ImmutableMap.of( + Attributes.Name.MANIFEST_VERSION.toString(), "1.2.3", + Constants.BUNDLE_VERSION, "4.5.6", + Constants.BUNDLE_SYMBOLICNAME, "myname"); + + generatedJar = bundleMaker.createTempBundle("test", manifest, + ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()))); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext"), true); + + @SuppressWarnings("deprecation") + Bundle bundle = bundleMaker.installBundle(generatedJar, false); + assertEquals(bundle.getSymbolicName(), "myname"); + assertEquals(bundle.getVersion(), new Version("4.5.6")); + } private File createEmptyJarFile() throws Exception { File result = Os.newTempFile("base", "jar"); @@ -241,29 +258,28 @@ private File createEmptyJarFile() throws Exception { return result; } - private void assertJarContents(File f, Map expectedContents) throws Exception { + private void assertJarContents(File f, Map expectedContents, boolean othersAllowed) throws Exception { ZipFile zipFile = new ZipFile(f); - String zipEntriesMsg = "entries="+enumerationToList(zipFile.entries()); + expectedContents = MutableMap.copyOf(expectedContents); try { - for (Map.Entry entry : expectedContents.entrySet()) { - ZipEntry zipEntry = zipFile.getEntry(entry.getKey()); - assertNotNull(zipEntry, "No entry for "+entry.getKey()+"; "+zipEntriesMsg); - String entryContents = Streams.readFullyString(zipFile.getInputStream(zipEntry)); - assertEquals(entryContents, entry.getValue()); + for (ZipEntry zipEntry: Collections.list(zipFile.entries())) { + String expectedContent = expectedContents.remove(zipEntry.getName()); + if (expectedContent==null) { + if (!othersAllowed) { + assertNotNull(expectedContent, "Unexpected item in ZIP: "+zipEntry.getName()); + } + } else { + String entryContents = Streams.readFullyString(zipFile.getInputStream(zipEntry)); + assertEquals(entryContents, expectedContent, "Contents not eas expectd for "+zipEntry.getName()); + } + } + if (!expectedContents.isEmpty()) { + Asserts.fail("ZIP did not contain expected contents: "+expectedContents.keySet()); } - assertEquals(zipFile.size(), expectedContents.size(), zipEntriesMsg); } finally { zipFile.close(); } } - private List enumerationToList(Enumeration e) { - List result = Lists.newArrayList(); - while (e.hasMoreElements()) { - result.add(e.nextElement()); - } - return result; - - } } diff --git a/karaf/init/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/karaf/init/src/main/resources/OSGI-INF/blueprint/blueprint.xml index 30e55a7626..ed6a0cb0c0 100644 --- a/karaf/init/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/karaf/init/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -119,15 +119,4 @@ limitations under the License. - - - - - - - - - diff --git a/launcher-common/src/main/java/org/apache/brooklyn/launcher/command/support/CloudExplorerSupport.java b/launcher-common/src/main/java/org/apache/brooklyn/launcher/command/support/CloudExplorerSupport.java index dc4000bced..e35517d3d8 100644 --- a/launcher-common/src/main/java/org/apache/brooklyn/launcher/command/support/CloudExplorerSupport.java +++ b/launcher-common/src/main/java/org/apache/brooklyn/launcher/command/support/CloudExplorerSupport.java @@ -104,7 +104,7 @@ public Void call() throws Exception { locs.add(loc); } else if (allLocations) { // Find all named locations that point at different target clouds - Map definedLocations = managementContext.getLocationRegistry().getDefinedLocations(); + Map definedLocations = managementContext.getLocationRegistry().getDefinedLocations(true); for (LocationDefinition locationDef : definedLocations.values()) { Location loc = managementContext.getLocationManager().createLocation( diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java index c0d419c2eb..e97552015e 100644 --- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java @@ -2392,10 +2392,10 @@ private Predicate getReachableAddressesPredicate(ConfigBag try { return pollForFirstReachableHostAndPortPredicate = predicateType.newInstance(); } catch (IllegalAccessException | InstantiationException newInstanceException) { - throw Exceptions.propagate("Failed to instantiate " + predicateType, newInstanceException); + throw Exceptions.propagateAnnotated("Failed to instantiate " + predicateType, newInstanceException); } } catch (InvocationTargetException | InstantiationException e) { - throw Exceptions.propagate("Failed to instantiate " + predicateType + " with Map constructor", e); + throw Exceptions.propagateAnnotated("Failed to instantiate " + predicateType + " with Map constructor", e); } } } 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 8e1f5d8146..698f97fb56 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 @@ -233,9 +233,10 @@ public List listEntities( @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)") @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions); + // bad name - it is just templates @GET @Path("/applications") - @ApiOperation(value = "Fetch a list of application templates optionally matching a query", + @ApiOperation(value = "Fetch a list of templates (for applications) optionally matching a query", response = CatalogItemSummary.class, responseContainer = "List") public List listApplications( 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 ae22c79f5e..fcb26f9625 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,25 +35,15 @@ import javax.ws.rs.core.UriInfo; import org.apache.brooklyn.api.catalog.CatalogItem; -import org.apache.brooklyn.api.entity.Application; -import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.api.entity.EntitySpec; -import org.apache.brooklyn.api.location.Location; -import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; -import org.apache.brooklyn.api.policy.Policy; -import org.apache.brooklyn.api.policy.PolicySpec; -import org.apache.brooklyn.api.sensor.Enricher; -import org.apache.brooklyn.api.sensor.EnricherSpec; import org.apache.brooklyn.api.typereg.RegisteredType; -import org.apache.brooklyn.core.catalog.CatalogPredicates; -import org.apache.brooklyn.core.catalog.internal.CatalogItemComparator; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument; import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; +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.CatalogEnricherSummary; @@ -94,11 +84,10 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat private static final Logger log = LoggerFactory.getLogger(CatalogResource.class); private static final String LATEST = "latest"; - @SuppressWarnings("rawtypes") - private Function toCatalogItemSummary(final UriInfo ui) { - return new Function() { + private Function toCatalogItemSummary(final UriInfo ui) { + return new Function() { @Override - public CatalogItemSummary apply(@Nullable CatalogItem input) { + public CatalogItemSummary apply(@Nullable RegisteredType input) { return CatalogTransformer.catalogItemSummary(brooklyn(), input, ui.getBaseUriBuilder()); } }; @@ -138,6 +127,7 @@ public Response create(String yaml, boolean forceUpdate) { return createFromYaml(yaml, forceUpdate); } + @SuppressWarnings("deprecation") @Override public Response createFromYaml(String yaml, boolean forceUpdate) { if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ADD_CATALOG_ITEM, yaml)) { @@ -147,7 +137,12 @@ public Response createFromYaml(String yaml, boolean forceUpdate) { try { final Iterable> items = brooklyn().getCatalog().addItems(yaml, forceUpdate); - return buildCreateResponse(items); + List itemsRT = MutableList.of(); + for (CatalogItem ci: items) { + RegisteredType rt = brooklyn().getTypeRegistry().get(ci.getId()); + itemsRT.add(rt!=null ? rt : RegisteredTypes.of(ci)); + } + return buildCreateResponse(itemsRT); } catch (Exception e) { Exceptions.propagateIfFatal(e); return badRequest(e); @@ -167,7 +162,6 @@ public String getMessage() { return message; } - @SuppressWarnings("deprecation") public static BundleInstallationRestResult of(OsgiBundleInstallationResult in, ManagementContext mgmt, BrooklynRestResourceUtils brooklynU, UriInfo ui) { BundleInstallationRestResult result = new BundleInstallationRestResult(); result.message = in.getMessage(); @@ -176,10 +170,7 @@ public static BundleInstallationRestResult of(OsgiBundleInstallationResult in, M if (in.getCatalogItemsInstalled()!=null) { result.types = MutableMap.of(); for (String id: in.getCatalogItemsInstalled()) { - // TODO prefer to use RegisteredType, but we need transformer for those in REST - //RegisteredType ci = mgmt.getTypeRegistry().get(id); - - CatalogItem ci = CatalogUtils.getCatalogItemOptionalVersion(mgmt, id); + RegisteredType ci = mgmt.getTypeRegistry().get(id); CatalogItemSummary summary = CatalogTransformer.catalogItemSummary(brooklynU, ci, ui.getBaseUriBuilder()); result.types.put(id, summary); } @@ -201,10 +192,8 @@ public Response createFromArchive(byte[] zipInput, boolean detail, boolean force if (OsgiBundleInstallationResult.ResultCode.IGNORING_BUNDLE_AREADY_INSTALLED.equals(result.getWithoutError().getCode())) { result = ReferenceWithError.newInstanceThrowingError(result.getWithoutError(), new IllegalStateException( - "Updating existing catalog entries is forbidden: " + - result.getWithoutError().getMetadata().getSymbolicName() + ":" + - result.getWithoutError().getMetadata().getSuppliedVersionString() + - ". Use forceUpdate argument to override.")); + "Cannot add bundle" + result.getWithoutError().getMetadata().getVersionedName() + + "; different bundle with same name already installed")); } if (result.hasError()) { @@ -219,12 +208,12 @@ public Response createFromArchive(byte[] zipInput, boolean detail, boolean force return Response.status(Status.CREATED).entity( detail ? resultR : resultR.types ).build(); } - private Response buildCreateResponse(Iterable> catalogItems) { + private Response buildCreateResponse(Iterable catalogItems) { log.info("REST created catalog items: "+catalogItems); Map result = MutableMap.of(); - for (CatalogItem catalogItem: catalogItems) { + for (RegisteredType catalogItem: catalogItems) { try { result.put( catalogItem.getId(), @@ -305,10 +294,10 @@ public void deleteLocation(String locationId, String version) throws Exception { @Override public List listEntities(String regex, String fragment, boolean allVersions) { - Predicate>> filter = + Predicate filter = Predicates.and( - CatalogPredicates.IS_ENTITY, - CatalogPredicates.>disabled(false)); + RegisteredTypePredicates.IS_ENTITY, + RegisteredTypePredicates.disabled(false)); List result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions); return castList(result, CatalogEntitySummary.class); } @@ -316,11 +305,11 @@ public List listEntities(String regex, String fragment, bo @Override public List listApplications(String regex, String fragment, boolean allVersions) { @SuppressWarnings("unchecked") - Predicate>> filter = + Predicate filter = Predicates.and( - CatalogPredicates.IS_TEMPLATE, - CatalogPredicates.>deprecated(false), - CatalogPredicates.>disabled(false)); + RegisteredTypePredicates.template(true), + RegisteredTypePredicates.deprecated(false), + RegisteredTypePredicates.disabled(false)); return getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions); } @@ -333,12 +322,7 @@ public CatalogEntitySummary getEntity(String symbolicName, String version) { version = processVersion(version); - //TODO These casts are not pretty, we could just provide separate get methods for the different types? - //Or we could provide asEntity/asPolicy cast methods on the CataloItem doing a safety check internally - @SuppressWarnings("unchecked") - CatalogItem> result = - (CatalogItem>) brooklyn().getCatalog().getCatalogItem(symbolicName, version); - + RegisteredType result = brooklyn().getTypeRegistry().get(symbolicName, version); if (result==null) { throw WebResourceUtils.notFound("Entity with id '%s:%s' not found", symbolicName, version); } @@ -353,10 +337,10 @@ public CatalogEntitySummary getApplication(String symbolicName, String version) @Override public List listPolicies(String regex, String fragment, boolean allVersions) { - Predicate>> filter = + Predicate filter = Predicates.and( - CatalogPredicates.IS_POLICY, - CatalogPredicates.>disabled(false)); + RegisteredTypePredicates.IS_POLICY, + RegisteredTypePredicates.disabled(false)); List result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions); return castList(result, CatalogPolicySummary.class); } @@ -369,11 +353,7 @@ public CatalogPolicySummary getPolicy(String policyId, String version) throws Ex } version = processVersion(version); - - @SuppressWarnings("unchecked") - CatalogItem> result = - (CatalogItem>)brooklyn().getCatalog().getCatalogItem(policyId, version); - + RegisteredType result = brooklyn().getTypeRegistry().get(policyId, version); if (result==null) { throw WebResourceUtils.notFound("Policy with id '%s:%s' not found", policyId, version); } @@ -383,10 +363,10 @@ public CatalogPolicySummary getPolicy(String policyId, String version) throws Ex @Override public List listLocations(String regex, String fragment, boolean allVersions) { - Predicate>> filter = + Predicate filter = Predicates.and( - CatalogPredicates.IS_LOCATION, - CatalogPredicates.>disabled(false)); + RegisteredTypePredicates.IS_LOCATION, + RegisteredTypePredicates.disabled(false)); List result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions); return castList(result, CatalogLocationSummary.class); } @@ -399,11 +379,7 @@ public CatalogLocationSummary getLocation(String locationId, String version) thr } version = processVersion(version); - - @SuppressWarnings("unchecked") - CatalogItem> result = - (CatalogItem>)brooklyn().getCatalog().getCatalogItem(locationId, version); - + RegisteredType result = brooklyn().getTypeRegistry().get(locationId, version); if (result==null) { throw WebResourceUtils.notFound("Location with id '%s:%s' not found", locationId, version); } @@ -412,22 +388,22 @@ public CatalogLocationSummary getLocation(String locationId, String version) thr } @SuppressWarnings({ "unchecked", "rawtypes" }) - private List getCatalogItemSummariesMatchingRegexFragment(Predicate> type, String regex, String fragment, boolean allVersions) { - List filters = new ArrayList(); + private List getCatalogItemSummariesMatchingRegexFragment( + Predicate type, String regex, String fragment, boolean allVersions) { + List> filters = new ArrayList(); filters.add(type); if (Strings.isNonEmpty(regex)) - filters.add(CatalogPredicates.stringRepresentationMatches(StringPredicates.containsRegex(regex))); + filters.add(RegisteredTypePredicates.stringRepresentationMatches(StringPredicates.containsRegex(regex))); if (Strings.isNonEmpty(fragment)) - filters.add(CatalogPredicates.stringRepresentationMatches(StringPredicates.containsLiteralIgnoreCase(fragment))); + filters.add(RegisteredTypePredicates.stringRepresentationMatches(StringPredicates.containsLiteralIgnoreCase(fragment))); if (!allVersions) - filters.add(CatalogPredicates.isBestVersion(mgmt())); + filters.add(RegisteredTypePredicates.isBestVersion(mgmt())); - filters.add(CatalogPredicates.entitledToSee(mgmt())); + filters.add(RegisteredTypePredicates.entitledToSee(mgmt())); - ImmutableList> sortedItems = - FluentIterable.from(brooklyn().getCatalog().getCatalogItems()) - .filter(Predicates.and(filters)) - .toSortedList(CatalogItemComparator.getInstance()); + ImmutableList sortedItems = + FluentIterable.from(brooklyn().getTypeRegistry().getMatching(Predicates.and(filters))) + .toSortedList(RegisteredTypes.RegisteredTypeNameThenBestFirstComparator.INSTANCE); return Lists.transform(sortedItems, toCatalogItemSummary(ui)); } @@ -465,10 +441,10 @@ public void setDisabled(String itemId, boolean disabled) { @Override public List listEnrichers(@ApiParam(name = "regex", value = "Regular expression to search for") @DefaultValue("") String regex, @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") @DefaultValue("") String fragment, @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)") @DefaultValue("false") boolean includeAllVersions) { - Predicate>> filter = + Predicate filter = Predicates.and( - CatalogPredicates.IS_ENRICHER, - CatalogPredicates.>disabled(false)); + RegisteredTypePredicates.IS_ENRICHER, + RegisteredTypePredicates.disabled(false)); List result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, includeAllVersions); return castList(result, CatalogEnricherSummary.class); } @@ -479,13 +455,8 @@ public CatalogEnricherSummary getEnricher(@ApiParam(name = "enricherId", value = throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry", Entitlements.getEntitlementContext().user()); } - version = processVersion(version); - - @SuppressWarnings("unchecked") - CatalogItem> result = - (CatalogItem>)brooklyn().getCatalog().getCatalogItem(enricherId, version); - + RegisteredType result = brooklyn().getTypeRegistry().get(enricherId, version); if (result==null) { throw WebResourceUtils.notFound("Enricher with id '%s:%s' not found", enricherId, version); } diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java index 55159facaf..052114a67c 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java @@ -83,7 +83,7 @@ public LocationSummary apply(LocationDefinition l) { } } }; - return FluentIterable.from(brooklyn().getLocationRegistry().getDefinedLocations().values()) + return FluentIterable.from(brooklyn().getLocationRegistry().getDefinedLocations(true).values()) .transform(transformer) .filter(LocationSummary.class) .toSortedList(nameOrSpecComparator()); diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java index 4259120c57..9fe024a795 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java @@ -25,6 +25,7 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import javax.ws.rs.core.Application; import javax.ws.rs.core.UriBuilder; import org.apache.brooklyn.api.catalog.CatalogItem; @@ -41,9 +42,12 @@ import org.apache.brooklyn.api.sensor.Enricher; import org.apache.brooklyn.api.sensor.EnricherSpec; import org.apache.brooklyn.api.sensor.Sensor; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.entity.EntityDynamicType; import org.apache.brooklyn.core.mgmt.BrooklynTags; import org.apache.brooklyn.core.objs.BrooklynTypes; +import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.rest.api.CatalogApi; import org.apache.brooklyn.rest.domain.CatalogEnricherSummary; import org.apache.brooklyn.rest.domain.CatalogEntitySummary; @@ -67,11 +71,180 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; -@SuppressWarnings("deprecation") public class CatalogTransformer { private static final org.slf4j.Logger log = LoggerFactory.getLogger(CatalogTransformer.class); + + public static CatalogEntitySummary catalogEntitySummary(BrooklynRestResourceUtils b, RegisteredType item, UriBuilder ub) { + Set config = Sets.newLinkedHashSet(); + Set sensors = Sets.newTreeSet(SummaryComparators.nameComparator()); + Set effectors = Sets.newTreeSet(SummaryComparators.nameComparator()); + + EntitySpec spec = null; + + try { + 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)); + + } 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); + } + } + + return new CatalogEntitySummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + spec!=null ? spec.getType().getName() : item.getSuperTypes().toString(), + spec!=null ? + CatalogItemType.ofTargetClass(spec.getType()).name() : + // RegisteredTypes.isTemplate(item) ? "template" : // could check this, but more reliable for clients to rely on tag + "unknown", + RegisteredTypes.getImplementationDataStringForSpec(item), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl(), ub), + makeTags(spec, item), config, sensors, effectors, + item.isDeprecated(), makeLinks(item, ub)); + } + + public static CatalogItemSummary catalogItemSummary(BrooklynRestResourceUtils b, RegisteredType item, UriBuilder ub) { + try { + if (item.getSuperTypes().contains(Application.class) || + item.getSuperTypes().contains(Entity.class)) { + return catalogEntitySummary(b, item, ub); + } else if (item.getSuperTypes().contains(Policy.class)) { + return catalogPolicySummary(b, item, ub); + } else if (item.getSuperTypes().contains(Enricher.class)) { + return catalogEnricherSummary(b, item, ub); + } else if (item.getSuperTypes().contains(Location.class)) { + return catalogLocationSummary(b, item, ub); + } else { + log.debug("Misc catalog item type when getting self link (supplying generic item): "+item+" "+item.getSuperTypes()); + } + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.warn("Invalid item in catalog when converting REST summaries (supplying generic item), at "+item+": "+e, e); + } + return new CatalogItemSummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + item.getSuperTypes().toString(), + item.getKind()==RegisteredTypeKind.BEAN ? "bean" : "unknown", + RegisteredTypes.getImplementationDataStringForSpec(item), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl(), ub), item.getTags(), item.isDeprecated(), makeLinks(item, ub)); + } + + public static CatalogPolicySummary catalogPolicySummary(BrooklynRestResourceUtils b, RegisteredType item, UriBuilder ub) { + final Set config = Sets.newLinkedHashSet(); + PolicySpec spec = null; + try{ + spec = b.getTypeRegistry().createSpec(item, null, PolicySpec.class); + for (final SpecParameter input : spec.getParameters()){ + config.add(EntityTransformer.policyConfigSummary(input)); + } + }catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.trace("Unable to create policy spec for "+item+": "+e, e); + } + return new CatalogPolicySummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + spec!=null ? spec.getType().getName() : item.getSuperTypes().toString(), + CatalogItemType.POLICY.toString(), + RegisteredTypes.getImplementationDataStringForSpec(item), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl(), ub), config, + item.getTags(), item.isDeprecated(), makeLinks(item, ub)); + } + + public static CatalogEnricherSummary catalogEnricherSummary(BrooklynRestResourceUtils b, RegisteredType item, UriBuilder ub) { + final Set config = Sets.newLinkedHashSet(); + EnricherSpec spec = null; + try{ + spec = b.getTypeRegistry().createSpec(item, null, EnricherSpec.class); + for (final SpecParameter input : spec.getParameters()){ + config.add(EntityTransformer.enricherConfigSummary(input)); + } + }catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.trace("Unable to create policy spec for "+item+": "+e, e); + } + return new CatalogEnricherSummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + spec!=null ? spec.getType().getName() : item.getSuperTypes().toString(), + CatalogItemType.ENRICHER.toString(), + RegisteredTypes.getImplementationDataStringForSpec(item), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl(), ub), config, + item.getTags(), item.isDeprecated(), makeLinks(item, ub)); + } + + public static CatalogLocationSummary catalogLocationSummary(BrooklynRestResourceUtils b, RegisteredType item, UriBuilder ub) { + Set config = ImmutableSet.of(); + return new CatalogLocationSummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + item.getSuperTypes().toString(), + CatalogItemType.LOCATION.toString(), + RegisteredTypes.getImplementationDataStringForSpec(item), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl(), ub), config, + item.getTags(), item.isDeprecated(), makeLinks(item, ub)); + } + + protected static Map makeLinks(RegisteredType item, UriBuilder ub) { + return MutableMap.of().addIfNotNull("self", getSelfLink(item, ub)); + } + protected static URI getSelfLink(RegisteredType item, UriBuilder ub) { + String itemId = item.getId(); + if (item.getSuperTypes().contains(Application.class)) { + return serviceUriBuilder(ub, CatalogApi.class, "getApplication").build(itemId, item.getVersion()); + } else if (item.getSuperTypes().contains(Entity.class)) { + return serviceUriBuilder(ub, CatalogApi.class, "getEntity").build(itemId, item.getVersion()); + } else if (item.getSuperTypes().contains(Policy.class)) { + return serviceUriBuilder(ub, CatalogApi.class, "getPolicy").build(itemId, item.getVersion()); + } else if (item.getSuperTypes().contains(Enricher.class)) { + return serviceUriBuilder(ub, CatalogApi.class, "getEnricher").build(itemId, item.getVersion()); + } else if (item.getSuperTypes().contains(Location.class)) { + return serviceUriBuilder(ub, CatalogApi.class, "getLocation").build(itemId, item.getVersion()); + } else { + log.warn("Unexpected catalog item type when getting self link (not supplying self link): "+item+" "+item.getSuperTypes()); + return null; + } + } + private static String tidyIconLink(BrooklynRestResourceUtils b, RegisteredType item, String iconUrl, UriBuilder ub) { + if (b.isUrlServerSideAndSafe(iconUrl)) { + return serviceUriBuilder(ub, CatalogApi.class, "getIcon").build(item.getSymbolicName(), item.getVersion()).toString(); + } + return iconUrl; + } + + private static Set makeTags(EntitySpec spec, RegisteredType item) { + return makeTags(spec, MutableSet.copyOf(item.getTags())); + } + private static Set makeTags(EntitySpec spec, CatalogItem item) { + return makeTags(spec, MutableSet.copyOf(item.tags().getTags())); + } + private static Set makeTags(EntitySpec spec, Set tags) { + // Combine tags on item with an InterfacesTag. + if (spec != null) { + Class type; + if (spec.getImplementation() != null) { + type = spec.getImplementation(); + } else { + type = spec.getType(); + } + if (type != null) { + tags.add(new BrooklynTags.TraitsTag(Reflections.getAllInterfaces(type))); + } + } + return tags; + } + + /** @deprecated since 0.12.0 use {@link RegisteredType} methods instead */ @Deprecated public static CatalogEntitySummary catalogEntitySummary(BrooklynRestResourceUtils b, CatalogItem> item, UriBuilder ub) { Set config = Sets.newLinkedHashSet(); Set sensors = Sets.newTreeSet(SummaryComparators.nameComparator()); @@ -111,6 +284,7 @@ public static CatalogEntitySummary catalogEntitySummary(Brook item.isDeprecated(), makeLinks(item, ub)); } + /** @deprecated since 0.12.0 use {@link RegisteredType} methods instead */ @Deprecated @SuppressWarnings({ "unchecked", "rawtypes" }) public static CatalogItemSummary catalogItemSummary(BrooklynRestResourceUtils b, CatalogItem item, UriBuilder ub) { try { @@ -136,6 +310,7 @@ public static CatalogItemSummary catalogItemSummary(BrooklynRestResourceUtils b, item.getDescription(), tidyIconLink(b, item, item.getIconUrl(), ub), item.tags().getTags(), item.isDeprecated(), makeLinks(item, ub)); } + /** @deprecated since 0.12.0 use {@link RegisteredType} methods instead */ @Deprecated public static CatalogPolicySummary catalogPolicySummary(BrooklynRestResourceUtils b, CatalogItem> item, UriBuilder ub) { final Set config = Sets.newLinkedHashSet(); try{ @@ -153,6 +328,7 @@ public static CatalogPolicySummary catalogPolicySummary(BrooklynRestResourceUtil item.tags().getTags(), item.isDeprecated(), makeLinks(item, ub)); } + /** @deprecated since 0.12.0 use {@link RegisteredType} methods instead */ @Deprecated public static CatalogEnricherSummary catalogEnricherSummary(BrooklynRestResourceUtils b, CatalogItem> item, UriBuilder ub) { final Set config = Sets.newLinkedHashSet(); try{ @@ -170,6 +346,7 @@ public static CatalogEnricherSummary catalogEnricherSummary(BrooklynRestResource item.tags().getTags(), item.isDeprecated(), makeLinks(item, ub)); } + /** @deprecated since 0.12.0 use {@link RegisteredType} methods instead */ @Deprecated public static CatalogLocationSummary catalogLocationSummary(BrooklynRestResourceUtils b, CatalogItem> item, UriBuilder ub) { Set config = ImmutableSet.of(); return new CatalogLocationSummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), @@ -178,11 +355,11 @@ public static CatalogLocationSummary catalogLocationSummary(BrooklynRestResource item.tags().getTags(), item.isDeprecated(), makeLinks(item, ub)); } - protected static Map makeLinks(CatalogItem item, UriBuilder ub) { + private static Map makeLinks(CatalogItem item, UriBuilder ub) { return MutableMap.of().addIfNotNull("self", getSelfLink(item, ub)); } - protected static URI getSelfLink(CatalogItem item, UriBuilder ub) { + private static URI getSelfLink(CatalogItem item, UriBuilder ub) { String itemId = item.getId(); switch (item.getCatalogItemType()) { case TEMPLATE: @@ -207,21 +384,4 @@ private static String tidyIconLink(BrooklynRestResourceUtils b, CatalogItem return iconUrl; } - private static Set makeTags(EntitySpec spec, CatalogItem item) { - // Combine tags on item with an InterfacesTag. - Set tags = MutableSet.copyOf(item.tags().getTags()); - if (spec != null) { - Class type; - if (spec.getImplementation() != null) { - type = spec.getImplementation(); - } else { - type = spec.getType(); - } - if (type != null) { - tags.add(new BrooklynTags.TraitsTag(Reflections.getAllInterfaces(type))); - } - } - return tags; - } - } diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java index fa3fb2c2db..430cbbe95f 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java @@ -25,12 +25,11 @@ import javax.ws.rs.core.UriBuilder; -import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.location.LocationDefinition; import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; -import org.apache.brooklyn.core.catalog.internal.CatalogUtils; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.config.Sanitizer; import org.apache.brooklyn.core.location.BasicLocationDefinition; import org.apache.brooklyn.core.location.CatalogLocationResolver; @@ -62,7 +61,6 @@ public static LocationSummary newInstance(String id, org.apache.brooklyn.rest.do return newInstance(null, id, locationSpec, LocationDetailLevel.LOCAL_EXCLUDING_SECRET, ub); } - @SuppressWarnings("unchecked") private static LocationSummary newInstance(ManagementContext mgmt, LocationSpec spec, ConfigBag explicitConfig, String optionalExplicitId, String name, String specString, @@ -91,10 +89,7 @@ private static LocationSummary newInstance(ManagementContext mgmt, CatalogLocationSummary catalogSummary = null; if (CatalogLocationResolver.isLegacyWrappedReference(specString)) { -// RegisteredType type = mgmt.getTypeRegistry().get(CatalogLocationResolver.unwrapLegacyWrappedReference(specString)); - // TODO REST items should switch to using the RegisteredType - @SuppressWarnings({ "rawtypes", "deprecation" }) - CatalogItem ci = CatalogUtils.getCatalogItemOptionalVersion(mgmt, CatalogLocationResolver.unwrapLegacyWrappedReference(specString)); + RegisteredType ci = mgmt.getTypeRegistry().get(CatalogLocationResolver.unwrapLegacyWrappedReference(specString)); if (ci!=null) { BrooklynRestResourceUtils br = new BrooklynRestResourceUtils(mgmt); catalogSummary = CatalogTransformer.catalogLocationSummary(br, ci, ub); diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java index 66467a10cd..9e1ce53670 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java @@ -39,6 +39,7 @@ import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.policy.Policy; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants; import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent; @@ -86,6 +87,10 @@ public BrooklynRestResourceUtils(ManagementContext mgmt) { this.mgmt = mgmt; } + public BrooklynTypeRegistry getTypeRegistry() { + return mgmt.getTypeRegistry(); + } + public BrooklynCatalog getCatalog() { return mgmt.getCatalog(); } diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java index 277fd3225e..2a53cc9fee 100644 --- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java +++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java @@ -32,7 +32,11 @@ import java.util.Set; import java.util.concurrent.TimeoutException; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.GenericType; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; @@ -71,7 +75,7 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.http.HttpAsserts; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.time.Duration; +import org.apache.cxf.jaxrs.client.WebClient; import org.apache.http.HttpHeaders; import org.apache.http.entity.ContentType; import org.slf4j.Logger; @@ -87,13 +91,6 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Maps; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.client.Entity; -import javax.ws.rs.core.GenericType; -import javax.ws.rs.core.MultivaluedHashMap; - -import org.apache.cxf.jaxrs.client.WebClient; - @Test(singleThreaded = true, // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures suiteName = "ApplicationResourceTest") @@ -608,11 +605,7 @@ public void testDisabledApplicationCatalog() throws TimeoutException, Interrupte String itemVersion = "1.0"; String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication"; - // Deploy the catalog item - addTestCatalogItem(itemSymbolicName, "template", itemVersion, serviceType); - List itemSummaries = client().path("/catalog/applications") - .query("fragment", itemSymbolicName).query("allVersions", "true").get(new GenericType>() {}); - CatalogItemSummary itemSummary = Iterables.getOnlyElement(itemSummaries); + CatalogItemSummary itemSummary = testTemplateItem(itemSymbolicName, itemVersion, serviceType); String itemVersionedId = String.format("%s:%s", itemSummary.getSymbolicName(), itemSummary.getVersion()); assertEquals(itemSummary.getId(), itemVersionedId); @@ -660,6 +653,15 @@ public void testDisabledApplicationCatalog() throws TimeoutException, Interrupte } } + protected CatalogEntitySummary testTemplateItem(String itemSymbolicName, String itemVersion, String serviceType) { + // Deploy the catalog item + addTestCatalogItem(itemSymbolicName, "template", itemVersion, serviceType); + log.info("Types after adding template: "+manager.getTypeRegistry().getAll()); + List itemSummaries = client().path("/catalog/applications") + .query("fragment", itemSymbolicName).query("allVersions", "true").get(new GenericType>() {}); + return Iterables.getOnlyElement(itemSummaries); + } + private void deprecateCatalogItem(String symbolicName, String version, boolean deprecated) { String id = String.format("%s:%s", symbolicName, version); Response response = client().path(String.format("/catalog/entities/%s/deprecated", id)) diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java index faec520cf0..0a91d5994e 100644 --- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java +++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java @@ -22,7 +22,8 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; -import java.awt.*; +import java.awt.Image; +import java.awt.Toolkit; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; @@ -47,10 +48,13 @@ 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.typereg.ManagedBundle; import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; 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; @@ -66,6 +70,7 @@ import org.apache.brooklyn.util.collections.MutableMap; 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; @@ -93,10 +98,32 @@ public class CatalogResourceTest extends BrooklynRestResourceTest { 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 */ @@ -167,7 +194,7 @@ public void testRegisterCustomEntityTopLevelSyntaxWithBundleWhereEntityIsFromCor public void testRegisterOsgiPolicyTopLevelSyntax() { TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH); - String symbolicName = "my.catalog.policy.id"; + 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; @@ -214,7 +241,7 @@ public void testFilterListOfEntitiesByName() { .query("fragment", "vaNIllasOFTWAREpROCESS").get(new GenericType>() {}); assertEquals(entities.size(), 1); - log.info("RedisCluster-like entities are: " + entities); + log.info("MAtching entities are: " + entities); List entities2 = client().path("/catalog/entities") .query("regex", "[Vv]an.[alS]+oftware\\w+").get(new GenericType>() {}); @@ -963,8 +990,11 @@ public void testGetOnlyLatestApplication() { assertEquals(application.getVersion(), TEST_LASTEST_VERSION); } - @Test(dependsOnMethods = {"testGetOnlyLatestApplication"}) + @Test public void testGetOnlyLatestDifferentCases() { + // depends on installation of this + testGetOnlyLatestApplication(); + String symbolicName = "latest.catalog.application.id"; CatalogItemSummary application = client().path("/catalog/applications/" + symbolicName + "/LaTeSt") @@ -1018,8 +1048,11 @@ public void testGetOnlyLatestLocation() { assertEquals(application.getVersion(), TEST_LASTEST_VERSION); } - @Test(dependsOnMethods = {"testGetOnlyLatestApplication", "testGetOnlyLatestDifferentCases"}) + @Test public void testDeleteOnlyLatestApplication() throws IOException { + // depends on installation of this + testGetOnlyLatestApplication(); + String symbolicName = "latest.catalog.application.id"; Response deleteResponse = client().path("/catalog/applications/" + symbolicName + "/latest").delete(); @@ -1031,8 +1064,11 @@ public void testDeleteOnlyLatestApplication() throws IOException { assertEquals(applications.get(0).getVersion(), TEST_VERSION); } - @Test(dependsOnMethods = {"testGetOnlyLatestEntity"}) + @Test public void testDeleteOnlyLatestEntity() throws IOException { + // depends on installation of this + testGetOnlyLatestEntity(); + String symbolicName = "latest.catalog.entity.id"; Response deleteResponse = client().path("/catalog/entities/" + symbolicName + "/latest").delete(); @@ -1044,8 +1080,11 @@ public void testDeleteOnlyLatestEntity() throws IOException { assertEquals(applications.get(0).getVersion(), TEST_VERSION); } - @Test(dependsOnMethods = {"testGetOnlyLatestPolicy"}) + @Test public void testDeleteOnlyLatestPolicy() throws IOException { + // depends on installation of this + testGetOnlyLatestPolicy(); + String symbolicName = "latest.catalog.policy.id"; Response deleteResponse = client().path("/catalog/policies/" + symbolicName + "/latest").delete(); @@ -1057,8 +1096,10 @@ public void testDeleteOnlyLatestPolicy() throws IOException { assertEquals(applications.get(0).getVersion(), TEST_VERSION); } - @Test(dependsOnMethods = {"testGetOnlyLatestLocation"}) + @Test public void testDeleteOnlyLatestLocation() throws IOException { + testGetOnlyLatestLocation(); + String symbolicName = "latest.catalog.location.id"; Response deleteResponse = client().path("/catalog/locations/" + symbolicName + "/latest").delete(); diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java index c61c3955bd..2c2acfdf96 100644 --- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java +++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java @@ -31,6 +31,7 @@ import javax.ws.rs.core.Response; import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.location.LocationConfigKeys; import org.apache.brooklyn.core.location.SimulatedLocation; import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation; @@ -58,7 +59,6 @@ public class LocationResourceTest extends BrooklynRestResourceTest { private static final Logger log = LoggerFactory.getLogger(LocationResourceTest.class); private String legacyLocationName = "my-jungle-legacy"; - private String legacyLocationVersion = "0.0.0.SNAPSHOT"; private String locationName = "my-jungle"; private String locationVersion = "0.1.2"; @@ -81,7 +81,7 @@ public void testAddLegacyLocationDefinition() { log.info("added legacy, at: " + addedLegacyLocationUri); LocationSummary location = client().path(response.getLocation()).get(LocationSummary.class); log.info(" contents: " + location); - assertEquals(location.getSpec(), "brooklyn.catalog:"+legacyLocationName+":"+legacyLocationVersion); + assertEquals(location.getSpec(), "brooklyn.catalog:"+legacyLocationName+":"+BasicBrooklynCatalog.NO_VERSION); assertTrue(addedLegacyLocationUri.getPath().startsWith("/locations/")); JcloudsLocation l = (JcloudsLocation) getManagementContext().getLocationRegistry().getLocationManaged(legacyLocationName); @@ -144,7 +144,7 @@ public boolean apply(@Nullable LocationSummary l) { }); LocationSummary location = Iterables.getOnlyElement(matching); - URI expectedLocationUri = URI.create(getEndpointAddress() + "/locations/"+locationName).normalize(); + URI expectedLocationUri = URI.create(getEndpointAddress() + "/locations/"+locationName+":"+locationVersion).normalize(); Assert.assertEquals(location.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion); Assert.assertEquals(location.getLinks().get("self").toString(), expectedLocationUri.getPath()); } @@ -180,7 +180,7 @@ public void testGetLocationConfig() { @Test(dependsOnMethods = { "testAddLegacyLocationDefinition" }) @Deprecated public void testDeleteLocation() { - final int size = getLocationRegistry().getDefinedLocations().size(); + final int size = getLocationRegistry().getDefinedLocations(true).size(); URI expectedLocationUri = URI.create("/locations/"+legacyLocationName); Response response = client().path(expectedLocationUri).delete(); @@ -188,7 +188,7 @@ public void testDeleteLocation() { Asserts.succeedsEventually(new Runnable() { @Override public void run() { - assertEquals(getLocationRegistry().getDefinedLocations().size(), size - 1); + assertEquals(getLocationRegistry().getDefinedLocations(true).size(), size - 1); } }); } diff --git a/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java b/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java index f68f14287a..606a2a1933 100644 --- a/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java +++ b/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java @@ -433,7 +433,7 @@ public Void apply(CatalogInitialization catInit) { try { populateCatalog(catInit.getManagementContext().getCatalog()); } catch (Throwable e) { - catInit.handleException(e, "overridden main class populate catalog"); + catInit.handleException(e, "in main class populate catalog override"); } // Force load of catalog (so web console is up to date) diff --git a/server-cli/src/main/resources/brooklyn/default.catalog.bom b/server-cli/src/main/resources/brooklyn/default.catalog.bom index 5a5d1b2731..d4d3797f17 100644 --- a/server-cli/src/main/resources/brooklyn/default.catalog.bom +++ b/server-cli/src/main/resources/brooklyn/default.catalog.bom @@ -3,11 +3,13 @@ # and templates to get started using Brooklyn brooklyn.catalog: + bundle: brooklyn-default-catalog version: "0.12.0-SNAPSHOT" # BROOKLYN_VERSION - items: # load everything in the classpath with a @Catalog annotation - - scanJavaAnnotations: true + scanJavaAnnotations: true + + items: - id: server itemType: entity diff --git a/utils/common/dependencies/osgi/com-example-entities/src/main/resources/catalog.bom b/utils/common/dependencies/osgi/com-example-entities/src/main/resources/catalog.bom index a5fe6bd0cd..9cc84c3c46 100644 --- a/utils/common/dependencies/osgi/com-example-entities/src/main/resources/catalog.bom +++ b/utils/common/dependencies/osgi/com-example-entities/src/main/resources/catalog.bom @@ -17,7 +17,6 @@ brooklyn.catalog: - version: "0.12.0-SNAPSHOT" # BROOKLYN_VERSION itemType: entity description: For testing loading catalog.bom with catalog scan displayName: I Haz Catalog diff --git a/utils/common/dependencies/osgi/entities/src/main/resources/catalog.bom b/utils/common/dependencies/osgi/entities/src/main/resources/catalog.bom index e491a23a30..8a768f1f77 100644 --- a/utils/common/dependencies/osgi/entities/src/main/resources/catalog.bom +++ b/utils/common/dependencies/osgi/entities/src/main/resources/catalog.bom @@ -17,7 +17,6 @@ brooklyn.catalog: - version: "0.12.0-SNAPSHOT" # BROOKLYN_VERSION itemType: entity description: For testing loading catalog.bom with catalog scan displayName: I Haz Catalog diff --git a/utils/common/dependencies/osgi/more-entities-v1/src/main/resources/catalog.bom b/utils/common/dependencies/osgi/more-entities-v1/src/main/resources/catalog.bom index c3e97dc5c6..2426d45446 100644 --- a/utils/common/dependencies/osgi/more-entities-v1/src/main/resources/catalog.bom +++ b/utils/common/dependencies/osgi/more-entities-v1/src/main/resources/catalog.bom @@ -16,7 +16,6 @@ # under the License. brooklyn.catalog: - version: "0.12.0-SNAPSHOT" # BROOKLYN_VERSION itemType: entity items: - id: org.apache.brooklyn.test.osgi.entities.more.MoreEntity diff --git a/utils/common/dependencies/osgi/more-entities-v2-evil-twin/src/main/resources/catalog.bom b/utils/common/dependencies/osgi/more-entities-v2-evil-twin/src/main/resources/catalog.bom index c3e97dc5c6..2426d45446 100644 --- a/utils/common/dependencies/osgi/more-entities-v2-evil-twin/src/main/resources/catalog.bom +++ b/utils/common/dependencies/osgi/more-entities-v2-evil-twin/src/main/resources/catalog.bom @@ -16,7 +16,6 @@ # under the License. brooklyn.catalog: - version: "0.12.0-SNAPSHOT" # BROOKLYN_VERSION itemType: entity items: - id: org.apache.brooklyn.test.osgi.entities.more.MoreEntity diff --git a/utils/common/dependencies/osgi/more-entities-v2/src/main/resources/catalog.bom b/utils/common/dependencies/osgi/more-entities-v2/src/main/resources/catalog.bom index 630750898c..0da6334a47 100644 --- a/utils/common/dependencies/osgi/more-entities-v2/src/main/resources/catalog.bom +++ b/utils/common/dependencies/osgi/more-entities-v2/src/main/resources/catalog.bom @@ -16,7 +16,6 @@ # under the License. brooklyn.catalog: - version: "0.12.0-SNAPSHOT" # BROOKLYN_VERSION items: - id: org.apache.brooklyn.test.osgi.entities.more.MorePolicy itemType: policy diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java index 3b41cf6587..55bf8a266e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java @@ -24,7 +24,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.UndeclaredThrowableException; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -130,6 +129,18 @@ public static RuntimeException propagate(Throwable throwable) { throw new PropagatedRuntimeException(throwable); } + /** + * Convenience for {@link #propagateAnnotateIfWrapping(String, Throwable)}. + * + * @deprecated since 0.12.0 behaviour will change (default should be to always wrap and annotate); + * for now users should review and switch to either + * {@link #propagateAnnotated(String, Throwable)} or {@link #propagateAnnotateIfWrapping(String, Throwable)} as appropriate. + */ + @Deprecated + public static RuntimeException propagate(String msg, Throwable throwable) { + return propagate(msg, throwable, false); + } + /** * See {@link #propagate(Throwable)}. *

    @@ -137,11 +148,14 @@ public static RuntimeException propagate(Throwable throwable) { * needs to be wrapped; otherwise the message is not used. * To always include the message, use {@link #propagateAnnotated(String, Throwable)}. */ - public static RuntimeException propagate(String msg, Throwable throwable) { + public static RuntimeException propagateAnnotateIfWrapping(String msg, Throwable throwable) { return propagate(msg, throwable, false); } - /** As {@link #propagate(String, Throwable)} but always re-wraps including the given message. */ + /** As {@link #propagate(String, Throwable)} but unlike earlier deprecated version + * this always re-wraps including the given message, until semantics of that method change to match this. + * See {@link #propagateAnnotateIfWrapping(String, Throwable)} if the message + * should be omitted and the given throwable preserved if it can already be propagated. */ public static RuntimeException propagateAnnotated(String msg, Throwable throwable) { return propagate(msg, throwable, true); } @@ -448,30 +462,30 @@ public String get() { public Throwable getOriginal() { return cause; } } - public static RuntimeException propagate(Collection exceptions) { + public static RuntimeException propagate(Iterable exceptions) { throw propagate(create(exceptions)); } - public static RuntimeException propagate(String prefix, Collection exceptions) { + public static RuntimeException propagate(String prefix, Iterable exceptions) { throw propagate(create(prefix, exceptions)); } /** creates the given exception, but without propagating it, for use when caller will be wrapping */ - public static Throwable create(Collection exceptions) { + public static Throwable create(Iterable exceptions) { return create(null, exceptions); } /** creates the given exception, but without propagating it, for use when caller will be wrapping */ - public static RuntimeException create(@Nullable String prefix, Collection exceptions) { - if (exceptions.size()==1) { + public static RuntimeException create(@Nullable String prefix, Iterable exceptions) { + if (Iterables.size(exceptions)==1) { Throwable e = exceptions.iterator().next(); if (Strings.isBlank(prefix)) return new PropagatedRuntimeException(e); return new PropagatedRuntimeException(prefix + ": " + Exceptions.collapseText(e), e); } - if (exceptions.isEmpty()) { + if (Iterables.isEmpty(exceptions)) { if (Strings.isBlank(prefix)) return new CompoundRuntimeException("(empty compound exception)", exceptions); return new CompoundRuntimeException(prefix, exceptions); } - if (Strings.isBlank(prefix)) return new CompoundRuntimeException(exceptions.size()+" errors, including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions); - return new CompoundRuntimeException(prefix+"; "+exceptions.size()+" errors including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions); + if (Strings.isBlank(prefix)) return new CompoundRuntimeException(Iterables.size(exceptions)+" errors, including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions); + return new CompoundRuntimeException(prefix+"; "+Iterables.size(exceptions)+" errors including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions); } /** Some throwables require a prefix for the message to make sense, 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 9ae70df6da..9fec03c03c 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 @@ -95,6 +95,13 @@ public Version getOsgiVersion() { return cachedOsgiVersion; } + @Nullable + public String getOsgiVersionString() { + Version ov = getOsgiVersion(); + if (ov==null) return null; + return ov.toString(); + } + @Nullable public String getVersionString() { return v; @@ -119,8 +126,13 @@ public boolean equals(Object other) { return Objects.equal(name, o.name) && Objects.equal(v, o.v); } - /** As {@link #equals(Object)} but accepting the argument as equal if versions are identical when injected to OSGi-valid versions */ + /** As {@link #equals(Object)} but accepting the argument as equal + * if versions are identical under the {@link #getOsgiVersion()} conversion; + * also accepts strings as the other, converting as per {@link #fromString(String)} */ public boolean equalsOsgi(Object other) { + if (other instanceof String) { + other = VersionedName.fromString((String)other); + } if (!(other instanceof VersionedName)) { return false; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/stream/Streams.java b/utils/common/src/main/java/org/apache/brooklyn/util/stream/Streams.java index 0f343573e5..a703dba0eb 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/stream/Streams.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/stream/Streams.java @@ -30,6 +30,9 @@ import java.io.StringReader; import java.io.Writer; import java.nio.charset.Charset; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import org.apache.brooklyn.util.exceptions.Exceptions; import org.slf4j.Logger; @@ -39,6 +42,7 @@ import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import com.google.common.base.Supplier; import com.google.common.io.ByteStreams; import com.google.common.io.CharStreams; @@ -215,4 +219,21 @@ public static boolean logStreamTail(Logger log, String message, ByteArrayOutputS return false; } + public static String getMd5Checksum(InputStream in) { + MessageDigest md; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw Exceptions.propagate(e); + } + DigestInputStream dis = new DigestInputStream(in, md); + readFullyAndClose(dis); + byte[] digest = md.digest(); + StringBuilder result = new StringBuilder(); + for (byte b: digest) { + result.append(Strings.padStart(Integer.toHexString((256+b)%256), 2, '0')); + } + return result.toString().toUpperCase(); + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java index 887ca6b756..1e0e698fb8 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java @@ -31,6 +31,8 @@ */ public class BrooklynVersionSyntax { + private static final String SNAPSHOT = "SNAPSHOT"; + public static final String USABLE_REGEX = "[^:\\s/\\\\]+"; public static final String DOT = "\\."; @@ -192,4 +194,9 @@ public static boolean equalAsOsgiVersions(String v1, String v2) { return toValidOsgiVersion(v1).equals(toValidOsgiVersion(v2)); } + public static boolean isSnapshot(String version) { + if (version==null) return false; + return version.toUpperCase().contains(SNAPSHOT); + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java index 120a210b69..449d0e5d86 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java @@ -44,8 +44,6 @@ */ public class VersionComparator implements Comparator { - private static final String SNAPSHOT = "SNAPSHOT"; - public static final VersionComparator INSTANCE = new VersionComparator(); public static VersionComparator getInstance() { @@ -53,10 +51,8 @@ public static VersionComparator getInstance() { } public static boolean isSnapshot(String version) { - if (version==null) return false; - return version.toUpperCase().contains(SNAPSHOT); + return BrooklynVersionSyntax.isSnapshot(version); } - @SuppressWarnings("unused") private static class TwoBooleans { diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/exceptions/ExceptionsTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/exceptions/ExceptionsTest.java index 272d3f6df2..345db932fd 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/exceptions/ExceptionsTest.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/exceptions/ExceptionsTest.java @@ -65,7 +65,7 @@ public void testPropagateCheckedExceptionWithMessage() throws Exception { String extraMsg = "my message"; Exception tothrow = new Exception("simulated"); try { - throw Exceptions.propagate(extraMsg, tothrow); + throw Exceptions.propagateAnnotateIfWrapping(extraMsg, tothrow); } catch (RuntimeException e) { assertEquals(e.getMessage(), "my message"); assertEquals(e.getCause(), tothrow); @@ -76,7 +76,7 @@ public void testPropagateCheckedExceptionWithMessage() throws Exception { public void testPropagateRuntimeExceptionIgnoresMessage() throws Exception { NullPointerException tothrow = new NullPointerException("simulated"); try { - throw Exceptions.propagate("my message", tothrow); + throw Exceptions.propagateAnnotateIfWrapping("my message", tothrow); } catch (NullPointerException e) { assertEquals(e, tothrow); } @@ -257,14 +257,14 @@ public void testPropagateWithoutAnnotationSuppressed() throws Exception { @Test public void testPropagateWithAnnotationNotExplicitIncludedWhenWrapped() throws Exception { Throwable t = new Throwable("test"); - try { Exceptions.propagate("important", t); } catch (Throwable t2) { t = t2; } + try { Exceptions.propagateAnnotateIfWrapping("important", t); } catch (Throwable t2) { t = t2; } Assert.assertEquals(Exceptions.collapseText(t), "important: test"); } @Test public void testPropagateWithAnnotationNotExplicitIgnoredWhenNotWrapped() throws Exception { Throwable t = new RuntimeException("test"); - try { Exceptions.propagate("ignore", t); } catch (Throwable t2) { t = t2; } + try { Exceptions.propagateAnnotateIfWrapping("ignore", t); } catch (Throwable t2) { t = t2; } Assert.assertEquals(Exceptions.collapseText(t), "test"); } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/stream/StreamsTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/stream/StreamsTest.java new file mode 100644 index 0000000000..37340f47bd --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/stream/StreamsTest.java @@ -0,0 +1,36 @@ +/* + * 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.util.stream; + +import java.io.ByteArrayInputStream; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class StreamsTest { + + @Test + public void testChecksum() { + Assert.assertEquals(Streams.getMd5Checksum(new ByteArrayInputStream("hello world".getBytes())), + // generated from 3rd party tool + "5EB63BBBE01EEED093CB22BB8F5ACDC3" + ); + + } +} diff --git a/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-com-example-entities.jar b/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-com-example-entities.jar index 51cf119bc4..44f5273a77 100644 Binary files a/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-com-example-entities.jar and b/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-com-example-entities.jar differ diff --git a/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-entities.jar b/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-entities.jar index 6d0cf2dbc6..f879df700f 100644 Binary files a/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-entities.jar and b/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-entities.jar differ diff --git a/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_0.1.0.jar b/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_0.1.0.jar index 84fc378d05..6763cc2ccd 100644 Binary files a/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_0.1.0.jar and b/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_0.1.0.jar differ diff --git a/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_0.2.0.jar b/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_0.2.0.jar index 24113f6a40..d457554ed5 100644 Binary files a/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_0.2.0.jar and b/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_0.2.0.jar differ diff --git a/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_evil-twin_0.2.0.jar b/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_evil-twin_0.2.0.jar index 21a114a4b2..1f58c67773 100644 Binary files a/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_evil-twin_0.2.0.jar and b/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_evil-twin_0.2.0.jar differ diff --git a/utils/test-support/src/main/java/org/apache/brooklyn/test/support/TestResourceUnavailableException.java b/utils/test-support/src/main/java/org/apache/brooklyn/test/support/TestResourceUnavailableException.java index cb4205fd3d..34c3de3b9b 100644 --- a/utils/test-support/src/main/java/org/apache/brooklyn/test/support/TestResourceUnavailableException.java +++ b/utils/test-support/src/main/java/org/apache/brooklyn/test/support/TestResourceUnavailableException.java @@ -53,7 +53,7 @@ public class TestResourceUnavailableException extends SkipException { * * Note that this will use the same classloader that was used to load this class. * - * @param resourceName the classpath resource name, e.g. + * @param resourceName the classpath resource name, as a path, e.g. * /brooklyn/osgi/brooklyn-test-osgi-entities.jar */ public static void throwIfResourceUnavailable(Class relativeToClass, String resourceName) { @@ -61,8 +61,10 @@ public static void throwIfResourceUnavailable(Class relativeToClass, String r checkNotNull(resourceName, "resourceName"); checkArgument(!resourceName.isEmpty(), "resourceName must not be empty"); InputStream resource = relativeToClass.getResourceAsStream(resourceName); - if (resource == null) + if (resource == null) { + // would be nice to support URLs but ResourceUtils isn't available here throw new TestResourceUnavailableException(resourceName); + } // just make sure we clean up the resource try {