in brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java [396:602]
private void collectCatalogItems(String sourceYaml, Map<?,?> itemMetadata, List<CatalogItemDtoAbstract<?, ?>> result, Map<?,?> parentMetadata) {
if (sourceYaml==null) sourceYaml = new Yaml().dump(itemMetadata);
Map<?, ?> itemMetadataWithoutItemDef = MutableMap.builder()
.putAll(itemMetadata)
.remove("item")
.remove("items")
.build();
// Parse CAMP-YAML DSL in item metadata (but not in item or items - those will be parsed only when used).
CampYamlParser parser = mgmt.getConfig().getConfig(CampYamlParser.YAML_PARSER_KEY);
if (parser != null) {
itemMetadataWithoutItemDef = parser.parse((Map<String, Object>) itemMetadataWithoutItemDef);
try {
itemMetadataWithoutItemDef = (Map<String, Object>) Tasks.resolveDeepValue(itemMetadataWithoutItemDef, Object.class, mgmt.getServerExecutionContext());
} catch (Exception e) {
throw Exceptions.propagate(e);
}
} else {
log.info("No Camp-YAML parser regsitered for parsing catalog item DSL; skipping DSL-parsing");
}
Map<Object,Object> catalogMetadata = MutableMap.<Object, Object>builder()
.putAll(parentMetadata)
.putAll(itemMetadataWithoutItemDef)
.putIfNotNull("item", itemMetadata.get("item"))
.putIfNotNull("items", itemMetadata.get("items"))
.build();
// 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<CatalogBundle> 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<CatalogBundle> libraryBundles = CatalogItemDtoAbstract.parseLibraries(librariesCombined);
// 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);
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));
} else {
throw new IllegalStateException("Cannot scan catalog node no local bundles, and with inherited bundles we will not scan the classpath");
}
}
Object items = catalogMetadata.remove("items");
Object item = catalogMetadata.remove("item");
if (items!=null) {
int count = 0;
for (Map<?,?> i: ((List<Map<?,?>>)items)) {
collectCatalogItems(Yamls.getTextOfYamlAtPath(sourceYaml, "items", count).getMatchedYamlTextOrWarn(),
i, result, catalogMetadata);
count++;
}
}
if (item==null) return;
// now look at the actual item, first correcting the sourceYaml and interpreting the catalog metadata
String itemYaml = Yamls.getTextOfYamlAtPath(sourceYaml, "item").getMatchedYamlTextOrWarn();
if (itemYaml!=null) sourceYaml = itemYaml;
else sourceYaml = new Yaml().dump(item);
CatalogItemType itemType = TypeCoercions.coerce(getFirstAs(catalogMetadata, Object.class, "itemType", "item_type").orNull(), CatalogItemType.class);
String id = getFirstAs(catalogMetadata, String.class, "id").orNull();
String version = getFirstAs(catalogMetadata, String.class, "version").orNull();
String symbolicName = getFirstAs(catalogMetadata, String.class, "symbolicName").orNull();
String displayName = getFirstAs(catalogMetadata, String.class, "displayName").orNull();
String name = getFirstAs(catalogMetadata, String.class, "name").orNull();
if ((Strings.isNonBlank(id) || Strings.isNonBlank(symbolicName)) &&
Strings.isNonBlank(displayName) &&
Strings.isNonBlank(name) && !name.equals(displayName)) {
log.warn("Name property will be ignored due to the existence of displayName and at least one of id, symbolicName");
}
PlanInterpreterGuessingType planInterpreter = new PlanInterpreterGuessingType(null, item, sourceYaml, itemType, libraryBundles, result).reconstruct();
if (!planInterpreter.isResolved()) {
throw Exceptions.create("Could not resolve 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
// (ideally in future we'll be able to return typed errors with caret position of error)
// + ":\n"+sourceYaml
, planInterpreter.getErrors());
}
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
// 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
if (Strings.isBlank(symbolicName)) {
if (Strings.isNonBlank(id)) {
if (CatalogUtils.looksLikeVersionedId(id)) {
symbolicName = CatalogUtils.getSymbolicNameFromVersionedId(id);
} else {
symbolicName = id;
}
} else if (Strings.isNonBlank(name)) {
if (CatalogUtils.looksLikeVersionedId(name)) {
symbolicName = CatalogUtils.getSymbolicNameFromVersionedId(name);
} else {
symbolicName = name;
}
} else {
symbolicName = setFromItemIfUnset(symbolicName, itemAsMap, "id");
symbolicName = setFromItemIfUnset(symbolicName, itemAsMap, "name");
// TODO we should let the plan transformer give us this
symbolicName = setFromItemIfUnset(symbolicName, itemAsMap, "template_name");
if (Strings.isBlank(symbolicName)) {
log.error("Can't infer catalog item symbolicName from the following plan:\n" + sourceYaml);
throw new IllegalStateException("Can't infer catalog item symbolicName from catalog item metadata");
}
}
}
// if version not set, infer from: id, then from name, then item version
if (CatalogUtils.looksLikeVersionedId(id)) {
String versionFromId = CatalogUtils.getVersionFromVersionedId(id);
if (versionFromId != null && Strings.isNonBlank(version) && !versionFromId.equals(version)) {
throw new IllegalArgumentException("Discrepency between version set in id " + versionFromId + " and version property " + version);
}
version = versionFromId;
}
if (Strings.isBlank(version)) {
if (CatalogUtils.looksLikeVersionedId(name)) {
version = CatalogUtils.getVersionFromVersionedId(name);
} else if (Strings.isBlank(version)) {
version = setFromItemIfUnset(version, itemAsMap, "version");
version = setFromItemIfUnset(version, itemAsMap, "template_version");
if (version==null) {
log.warn("No version specified for catalog item " + symbolicName + ". Using default value.");
version = null;
}
}
}
// if not set, ID can come from symname:version, failing that, from the plan.id, failing that from the sym name
if (Strings.isBlank(id)) {
// let ID be inferred, especially from name, to support style where only "name" is specified, with inline version
if (Strings.isNonBlank(symbolicName) && Strings.isNonBlank(version)) {
id = symbolicName + ":" + version;
}
id = setFromItemIfUnset(id, itemAsMap, "id");
if (Strings.isBlank(id)) {
if (Strings.isNonBlank(symbolicName)) {
id = symbolicName;
} else {
log.error("Can't infer catalog item id from the following plan:\n" + sourceYaml);
throw new IllegalStateException("Can't infer catalog item id from catalog item metadata");
}
}
}
if (Strings.isBlank(displayName)) {
if (Strings.isNonBlank(name)) displayName = name;
displayName = setFromItemIfUnset(displayName, itemAsMap, "name");
}
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
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
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);
}
String sourcePlanYaml = planInterpreter.getPlanYaml();
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);
}