/*
* 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 java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
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.internal.AbstractBrooklynObjectSpec;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry;
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.core.catalog.internal.BasicBrooklynCatalog;
import org.apache.brooklyn.core.catalog.internal.CatalogItemBuilder;
import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.text.Identifiers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
public class BasicBrooklynTypeRegistry implements BrooklynTypeRegistry {
private static final Logger log = LoggerFactory.getLogger(BasicBrooklynTypeRegistry.class);
private ManagementContext mgmt;
private Map<String,RegisteredType> localRegisteredTypes = MutableMap.of();
public BasicBrooklynTypeRegistry(ManagementContext mgmt) {
this.mgmt = mgmt;
}
public Iterable<RegisteredType> getAll() {
return getMatching(Predicates.alwaysTrue());
}
private Iterable<RegisteredType> getAllWithoutCatalog(Predicate<? super RegisteredType> filter) {
// TODO thread safety
// TODO optimisation? make indexes and look up?
return Iterables.filter(localRegisteredTypes.values(), filter);
}
private Maybe<RegisteredType> getExactWithoutLegacyCatalog(String symbolicName, String version, RegisteredTypeLoadingContext constraint) {
// TODO look in any nested/private registries
RegisteredType item = localRegisteredTypes.get(symbolicName+":"+version);
return RegisteredTypes.tryValidate(item, constraint);
}
@SuppressWarnings("deprecation")
@Override
public Iterable<RegisteredType> getMatching(Predicate<? super RegisteredType> filter) {
return Iterables.filter(Iterables.concat(
getAllWithoutCatalog(filter),
Iterables.transform(mgmt.getCatalog().getCatalogItems(), RegisteredTypes.CI_TO_RT)),
filter);
}
@SuppressWarnings("deprecation")
private Maybe<RegisteredType> getSingle(String symbolicNameOrAliasIfNoVersion, final String versionFinal, final RegisteredTypeLoadingContext contextFinal) {
RegisteredTypeLoadingContext context = contextFinal;
if (context==null) context = RegisteredTypeLoadingContexts.any();
String version = versionFinal;
if (version==null) version = BrooklynCatalog.DEFAULT_VERSION;
if (!BrooklynCatalog.DEFAULT_VERSION.equals(version)) {
// normal code path when version is supplied
Maybe<RegisteredType> type = getExactWithoutLegacyCatalog(symbolicNameOrAliasIfNoVersion, version, context);
if (type.isPresent()) return type;
}
if (BrooklynCatalog.DEFAULT_VERSION.equals(version)) {
// alternate code path, if version blank or default
Iterable<RegisteredType> types = getMatching(Predicates.and(RegisteredTypePredicates.symbolicName(symbolicNameOrAliasIfNoVersion),
RegisteredTypePredicates.satisfies(context)));
if (Iterables.isEmpty(types)) {
// look for alias if no exact symbolic name match AND no version is specified
types = getMatching(Predicates.and(RegisteredTypePredicates.alias(symbolicNameOrAliasIfNoVersion),
RegisteredTypePredicates.satisfies(context) ) );
// if there are multiple symbolic names then throw?
Set<String> uniqueSymbolicNames = MutableSet.of();
for (RegisteredType t: types) {
uniqueSymbolicNames.add(t.getSymbolicName());
}
if (uniqueSymbolicNames.size()>1) {
String message = "Multiple matches found for alias '"+symbolicNameOrAliasIfNoVersion+"': "+uniqueSymbolicNames+"; "
+ "refusing to select any.";
log.warn(message);
return Maybe.absent(message);
}
}
if (!Iterables.isEmpty(types)) {
RegisteredType type = RegisteredTypes.getBestVersion(types);
if (type!=null) return Maybe.of(type);
}
}
// missing case is to look for exact version in legacy catalog
CatalogItem<?, ?> item = mgmt.getCatalog().getCatalogItem(symbolicNameOrAliasIfNoVersion, version);
if (item!=null)
return Maybe.of( RegisteredTypes.CI_TO_RT.apply( item ) );
return Maybe.absent("No matches for "+symbolicNameOrAliasIfNoVersion+
(versionFinal!=null ? ":"+versionFinal : "")+
(contextFinal!=null ? " ("+contextFinal+")" : "") );
}
@Override
public RegisteredType get(String symbolicName, String version) {
return getSingle(symbolicName, version, null).orNull();
}
@Override
public RegisteredType get(String symbolicNameWithOptionalVersion, RegisteredTypeLoadingContext context) {
return getMaybe(symbolicNameWithOptionalVersion, context).orNull();
}
@Override
public Maybe<RegisteredType> getMaybe(String symbolicNameWithOptionalVersion, RegisteredTypeLoadingContext context) {
Maybe<RegisteredType> r1 = null;
if (CatalogUtils.looksLikeVersionedId(symbolicNameWithOptionalVersion)) {
String symbolicName = CatalogUtils.getSymbolicNameFromVersionedId(symbolicNameWithOptionalVersion);
String version = CatalogUtils.getVersionFromVersionedId(symbolicNameWithOptionalVersion);
r1 = getSingle(symbolicName, version, context);
if (r1.isPresent()) return r1;
}
Maybe<RegisteredType> r2 = getSingle(symbolicNameWithOptionalVersion, BrooklynCatalog.DEFAULT_VERSION, context);
if (r2.isPresent() || r1==null) return r2;
return r1;
}
@Override
public RegisteredType get(String symbolicNameWithOptionalVersion) {
return get(symbolicNameWithOptionalVersion, (RegisteredTypeLoadingContext)null);
}
@Override
public <SpecT extends AbstractBrooklynObjectSpec<?,?>> SpecT createSpec(RegisteredType type, @Nullable RegisteredTypeLoadingContext constraint, Class<SpecT> specSuperType) {
Preconditions.checkNotNull(type, "type");
if (type.getKind()!=RegisteredTypeKind.SPEC) {
throw new IllegalStateException("Cannot create spec from type "+type+" (kind "+type.getKind()+")");
}
return createSpec(type, type.getPlan(), type.getSymbolicName(), type.getVersion(), type.getSuperTypes(), constraint, specSuperType);
}
@SuppressWarnings({ "deprecation", "unchecked", "rawtypes" })
private <SpecT extends AbstractBrooklynObjectSpec<?,?>> SpecT createSpec(
RegisteredType type,
TypeImplementationPlan plan,
@Nullable String symbolicName, @Nullable String version, Set<Object> superTypes,
@Nullable RegisteredTypeLoadingContext constraint, Class<SpecT> specSuperType) {
// TODO type is only used to call to "transform"; we should perhaps change transform so it doesn't need the type?
if (constraint!=null) {
if (constraint.getExpectedKind()!=null && constraint.getExpectedKind()!=RegisteredTypeKind.SPEC) {
throw new IllegalStateException("Cannot create spec with constraint "+constraint);
}
if (constraint.getAlreadyEncounteredTypes().contains(symbolicName)) {
// avoid recursive cycle
// TODO implement using java if permitted
}
}
constraint = RegisteredTypeLoadingContexts.withSpecSuperType(constraint, specSuperType);
Maybe<Object> result = TypePlanTransformers.transform(mgmt, type, constraint);
if (result.isPresent()) return (SpecT) result.get();
// fallback: look up in (legacy) catalog
// TODO remove once all transformers are available in the new style
CatalogItem item = symbolicName!=null ? (CatalogItem) mgmt.getCatalog().getCatalogItem(symbolicName, version) : null;
if (item==null) {
// if not in catalog (because loading a new item?) then look up item based on type
// (only really used in tests; possibly also for any recursive legacy transformers we might have to create a CI; cross that bridge when we come to it)
CatalogItemType ciType = CatalogItemType.ofTargetClass( (Class)constraint.getExpectedJavaSuperType() );
if (ciType==null) {
// throw -- not supported for non-spec types
result.get();
}
item = CatalogItemBuilder.newItem(ciType,
symbolicName!=null ? symbolicName : Identifiers.makeRandomId(8),
version!=null ? version : BasicBrooklynCatalog.DEFAULT_VERSION)
.plan((String)plan.getPlanData())
.build();
}
try {
return (SpecT) BasicBrooklynCatalog.internalCreateSpecLegacy(mgmt, item, constraint.getAlreadyEncounteredTypes(), false);
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
// for now, combine this failure with the original
try {
result.get();
// above will throw -- so won't come here
throw new IllegalStateException("should have failed getting type resolution for "+symbolicName);
} catch (Exception e0) {
Set<Exception> exceptionsInOrder = MutableSet.of();
if (e0.toString().indexOf("none of the available transformers")>=0) {
// put the legacy exception first if none of the new transformers support the type
// (until the new transformer is the primary pathway)
exceptionsInOrder.add(e);
}
exceptionsInOrder.add(e0);
exceptionsInOrder.add(e);
throw Exceptions.create("Unable to instantiate "+(symbolicName==null ? "item" : symbolicName), exceptionsInOrder);
}
}
}
@Override
public <SpecT extends AbstractBrooklynObjectSpec<?, ?>> SpecT createSpecFromPlan(String planFormat, Object planData, RegisteredTypeLoadingContext optionalConstraint, Class<SpecT> optionalSpecSuperType) {
return createSpec(RegisteredTypes.spec(null, null, new BasicTypeImplementationPlan(planFormat, planData), null),
optionalConstraint, optionalSpecSuperType);
}
@Override
public <T> T createBean(RegisteredType type, RegisteredTypeLoadingContext constraint, Class<T> optionalResultSuperType) {
Preconditions.checkNotNull(type, "type");
if (type.getKind()!=RegisteredTypeKind.SPEC) {
throw new IllegalStateException("Cannot create spec 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);
}
if (constraint.getAlreadyEncounteredTypes().contains(type.getSymbolicName())) {
// avoid recursive cycle
// TODO create type using java if permitted?
// OR remove this creator from those permitted
}
}
constraint = RegisteredTypeLoadingContexts.withBeanSuperType(constraint, optionalResultSuperType);
@SuppressWarnings("unchecked")
T result = (T) TypePlanTransformers.transform(mgmt, type, constraint).get();
return result;
}
@Override
public <T> T createBeanFromPlan(String planFormat, Object planData, RegisteredTypeLoadingContext optionalConstraint, Class<T> optionalBeanSuperType) {
return createBean(RegisteredTypes.bean(null, null, new BasicTypeImplementationPlan(planFormat, planData), null),
optionalConstraint, optionalBeanSuperType);
}
@Beta // API is stabilising
public void addToLocalUnpersistedTypeRegistry(RegisteredType type, boolean canForce) {
Preconditions.checkNotNull(type);
Preconditions.checkNotNull(type.getSymbolicName());
Preconditions.checkNotNull(type.getVersion());
Preconditions.checkNotNull(type.getId());
if (!type.getId().equals(type.getSymbolicName()+":"+type.getVersion()))
Asserts.fail("Registered type "+type+" has ID / symname mismatch");
RegisteredType oldType = mgmt.getTypeRegistry().get(type.getId());
if (oldType==null || canForce) {
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;
}
throw new IllegalStateException("Cannot add "+type+" to catalog; different "+oldType+" is already present");
}
}
}