This closes #1117
diff --git a/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java b/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java
index b53722d..f6ee41a 100644
--- a/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java
+++ b/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java
@@ -116,9 +116,10 @@
* Set the immediate catalog item ID of this object, and the search path of other catalog items used to define it.
*/
public synchronized SpecT catalogItemIdAndSearchPath(String catalogItemId, Collection<String> searchPath) {
- // TODO if ID is null should we really ignore search path?
if (catalogItemId != null) {
catalogItemId(catalogItemId);
+ }
+ if (searchPath!=null) {
synchronized (catalogItemIdSearchPath) {
catalogItemIdSearchPath.clear();
if (searchPath!=null) {
@@ -137,7 +138,18 @@
}
return self();
}
-
+
+ public synchronized SpecT addSearchPathAtStart(List<String> searchPath) {
+ if (searchPath!=null) {
+ synchronized (catalogItemIdSearchPath) {
+ Set<String> newPath = MutableSet.copyOf(searchPath).putAll(catalogItemIdSearchPath);
+ catalogItemIdSearchPath.clear();
+ catalogItemIdSearchPath.addAll(newPath);
+ }
+ }
+ return self();
+ }
+
/**
* @deprecated since 0.11.0, most callers would want {@link #stackCatalogItemId(String)} instead, though semantics are different
*/
@@ -173,12 +185,8 @@
public SpecT stackCatalogItemId(String val) {
if (null != val) {
if (null != catalogItemId && !catalogItemId.equals(val)) {
- synchronized (catalogItemIdSearchPath) {
- Set<String> newPath = MutableSet.of();
- newPath.add(catalogItemId);
- newPath.addAll(catalogItemIdSearchPath);
- catalogItemIdSearchPath.clear();
- catalogItemIdSearchPath.addAll(newPath);
+ if (!catalogItemIdSearchPath.contains(catalogItemId)) {
+ addSearchPathAtStart(Collections.singletonList(catalogItemId));
}
}
catalogItemId(val);
diff --git a/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObject.java b/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObject.java
index 1d34982..95afe9e 100644
--- a/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObject.java
+++ b/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObject.java
@@ -47,7 +47,7 @@
String getDisplayName();
/**
- * The catalog item ID this object was loaded from.
+ * The registered type (catalog item) ID this object was loaded from.
* <p>
* This can be used to understand the appropriate classloading context,
* such as for versioning purposes, as well as meta-information such as
@@ -63,8 +63,8 @@
String getCatalogItemId();
/**
- * An immutable list of ids of catalog items that this item depends on in some way,
- * with the item that directly defines it implicit, but other items it references explicit.
+ * An immutable list of ids of registered types or bundles that contain them (catalog items) that this item depends on in some way,
+ * with the item (itself and its bundle) that directly defines it usually implicit, but other items it references explicit.
* Wrapping items are first in the list (i.e. wrapping items precede wrapped items),
* so for example, if the catalog is:
* <pre>
@@ -77,6 +77,14 @@
* </pre>
* the spec for Z will have getCatalogId() of Z and getCatalogItemIdSearchPath() of Y, X.
* (The self catalog ID is implicit at the head of the search path.)
+ * <p>
+ * The {@link #getCatalogItemId()} is often not included in this list, and that bundle/type should be used as the first target when searching.
+ * However if {@link #getCatalogItemId()} is included in this list, it should be used in the order where it occurs in this list, and not first.
+ * (This is because sometimes the entity is being referenced from a context where that context's search path should take priority.)
+ * <p>
+ * It is sufficient, functionally equivalent, and more efficient for this to contain just bundle IDs, ie the containing bundles of registered types,
+ * rather than many registered types, because the search path can be de-duped if bundles are used.
+ * However for legacy and complexity reasons in many cases registered type IDs are included here.
*/
List<String> getCatalogItemIdSearchPath();
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 5d79d81..ee3f2fd 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
@@ -20,15 +20,11 @@
import static com.google.common.base.Preconditions.checkNotNull;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
+import java.util.stream.Collectors;
import javax.annotation.Nullable;
import com.google.common.reflect.TypeToken;
@@ -213,6 +209,8 @@
return overrides;
}
+
+
@SuppressWarnings("unchecked")
private <T extends Entity> void populateSpec(EntitySpec<T> spec, Set<String> encounteredRegisteredTypeIds) {
String name, source=null, templateId=null, planId=null;
@@ -227,6 +225,14 @@
if (planId==null)
planId = (String) attrs.getStringKey(BrooklynCampConstants.PLAN_ID_FLAG);
+ Stack<RegisteredType> itemBeingResolved = CampResolver.currentlyCreatingSpec.get();
+ if (itemBeingResolved!=null && itemBeingResolved.peek()!=null) {
+ MutableList<String> searchPath = MutableList.<String>of()
+ .appendIfNotNull(itemBeingResolved.peek().getContainingBundle())
+ .appendAll(itemBeingResolved.peek().getLibraries().stream().map(bundle -> bundle.getVersionedName().toString()).collect(Collectors.toList()));
+ spec.addSearchPathAtStart(searchPath);
+ }
+
Object childrenObj = attrs.getStringKey(BrooklynCampReservedKeys.BROOKLYN_CHILDREN);
if (childrenObj != null) {
Iterable<Map<String,?>> children = (Iterable<Map<String,?>>)childrenObj;
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 f0116cf..1a71b92 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
@@ -18,8 +18,10 @@
*/
package org.apache.brooklyn.camp.brooklyn.spi.creation;
+import com.google.common.annotations.Beta;
import java.util.Set;
+import java.util.Stack;
import org.apache.brooklyn.api.entity.Application;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntitySpec;
@@ -81,6 +83,9 @@
return createSpecFromFull(mgmt, type, context.getExpectedJavaSuperType(), context.getAlreadyEncounteredTypes(), context.getLoader());
}
+ @Beta
+ public static final ThreadLocal<Stack<RegisteredType>> currentlyCreatingSpec = new ThreadLocal<Stack<RegisteredType>>();
+
static AbstractBrooklynObjectSpec<?, ?> createSpecFromFull(ManagementContext mgmt, RegisteredType item, Class<?> expectedType, Set<String> parentEncounteredTypes, BrooklynClassLoadingContext loaderO) {
// for this method, a prefix "services" or "brooklyn.{location,policies}" is required at the root;
// we now prefer items to come in "{ type: .. }" format, except for application roots which
@@ -98,50 +103,68 @@
encounteredTypes = parentEncounteredTypes;
}
- AbstractBrooklynObjectSpec<?, ?> spec;
- String planYaml = RegisteredTypes.getImplementationDataStringForSpec(item);
- MutableSet<Object> supers = MutableSet.copyOf(item.getSuperTypes());
- supers.addIfNotNull(expectedType);
- if (RegisteredTypes.isAnyTypeSubtypeOf(supers, Policy.class)) {
- spec = CampInternalUtils.createPolicySpec(planYaml, loader, encounteredTypes);
- } else if (RegisteredTypes.isAnyTypeSubtypeOf(supers, Enricher.class)) {
- spec = CampInternalUtils.createEnricherSpec(planYaml, loader, encounteredTypes);
- } else if (RegisteredTypes.isAnyTypeSubtypeOf(supers, Location.class)) {
- spec = CampInternalUtils.createLocationSpec(planYaml, loader, encounteredTypes);
- } else if (RegisteredTypes.isAnyTypeSubtypeOf(supers, Application.class)) {
- spec = createEntitySpecFromServicesBlock(planYaml, loader, encounteredTypes, true);
- } else if (RegisteredTypes.isAnyTypeSubtypeOf(supers, Entity.class)) {
- spec = createEntitySpecFromServicesBlock(planYaml, loader, encounteredTypes, false);
- } else {
- throw new IllegalStateException("Cannot detect spec type from "+item.getSuperTypes()+" for "+item+"\n"+planYaml);
+ // store the currently creating spec so that we can set the search path on items created by this
+ // (messy using a thread local; ideally we'll add it to the API, and/or use the LoadingContext for both this and for encountered types)
+ if (currentlyCreatingSpec.get()==null) {
+ currentlyCreatingSpec.set(new Stack<>());
}
- if (expectedType!=null && !expectedType.isAssignableFrom(spec.getType())) {
- throw new IllegalStateException("Creating spec from "+item+", got "+spec.getType()+" which is incompatible with expected "+expectedType);
- }
+ currentlyCreatingSpec.get().push(item);
+ try {
- spec.stackCatalogItemId(item.getId());
-
- if (spec instanceof EntitySpec) {
- String name = spec.getDisplayName();
- Object defaultNameConf = spec.getConfig().get(AbstractEntity.DEFAULT_DISPLAY_NAME);
- Object defaultNameFlag = spec.getFlags().get(AbstractEntity.DEFAULT_DISPLAY_NAME.getName());
- if (Strings.isBlank(name) && defaultNameConf == null && defaultNameFlag == null) {
- spec.configure(AbstractEntity.DEFAULT_DISPLAY_NAME, item.getDisplayName());
+ AbstractBrooklynObjectSpec<?, ?> spec;
+ String planYaml = RegisteredTypes.getImplementationDataStringForSpec(item);
+ MutableSet<Object> supers = MutableSet.copyOf(item.getSuperTypes());
+ supers.addIfNotNull(expectedType);
+ if (RegisteredTypes.isAnyTypeSubtypeOf(supers, Policy.class)) {
+ spec = CampInternalUtils.createPolicySpec(planYaml, loader, encounteredTypes);
+ } else if (RegisteredTypes.isAnyTypeSubtypeOf(supers, Enricher.class)) {
+ spec = CampInternalUtils.createEnricherSpec(planYaml, loader, encounteredTypes);
+ } else if (RegisteredTypes.isAnyTypeSubtypeOf(supers, Location.class)) {
+ spec = CampInternalUtils.createLocationSpec(planYaml, loader, encounteredTypes);
+ } else if (RegisteredTypes.isAnyTypeSubtypeOf(supers, Application.class)) {
+ spec = createEntitySpecFromServicesBlock(planYaml, loader, encounteredTypes, true);
+ } else if (RegisteredTypes.isAnyTypeSubtypeOf(supers, Entity.class)) {
+ spec = createEntitySpecFromServicesBlock(planYaml, loader, encounteredTypes, false);
+ } else {
+ throw new IllegalStateException("Cannot detect spec type from " + item.getSuperTypes() + " for " + item + "\n" + planYaml);
}
- } else {
- // See https://issues.apache.org/jira/browse/BROOKLYN-248, and the tests in
- // ApplicationYamlTest and CatalogYamlLocationTest.
- if (Strings.isNonBlank(item.getDisplayName())) {
- spec.displayName(item.getDisplayName());
+ if (expectedType != null && !expectedType.isAssignableFrom(spec.getType())) {
+ throw new IllegalStateException("Creating spec from " + item + ", got " + spec.getType() + " which is incompatible with expected " + expectedType);
+ }
+
+ spec.stackCatalogItemId(item.getId());
+
+ if (spec instanceof EntitySpec) {
+ String name = spec.getDisplayName();
+ Object defaultNameConf = spec.getConfig().get(AbstractEntity.DEFAULT_DISPLAY_NAME);
+ Object defaultNameFlag = spec.getFlags().get(AbstractEntity.DEFAULT_DISPLAY_NAME.getName());
+ if (Strings.isBlank(name) && defaultNameConf == null && defaultNameFlag == null) {
+ spec.configure(AbstractEntity.DEFAULT_DISPLAY_NAME, item.getDisplayName());
+ }
+ } else {
+ // See https://issues.apache.org/jira/browse/BROOKLYN-248, and the tests in
+ // ApplicationYamlTest and CatalogYamlLocationTest.
+ if (Strings.isNonBlank(item.getDisplayName())) {
+ spec.displayName(item.getDisplayName());
+ }
+ }
+
+ return spec;
+
+ } finally {
+ currentlyCreatingSpec.get().pop();
+ if (currentlyCreatingSpec.get().isEmpty()) {
+ currentlyCreatingSpec.remove();
}
}
-
- return spec;
}
private static EntitySpec<?> createEntitySpecFromServicesBlock(String plan, BrooklynClassLoadingContext loader, Set<String> encounteredTypes, boolean isApplication) {
CampPlatform camp = CampInternalUtils.getCampPlatform(loader.getManagementContext());
+ // TODO instead of BasicBrooklynCatalog.attemptLegacySpecTransformers where candidate yaml has 'services:' prepended, try that in this method
+ // if 'services:' is not declared, but a 'type:' is, then see whether we can parse it with services.
+
AssemblyTemplate at = CampInternalUtils.resolveDeploymentPlan(plan, loader, camp);
AssemblyTemplateInstantiator instantiator = CampInternalUtils.getInstantiator(at);
if (instantiator instanceof AssemblyTemplateSpecInstantiator) {
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 b59d213..7065a78 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
@@ -18,6 +18,7 @@
*/
package org.apache.brooklyn.camp.brooklyn.catalog;
+import org.apache.brooklyn.util.exceptions.CompoundRuntimeException;
import static org.testng.Assert.assertEquals;
import java.util.Map;
@@ -277,6 +278,10 @@
Asserts.shouldHaveFailedPreviously("Expected deployment to fail rebind; instead got "+app2);
} catch (Exception e) {
// should fail to rebind this entity
+ if (e instanceof CompoundRuntimeException) {
+ // bit brittle but good enough for now to find the exception with our message
+ e = (Exception) ((CompoundRuntimeException)e).getAllCauses().get(2);
+ }
Asserts.expectedFailureContainsIgnoreCase(e, more.getId(), "class", BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, "not found");
}
}
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 5abf347..d95837c 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
@@ -18,6 +18,14 @@
*/
package org.apache.brooklyn.camp.brooklyn.catalog;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynComponentTemplateResolver;
+import org.apache.brooklyn.core.entity.Dumper;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
+import org.apache.brooklyn.util.collections.MutableList;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
@@ -102,11 +110,99 @@
Entity simpleEntity = Iterables.getOnlyElement(app.getChildren());
assertEquals(simpleEntity.getEntityType().getName(), SIMPLE_ENTITY_TYPE);
+ Assert.assertEquals(simpleEntity.getCatalogItemId(), ver(referrerSymbolicName));
+ Dumper.dumpInfo(simpleEntity);
+ assertCatalogItemIdAndSearchPath(simpleEntity, ver(referrerSymbolicName), Arrays.asList(
+ ver(referencedSymbolicName), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_VERSIONED_NAME));
deleteCatalogRegisteredType(referencedSymbolicName);
deleteCatalogRegisteredType(referrerSymbolicName);
}
+ private void assertCatalogItemIdAndSearchPath(Entity ent, String cid, List<String> csp) {
+ Asserts.assertEquals(ent.getCatalogItemId(), cid);
+
+ // treat catalog item id at the head of the search path as equivalent to it not being present
+ List<String> sp = ent.getCatalogItemIdSearchPath();
+ if (sp.contains(cid)) {
+ if (!csp.contains(cid)) {
+ Asserts.assertEquals(sp, MutableList.of(cid).appendAll(csp));
+ return;
+ }
+ } else if (csp.contains(cid)) {
+ Asserts.assertEquals(MutableList.of(cid).appendAll(sp), csp);
+ return;
+ }
+ Asserts.assertEquals(sp, csp);
+ return;
+ }
+
+ @Test
+ public void testLaunchApplicationWithCatalogReferencingOtherCatalogInServicesBlock() throws Exception {
+ TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+
+ String referencedSymbolicName = "my.catalog.app.id.referenced";
+ String referrer1SymbolicName = "my.catalog.app.id.referring1";
+ String referrer2SymbolicName = "my.catalog.app.id.referring2";
+ addCatalogOSGiEntity(referencedSymbolicName, SIMPLE_ENTITY_TYPE);
+ addCatalogEntity(referrer1SymbolicName, ver(referencedSymbolicName));
+ addCatalogEntityInServicesBlock(referrer2SymbolicName, ver(referrer1SymbolicName));
+
+ Entity app = createAndStartApplication(
+ "services:",
+ "- type: " + ver(referrer2SymbolicName));
+
+ Dumper.dumpInfo(app);
+ Entity parent = app;
+ Assert.assertNull(parent.getCatalogItemId()); // is just an implicit BasicApplication
+ Asserts.assertEquals(parent.getCatalogItemIdSearchPath(), Collections.emptyList());
+
+ Entity child = app.getChildren().iterator().next();
+ assertEquals(child.getEntityType().getName(), SIMPLE_ENTITY_TYPE);
+ assertCatalogItemIdAndSearchPath(child, ver(referrer2SymbolicName), Arrays.asList(
+ ver(referrer1SymbolicName),
+ ver(referencedSymbolicName),
+ OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_VERSIONED_NAME));
+
+ deleteCatalogRegisteredType(referrer2SymbolicName);
+ deleteCatalogRegisteredType(referrer1SymbolicName);
+ deleteCatalogRegisteredType(referencedSymbolicName);
+ }
+
+
+ @Test
+ public void testLaunchApplicationWithCatalogReferencingOtherCatalogInServicesBlockTwice() throws Exception {
+ TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+
+ String referencedSymbolicName = "my.catalog.app.id.referenced";
+ String referrer1SymbolicName = "my.catalog.app.id.referring1";
+ String referrer2SymbolicName = "my.catalog.app.id.referring2";
+ addCatalogOSGiEntity(referencedSymbolicName, SIMPLE_ENTITY_TYPE);
+ addCatalogEntity(referrer1SymbolicName, ver(referencedSymbolicName));
+ addCatalogEntityInServicesBlockTwice(referrer2SymbolicName, ver(referrer1SymbolicName));
+
+ Entity app = createAndStartApplication(
+ "services:",
+ "- type: " + ver(referrer2SymbolicName));
+
+ Dumper.dumpInfo(app); // referrer2 is an application here so is promoted, and should see referrer 2, but not referrer 1 or referenced as those are the children nodes only
+ Entity parent = app;
+ Assert.assertEquals(parent.getCatalogItemId(), ver(referrer2SymbolicName));
+ Asserts.assertEquals(parent.getCatalogItemIdSearchPath(), Arrays.asList(ver(referrer2SymbolicName)));
+
+ Entity child = app.getChildren().iterator().next();
+ assertEquals(child.getEntityType().getName(), SIMPLE_ENTITY_TYPE);
+ assertCatalogItemIdAndSearchPath(child, ver(referrer1SymbolicName), Arrays.asList(
+ ver(referrer2SymbolicName),
+ ver(referrer1SymbolicName),
+ ver(referencedSymbolicName),
+ OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_VERSIONED_NAME));
+
+ deleteCatalogRegisteredType(referrer2SymbolicName);
+ deleteCatalogRegisteredType(referrer1SymbolicName);
+ deleteCatalogRegisteredType(referencedSymbolicName);
+ }
+
@Test
public void testLaunchApplicationChildWithCatalogReferencingOtherCatalog() throws Exception {
TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
@@ -758,6 +854,7 @@
String symbolicNameOuter = "my.catalog.app.id.outer";
addCatalogItems(
"brooklyn.catalog:",
+ " bundle: " + symbolicNameOuter,
" version: " + TEST_VERSION,
" items:",
" - id: " + symbolicNameInner,
@@ -776,9 +873,11 @@
Entity app = createAndStartApplication(yaml);
Entity entity = app.getChildren().iterator().next();
- assertEquals(entity.getCatalogItemId(), ver(symbolicNameOuter));
- assertEquals(entity.getCatalogItemIdSearchPath(), ImmutableList.of(ver(symbolicNameInner)),
- "should have just " + symbolicNameInner + " in search path");
+ Dumper.dumpInfo(entity);
+ assertCatalogItemIdAndSearchPath(entity, ver(symbolicNameOuter), ImmutableList.of(
+ ver(symbolicNameInner),
+ ver(symbolicNameOuter),
+ OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_VERSIONED_NAME));
deleteCatalogRegisteredType(symbolicNameInner);
deleteCatalogRegisteredType(symbolicNameOuter);
@@ -846,6 +945,29 @@
" type: " + serviceType);
}
+ private void addCatalogEntityInServicesBlock(String symbolicName, String serviceType) {
+ addCatalogItems(
+ "brooklyn.catalog:",
+ " id: " + symbolicName,
+ " version: " + TEST_VERSION,
+ " itemType: entity",
+ " item:",
+ " services:",
+ " - type: " + serviceType);
+ }
+
+ private void addCatalogEntityInServicesBlockTwice(String symbolicName, String serviceType) {
+ addCatalogItems(
+ "brooklyn.catalog:",
+ " id: " + symbolicName,
+ " version: " + TEST_VERSION,
+ " itemType: entity",
+ " item:",
+ " services:",
+ " - type: " + serviceType,
+ " - type: " + serviceType);
+ }
+
private void addCatalogChildEntity(String symbolicName, String serviceType) {
addCatalogItems(
"brooklyn.catalog:",
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
index 2414bea..679c18a 100644
--- 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
@@ -18,6 +18,7 @@
*/
package org.apache.brooklyn.camp.brooklyn.catalog;
+import java.util.Map;
import org.apache.brooklyn.api.typereg.RegisteredType;
import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
import org.apache.brooklyn.core.test.entity.TestEntity;
@@ -26,6 +27,7 @@
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.collections.CollectionFunctionals;
import org.apache.brooklyn.util.osgi.VersionedName;
+import org.apache.brooklyn.util.yaml.Yamls;
import org.testng.annotations.Test;
import com.google.common.base.Predicates;
@@ -49,6 +51,8 @@
// use type registry approach
@Override
protected void addCatalogItems(String catalogYaml) {
+ boolean skipStart = false;
+
switch (itemsInstallMode!=null ? itemsInstallMode :
// this is the default because some "bundles" aren't resolvable or library BOMs loadable in test context
CatalogItemsInstallationMode.BUNDLE_BUT_NOT_STARTED) {
@@ -56,11 +60,20 @@
super.addCatalogItems(catalogYaml);
break;
case BUNDLE_BUT_NOT_STARTED:
- // TODO if id/bundle set in catalog yaml use that
- addCatalogItemsAsOsgiWithoutStartingBundles(mgmt(), catalogYaml, new VersionedName(bundleName(), bundleVersion()), isForceUpdate());
- break;
+ skipStart = true;
+ // continue to below
case USUAL_OSGI_WAY_AS_BUNDLE_WITH_DEFAULT_NAME:
- addCatalogItemsAsOsgiInUsualWay(mgmt(), catalogYaml, new VersionedName(bundleName(), bundleVersion()), isForceUpdate());
+ String bundle = bundleName();
+ String version = bundleVersion();
+ Map<?, ?> cy = (Map<?, ?>) Yamls.parseAll(catalogYaml).iterator().next();
+ cy = (Map<?, ?>) cy.get("brooklyn.catalog");
+ if (cy.containsKey("bundle")) bundle = (String)cy.get("bundle");
+ if (cy.containsKey("version")) version = (String)cy.get("version");
+ if (skipStart) {
+ addCatalogItemsAsOsgiWithoutStartingBundles(mgmt(), catalogYaml, new VersionedName(bundle, version), isForceUpdate());
+ } else {
+ addCatalogItemsAsOsgiInUsualWay(mgmt(), catalogYaml, new VersionedName(bundle, version), isForceUpdate());
+ }
break;
case USUAL_OSGI_WAY_AS_ZIP_NO_MANIFEST_NAME_MAYBE_IN_BOM:
addCatalogItemsAsOsgiInUsualWay(mgmt(), catalogYaml, null, isForceUpdate());
@@ -87,7 +100,7 @@
String symbolicName = "my.catalog.app.id.load";
addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), BasicEntity.class.getName());
- Iterable<RegisteredType> itemsInstalled = mgmt().getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(new VersionedName(bundleName(), bundleVersion())));
+ Iterable<RegisteredType> itemsInstalled = mgmt().getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(new VersionedName(symbolicName, TEST_VERSION)));
Asserts.assertSize(itemsInstalled, 1);
RegisteredType item = mgmt().getTypeRegistry().get(symbolicName, TEST_VERSION);
Asserts.assertEquals(item, Iterables.getOnlyElement(itemsInstalled), "Wrong item; installed: "+itemsInstalled);
@@ -174,5 +187,9 @@
}
// also runs many other tests from super, here using the osgi/type-registry appraoch
-
+
+ @Test
+ public void testCatalogItemIdInReferencedItems() throws Exception {
+ super.testCatalogItemIdInReferencedItems();
+ }
}
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 6f52053..7340640 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
@@ -21,6 +21,9 @@
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Throwables;
import java.util.Collection;
+import java.util.Set;
+import org.apache.brooklyn.core.entity.Dumper;
+import org.apache.brooklyn.util.collections.MutableSet;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
@@ -420,7 +423,7 @@
addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), BasicEntity.class.getName());
Asserts.shouldHaveFailedPreviously();
} catch (Exception e) {
- Asserts.expectedFailureContains(e, "different", symbolicName, TEST_VERSION, "already present");
+ Asserts.expectedFailureContains(e, "different", symbolicName, TEST_VERSION);
}
}
@@ -512,6 +515,7 @@
// Being a child is important, triggers the case where: we allow retrying with other transformers.
addCatalogItems(
"brooklyn.catalog:",
+ " bundle: " + callerSymbolicName,
" id: " + callerSymbolicName,
" version: " + TEST_VERSION,
" itemType: entity",
@@ -719,8 +723,10 @@
public void testCatalogItemIdInReferencedItems() throws Exception {
String symbolicNameInner = "my.catalog.app.id.inner";
String symbolicNameOuter = "my.catalog.app.id.outer";
+ String bundleName = "my.catalog.app.bundle";
addCatalogItems(
"brooklyn.catalog:",
+ " bundle: "+bundleName,
" version: " + TEST_VERSION,
" items:",
" - id: " + symbolicNameInner,
@@ -736,8 +742,13 @@
Entity entity = app.getChildren().iterator().next();
assertEquals(entity.getCatalogItemId(), ver(symbolicNameOuter));
- assertEquals(entity.getCatalogItemIdSearchPath(), ImmutableList.of(ver(symbolicNameInner)),
- "should have just " + symbolicNameInner + " in search path");
+ Dumper.dumpInfo(entity);
+ Set<String> searchPath = MutableSet.copyOf(entity.getCatalogItemIdSearchPath());
+ boolean hadThisBundleOrInner = searchPath.remove(bundleName+":"+TEST_VERSION);
+ hadThisBundleOrInner = searchPath.remove(ver(symbolicNameInner)) || hadThisBundleOrInner;
+ Assert.assertTrue(hadThisBundleOrInner, "search path should have referred to inner or bundle");
+ searchPath.remove(ver(symbolicNameOuter)); // this might get added (in osgi subclass)
+ Assert.assertTrue(searchPath.isEmpty(), "search path contained unexpected items: "+searchPath);
deleteCatalogRegisteredType(symbolicNameInner);
deleteCatalogRegisteredType(symbolicNameOuter);
@@ -844,6 +855,7 @@
addCatalogItems(
"brooklyn.catalog:",
" id: " + idAndVersion.id,
+ " bundle: " + idAndVersion.id,
" version: " + idAndVersion.version,
" itemType: entity",
" item:",
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 2c0f55f..f3bafb2 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
@@ -1304,8 +1304,10 @@
return this;
}
- // couldn't resolve it with the plan transformers; retry with legacy "spec" transformers
- // (not sure if/when we come here...)
+ // couldn't resolve it with the plan transformers; retry with legacy "spec" transformers.
+ // TODO this legacy path is still needed where an entity is declared with nice abbreviated 'type: xxx' syntax, not the full-camp 'services: [ { type: xxx } ]' syntax.
+ // would be nice to move that logic internally to CAMP and see if we can remove this altogether.
+ // (see org.apache.brooklyn.camp.brooklyn.spi.creation.CampResolver.createEntitySpecFromServicesBlock )
if (format==null) {
attemptLegacySpecTransformersForVariousSpecTypes();
}
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 a25e19d..649180a 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
@@ -134,11 +134,16 @@
return result;
}
+ /** Creates a new loading context using the primary bundle and the search bundles.
+ * If the primary bundle is included in the search bundles, then it is read in the order it lies within the search bundle.
+ * If it is not in the search list it is read first. */
public static BrooklynClassLoadingContext newClassLoadingContextForCatalogItems(
ManagementContext managementContext, String primaryItemId, List<String> searchPath) {
BrooklynClassLoadingContextSequential seqLoader = new BrooklynClassLoadingContextSequential(managementContext);
- addSearchItem(managementContext, seqLoader, primaryItemId, false /* primary ID may be temporary */);
+ if (!searchPath.contains(primaryItemId)) {
+ addSearchItem(managementContext, seqLoader, primaryItemId, false /* primary ID may be temporary */);
+ }
for (String searchId : searchPath) {
addSearchItem(managementContext, seqLoader, searchId, true);
}
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/Dumper.java b/core/src/main/java/org/apache/brooklyn/core/entity/Dumper.java
index 7323e11..c034162 100644
--- a/core/src/main/java/org/apache/brooklyn/core/entity/Dumper.java
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/Dumper.java
@@ -94,9 +94,9 @@
out.append(currentIndentation + tab + tab + "searchPath = [");
for (int i = 0 ; i < searchPath.size() ; i++) {
out.append(i > 0 ? ",\n" : "\n");
- out.append(currentIndentation + tab + tab + searchPath.get(i));
+ out.append(currentIndentation + tab + tab + tab + searchPath.get(i));
}
- out.append("\n" + currentIndentation + tab + tab + "]");
+ out.append("\n" + currentIndentation + tab + tab + "]\n");
}
out.append(currentIndentation+tab+tab+"locations = "+e.getLocations()+"\n");
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
index f66bf83..8d5a07e 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
@@ -20,6 +20,7 @@
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Callable;
import javax.annotation.Nullable;
@@ -43,6 +44,7 @@
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.task.TaskBuilder;
import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.Duration;
import org.slf4j.Logger;
@@ -247,7 +249,7 @@
EntitySpec<? extends Application> wrappedApplication = (EntitySpec<? extends Application>) unwrapEntity(wrapperApplication);
return wrappedApplication;
}
-
+
/** Modifies the child so it includes the inessential setup of its parent,
* for use when unwrapping specific children, but a name or other item may have been set on the parent.
* See {@link #WRAPPER_APP_MARKER}. */
@@ -263,8 +265,6 @@
wrappedChild.parametersAdd(wrapperParent.getParameters());
}
- wrappedChild.catalogItemIdAndSearchPath(wrapperParent.getCatalogItemId(), wrapperParent.getCatalogItemIdSearchPath());
-
// NB: this clobber's child config wherever they conflict; might prefer to deeply merge maps etc
// (or maybe even prevent the merge in these cases;
// not sure there is a compelling reason to have config on a pure-wrapper parent)
@@ -273,7 +273,25 @@
Predicates.not(Predicates.<ConfigKey<?>>equalTo(EntityManagementUtils.WRAPPER_APP_MARKER)));
wrappedChild.configure(configWithoutWrapperMarker);
wrappedChild.configure(wrapperParent.getFlags());
-
+
+ // add the search path to children when unwrapped, in preference to anything on the children
+ String preferredCatalogItemId, otherCatalogItemId;
+ if (wrapperParent.getCatalogItemId()!=null) {
+ preferredCatalogItemId = wrapperParent.getCatalogItemId();
+ otherCatalogItemId = wrappedChild.getCatalogItemId();
+ if (Objects.equals(otherCatalogItemId, preferredCatalogItemId)) {
+ otherCatalogItemId = null;
+ }
+ } else {
+ preferredCatalogItemId = wrappedChild.getCatalogItemId();
+ otherCatalogItemId = null;
+ }
+ MutableList<String> searchPath = MutableList.<String>of()
+ .appendAll(wrapperParent.getCatalogItemIdSearchPath())
+ .appendIfNotNull(otherCatalogItemId)
+ .appendAll(wrappedChild.getCatalogItemIdSearchPath());
+ wrappedChild.catalogItemIdAndSearchPath(preferredCatalogItemId, searchPath);
+
// copying tags to all entities may be something the caller wants to control,
// e.g. if we're adding multiple, the caller might not want to copy the parent
// (the BrooklynTags.YAML_SPEC tag will include the parents source including siblings),
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 d1f9537..e51f182 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
@@ -85,6 +85,7 @@
import org.apache.brooklyn.core.location.internal.LocationInternal;
import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContextSequential;
import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext;
+import org.apache.brooklyn.core.mgmt.ha.OsgiManager;
import org.apache.brooklyn.core.mgmt.internal.BrooklynObjectManagementMode;
import org.apache.brooklyn.core.mgmt.internal.BrooklynObjectManagerInternal;
import org.apache.brooklyn.core.mgmt.internal.EntityManagerInternal;
@@ -1009,6 +1010,17 @@
if (searchPath != null && !searchPath.isEmpty()) {
for (String searchItemId : searchPath) {
String fixedSearchItemId = null;
+ OsgiManager osgi = managementContext.getOsgiManager().orNull();
+ if (osgi!=null) {
+ ManagedBundle bundle = osgi.getManagedBundle(VersionedName.fromString(searchItemId));
+ if (bundle!=null) {
+ // found as bundle
+ reboundSearchPath.add(searchItemId);
+ continue;
+ }
+ }
+
+ // look for as a type now
RegisteredType t1 = managementContext.getTypeRegistry().get(searchItemId);
if (t1==null) {
String newSearchItemId = CatalogUpgrades.getTypeUpgradedIfNecessary(managementContext, searchItemId);
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/osgi/OsgiStandaloneTest.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/osgi/OsgiStandaloneTest.java
index 03d1307..6cde61c 100644
--- a/core/src/test/java/org/apache/brooklyn/core/mgmt/osgi/OsgiStandaloneTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/osgi/OsgiStandaloneTest.java
@@ -52,6 +52,7 @@
public static final String BROOKLYN_TEST_OSGI_MORE_ENTITIES_0_1_0_URL = "classpath:"+BROOKLYN_TEST_OSGI_MORE_ENTITIES_0_1_0_PATH;
public static final String BROOKLYN_TEST_OSGI_ENTITIES_NAME = "org.apache.brooklyn.test.resources.osgi.brooklyn-test-osgi-entities";
public static final String BROOKLYN_TEST_OSGI_ENTITIES_VERSION = "0.1.0";
+ public static final String BROOKLYN_TEST_OSGI_ENTITIES_VERSIONED_NAME = BROOKLYN_TEST_OSGI_ENTITIES_NAME+":"+BROOKLYN_TEST_OSGI_ENTITIES_VERSION;
@Test
public void testInstallBundle() throws Exception {