/*
* 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 groovy.xml.Entity;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec;
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.RegisteredTypeLoadingContext;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.javalang.JavaClassNames;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableSet;
public class RegisteredTypeLoadingContexts {
private static final Logger log = LoggerFactory.getLogger(RegisteredTypeLoadingContexts.class);
/** Immutable (from caller's perspective) record of a constraint */
public final static class BasicRegisteredTypeLoadingContext implements RegisteredTypeLoadingContext {
@Nullable private RegisteredTypeKind kind;
@Nullable private Class<?> expectedSuperType;
@Nonnull private Set<String> encounteredTypes = ImmutableSet.of();
@Nullable BrooklynClassLoadingContext loader;
private BasicRegisteredTypeLoadingContext() {}
public BasicRegisteredTypeLoadingContext(@Nullable RegisteredTypeLoadingContext source) {
if (source==null) return;
this.kind = source.getExpectedKind();
this.expectedSuperType = source.getExpectedJavaSuperType();
this.encounteredTypes = source.getAlreadyEncounteredTypes();
this.loader = source.getLoader();
}
@Override
public RegisteredTypeKind getExpectedKind() {
return kind;
}
@Override
public Class<?> getExpectedJavaSuperType() {
if (expectedSuperType==null) return Object.class;
return expectedSuperType;
}
@Override
public Set<String> getAlreadyEncounteredTypes() {
if (encounteredTypes==null) return ImmutableSet.of();
return ImmutableSet.<String>copyOf(encounteredTypes);
}
@Override
public BrooklynClassLoadingContext getLoader() {
return loader;
}
@Override
public String toString() {
return JavaClassNames.cleanSimpleClassName(this)+"["+kind+","+expectedSuperType+","+encounteredTypes+"]";
}
}
/** returns a constraint which allows anything */
public static RegisteredTypeLoadingContext any() {
return new BasicRegisteredTypeLoadingContext();
}
public static RegisteredTypeLoadingContext alreadyEncountered(Set<String> encounteredTypeSymbolicNames) {
BasicRegisteredTypeLoadingContext result = new BasicRegisteredTypeLoadingContext();
result.encounteredTypes = encounteredTypeSymbolicNames == null ? ImmutableSet.<String>of() : ImmutableSet.copyOf(encounteredTypeSymbolicNames);
return result;
}
public static RegisteredTypeLoadingContext alreadyEncountered(Set<String> encounteredTypeSymbolicNames, String anotherEncounteredType) {
BasicRegisteredTypeLoadingContext result = new BasicRegisteredTypeLoadingContext();
MutableSet<String> encounteredTypes = MutableSet.copyOf(encounteredTypeSymbolicNames);
encounteredTypes.addIfNotNull(anotherEncounteredType);
result.encounteredTypes = encounteredTypes.asUnmodifiable();
return result;
}
public static RegisteredTypeLoadingContext loaderAlreadyEncountered(BrooklynClassLoadingContext loader, Set<String> encounteredTypeSymbolicNames) {
return loaderAlreadyEncountered(loader, encounteredTypeSymbolicNames, null);
}
public static RegisteredTypeLoadingContext loaderAlreadyEncountered(BrooklynClassLoadingContext loader, Set<String> encounteredTypeSymbolicNames, String anotherEncounteredType) {
return withLoader(alreadyEncountered(encounteredTypeSymbolicNames, anotherEncounteredType), loader);
}
private static RegisteredTypeLoadingContext of(RegisteredTypeKind kind, Class<?> javaSuperType) {
BasicRegisteredTypeLoadingContext result = new BasicRegisteredTypeLoadingContext();
result.kind = kind;
result.expectedSuperType = javaSuperType;
return result;
}
public static RegisteredTypeLoadingContext bean(Class<?> javaSuperType) {
return of(RegisteredTypeKind.BEAN, javaSuperType);
}
public static RegisteredTypeLoadingContext spec(Class<? extends BrooklynObject> javaSuperType) {
return of(RegisteredTypeKind.SPEC, javaSuperType);
}
public static <T> RegisteredTypeLoadingContext withBeanSuperType(@Nullable RegisteredTypeLoadingContext source, @Nullable Class<T> beanSuperType) {
Class<T> superType = beanSuperType;
BasicRegisteredTypeLoadingContext constraint = new BasicRegisteredTypeLoadingContext(source);
if (source==null) source = constraint;
if (superType==null) return source;
constraint.expectedSuperType = superType;
if (source.getExpectedJavaSuperType()==null || source.getExpectedJavaSuperType().isAssignableFrom( superType )) {
// the old constraint was weaker than present; return the new constraint
return constraint;
}
if (superType.isAssignableFrom( source.getExpectedJavaSuperType() )) {
// the constraint was already for something more specific; ignore what we've inferred here
return source;
}
log.warn("Ambiguous bean supertypes ("+beanSuperType+" for target "+source.getExpectedJavaSuperType()+"); "
+ "it is recommended that any registered type constraint for a spec be compatible with the spec type");
return source;
}
/** Takes a Spec java type and adds an expected java type to the {@link RegisteredTypeLoadingContext} */
public static <T extends AbstractBrooklynObjectSpec<?,?>> RegisteredTypeLoadingContext withSpecSuperType(@Nullable RegisteredTypeLoadingContext source, @Nullable Class<T> specSuperType) {
Class<?> superType = lookupTargetTypeForSpec(specSuperType);
BasicRegisteredTypeLoadingContext constraint = new BasicRegisteredTypeLoadingContext(source);
if (source==null) source = constraint;
if (superType==null) return source;
constraint.expectedSuperType = superType;
if (source.getExpectedJavaSuperType()==null || source.getExpectedJavaSuperType().isAssignableFrom( superType )) {
// the old constraint was weaker than present; return the new constraint
return constraint;
}
if (superType.isAssignableFrom( source.getExpectedJavaSuperType() )) {
// the constraint was already for something more specific; ignore what we've inferred here
return source;
}
// trickier situation; the constraint had a type not compatible with the spec type; log a warning and leave alone
// (e.g. caller specified some java super type which is not a super or sub of the spec target type;
// this may be because the caller specified a Spec as the type supertype, which is wrong;
// or they may have specified an interface along a different hierarchy, which we discouraged
// as it will make filtering/indexing more complex)
log.warn("Ambiguous spec supertypes ("+specSuperType+" for target "+source.getExpectedJavaSuperType()+"); "
+ "it is recommended that any registered type constraint for a spec be compatible with the spec type");
return source;
}
/** given a spec, returns the class of the item it targets, for instance returns {@link Entity} given {@link EntitySpec};
* see also {@link #lookupSpecTypeForTarget(Class)} */
static <T extends AbstractBrooklynObjectSpec<?,?>> Class<? extends BrooklynObject> lookupTargetTypeForSpec(Class<T> specSuperType) {
if (specSuperType==null) return BrooklynObject.class;
BrooklynObjectType best = null;
for (BrooklynObjectType t: BrooklynObjectType.values()) {
if (t.getSpecType()==null) continue;
if (!t.getSpecType().isAssignableFrom(specSuperType)) continue;
// on equality, exit immediately
if (t.getSpecType().equals(specSuperType)) return t.getInterfaceType();
// else pick which is best
if (best==null) { best = t; continue; }
// if t is more specific, it is better (handles case when e.g. a Policy is a subclass of Entity)
if (best.getSpecType().isAssignableFrom(t.getSpecType())) { best = t; continue; }
}
if (best==null) {
log.warn("Unexpected spec supertype ("+specSuperType+"); treating as any "+BrooklynObject.class, new Throwable("Trace for unexpected spec supertype"));
return BrooklynObject.class;
}
// the spec is more specific, but we're not familiar with it here; return the best
return best.getInterfaceType();
}
/** given a {@link BrooklynObject}, returns the spec class which would generate it, for instance returns {@link EntitySpec} given {@link Entity},
* or null if not known */
static <BO extends BrooklynObject> Class<? extends AbstractBrooklynObjectSpec<?,?>> lookupSpecTypeForTarget(Class<BO> targetSuperType) {
if (targetSuperType==null) return null;
BrooklynObjectType best = null;
for (BrooklynObjectType t: BrooklynObjectType.values()) {
if (t.getInterfaceType()==null) continue;
if (!t.getInterfaceType().isAssignableFrom(targetSuperType)) continue;
// on equality, exit immediately
if (t.getInterfaceType().equals(targetSuperType)) return t.getSpecType();
// else pick which is best
if (best==null) { best = t; continue; }
// if t is more specific, it is better (handles case when e.g. a Policy is a subclass of Entity)
if (best.getInterfaceType().isAssignableFrom(t.getInterfaceType())) { best = t; continue; }
}
if (best==null) {
log.warn("Unexpected target supertype ("+targetSuperType+"); unable to infer spec type");
return null;
}
// the spec is more specific, but we're not familiar with it here; return the best
return best.getSpecType();
}
public static RegisteredTypeLoadingContext loader(BrooklynClassLoadingContext loader) {
BasicRegisteredTypeLoadingContext result = new BasicRegisteredTypeLoadingContext();
result.loader = loader;
return result;
}
public static RegisteredTypeLoadingContext withLoader(RegisteredTypeLoadingContext constraint, BrooklynClassLoadingContext loader) {
BasicRegisteredTypeLoadingContext result = new BasicRegisteredTypeLoadingContext(constraint);
result.loader = loader;
return result;
}
}