| /* |
| * |
| * 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.qpid.server.model; |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.lang.reflect.ParameterizedType; |
| import java.lang.reflect.Type; |
| import java.util.AbstractCollection; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| |
| import com.fasterxml.jackson.core.JsonProcessingException; |
| import com.google.common.util.concurrent.ListenableFuture; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import org.apache.qpid.server.plugin.ConfiguredObjectAttributeInjector; |
| import org.apache.qpid.server.plugin.ConfiguredObjectRegistration; |
| import org.apache.qpid.server.plugin.ConfiguredObjectTypeFactory; |
| import org.apache.qpid.server.util.Action; |
| import org.apache.qpid.server.util.ServerScopedRuntimeException; |
| import org.apache.qpid.util.Strings; |
| |
| public class ConfiguredObjectTypeRegistry |
| { |
| private static final Logger LOGGER = LoggerFactory.getLogger(ConfiguredObjectTypeRegistry.class); |
| |
| private static Map<String, Integer> STANDARD_FIRST_FIELDS_ORDER = new HashMap<>(); |
| |
| private static final ConcurrentMap<Class<?>, Class<? extends ConfiguredObject>> CATEGORY_CACHE = |
| new ConcurrentHashMap<>(); |
| |
| static |
| { |
| int i = 0; |
| for (String name : Arrays.asList(ConfiguredObject.ID, |
| ConfiguredObject.NAME, |
| ConfiguredObject.DESCRIPTION, |
| ConfiguredObject.TYPE, |
| ConfiguredObject.DESIRED_STATE, |
| ConfiguredObject.STATE, |
| ConfiguredObject.DURABLE, |
| ConfiguredObject.LIFETIME_POLICY, |
| ConfiguredObject.CONTEXT)) |
| { |
| STANDARD_FIRST_FIELDS_ORDER.put(name, i++); |
| } |
| |
| } |
| |
| private static Map<String, Integer> STANDARD_LAST_FIELDS_ORDER = new HashMap<>(); |
| |
| static |
| { |
| int i = 0; |
| for (String name : Arrays.asList(ConfiguredObject.LAST_UPDATED_BY, |
| ConfiguredObject.LAST_UPDATED_TIME, |
| ConfiguredObject.CREATED_BY, |
| ConfiguredObject.CREATED_TIME)) |
| { |
| STANDARD_LAST_FIELDS_ORDER.put(name, i++); |
| } |
| |
| } |
| |
| |
| private static final Comparator<ConfiguredObjectAttributeOrStatistic<?, ?>> OBJECT_NAME_COMPARATOR = |
| new Comparator<ConfiguredObjectAttributeOrStatistic<?, ?>>() |
| { |
| @Override |
| public int compare(final ConfiguredObjectAttributeOrStatistic<?, ?> left, |
| final ConfiguredObjectAttributeOrStatistic<?, ?> right) |
| { |
| String leftName = left.getName(); |
| String rightName = right.getName(); |
| return compareAttributeNames(leftName, rightName); |
| } |
| }; |
| |
| private static final Comparator<String> NAME_COMPARATOR = new Comparator<String>() |
| { |
| @Override |
| public int compare(final String left, final String right) |
| { |
| return compareAttributeNames(left, right); |
| } |
| }; |
| |
| private static int compareAttributeNames(final String leftName, final String rightName) |
| { |
| int result; |
| if (leftName.equals(rightName)) |
| { |
| result = 0; |
| } |
| else if (STANDARD_FIRST_FIELDS_ORDER.containsKey(leftName)) |
| { |
| if (STANDARD_FIRST_FIELDS_ORDER.containsKey(rightName)) |
| { |
| result = STANDARD_FIRST_FIELDS_ORDER.get(leftName) - STANDARD_FIRST_FIELDS_ORDER.get(rightName); |
| } |
| else |
| { |
| result = -1; |
| } |
| } |
| else if (STANDARD_FIRST_FIELDS_ORDER.containsKey(rightName)) |
| { |
| result = 1; |
| } |
| else if (STANDARD_LAST_FIELDS_ORDER.containsKey(rightName)) |
| { |
| if (STANDARD_LAST_FIELDS_ORDER.containsKey(leftName)) |
| { |
| result = STANDARD_LAST_FIELDS_ORDER.get(leftName) - STANDARD_LAST_FIELDS_ORDER.get(rightName); |
| } |
| else |
| { |
| result = -1; |
| } |
| } |
| else if (STANDARD_LAST_FIELDS_ORDER.containsKey(leftName)) |
| { |
| result = 1; |
| } |
| else |
| { |
| result = leftName.compareTo(rightName); |
| } |
| |
| return result; |
| } |
| |
| |
| private final Map<Class<? extends ConfiguredObject>, Collection<ConfiguredObjectAttribute<?, ?>>> _allAttributes = |
| Collections.synchronizedMap(new HashMap<Class<? extends ConfiguredObject>, Collection<ConfiguredObjectAttribute<?, ?>>>()); |
| |
| private final Map<Class<? extends ConfiguredObject>, Collection<ConfiguredObjectStatistic<?, ?>>> _allStatistics = |
| Collections.synchronizedMap(new HashMap<Class<? extends ConfiguredObject>, Collection<ConfiguredObjectStatistic<?, ?>>>()); |
| |
| private final Map<Class<? extends ConfiguredObject>, Map<String, ConfiguredObjectAttribute<?, ?>>> |
| _allAttributeTypes = |
| Collections.synchronizedMap(new HashMap<Class<? extends ConfiguredObject>, Map<String, ConfiguredObjectAttribute<?, ?>>>()); |
| |
| private final Map<Class<? extends ConfiguredObject>, Map<String, AutomatedField>> _allAutomatedFields = |
| Collections.synchronizedMap(new HashMap<Class<? extends ConfiguredObject>, Map<String, AutomatedField>>()); |
| |
| private final Map<String, String> _defaultContext = |
| Collections.synchronizedMap(new HashMap<String, String>()); |
| |
| private final Map<String, ManagedContextDefault> _contextDefinitions = |
| Collections.synchronizedMap(new HashMap<String, ManagedContextDefault>()); |
| private final Map<Class<? extends ConfiguredObject>, Set<String>> _contextUses = |
| Collections.synchronizedMap(new HashMap<Class<? extends ConfiguredObject>, Set<String>>()); |
| |
| private final Map<Class<? extends ConfiguredObject>, Set<Class<? extends ConfiguredObject>>> _knownTypes = |
| Collections.synchronizedMap(new HashMap<Class<? extends ConfiguredObject>, Set<Class<? extends ConfiguredObject>>>()); |
| |
| private final Map<Class<? extends ConfiguredObject>, Collection<ConfiguredObjectAttribute<?, ?>>> |
| _typeSpecificAttributes = |
| Collections.synchronizedMap(new HashMap<Class<? extends ConfiguredObject>, Collection<ConfiguredObjectAttribute<?, ?>>>()); |
| |
| private final Map<Class<? extends ConfiguredObject>, Map<State, Map<State, Method>>> _stateChangeMethods = |
| Collections.synchronizedMap(new HashMap<Class<? extends ConfiguredObject>, Map<State, Map<State, Method>>>()); |
| |
| private final Map<Class<? extends ConfiguredObject>, Set<Class<? extends ManagedInterface>>> _allManagedInterfaces = |
| Collections.synchronizedMap(new HashMap<Class<? extends ConfiguredObject>, Set<Class<? extends ManagedInterface>>>()); |
| |
| private final Map<Class<? extends ConfiguredObject>, Set<ConfiguredObjectOperation<?>>> _allOperations = |
| Collections.synchronizedMap(new HashMap<Class<? extends ConfiguredObject>, Set<ConfiguredObjectOperation<?>>>()); |
| |
| |
| private final Map<Class<? extends ConfiguredObject>, Map<String, Collection<String>>> _validChildTypes = |
| Collections.synchronizedMap(new HashMap<Class<? extends ConfiguredObject>, Map<String, Collection<String>>>()); |
| |
| private final ConfiguredObjectFactory _objectFactory; |
| private final Iterable<ConfiguredObjectAttributeInjector> _attributeInjectors; |
| |
| public ConfiguredObjectTypeRegistry(Iterable<ConfiguredObjectRegistration> configuredObjectRegistrations, |
| final Iterable<ConfiguredObjectAttributeInjector> attributeInjectors, |
| Collection<Class<? extends ConfiguredObject>> categoriesRestriction, |
| final ConfiguredObjectFactory objectFactory) |
| { |
| _objectFactory = objectFactory; |
| _attributeInjectors = attributeInjectors; |
| Set<Class<? extends ConfiguredObject>> categories = new HashSet<>(); |
| Set<Class<? extends ConfiguredObject>> types = new HashSet<>(); |
| |
| |
| for (ConfiguredObjectRegistration registration : configuredObjectRegistrations) |
| { |
| for (Class<? extends ConfiguredObject> configuredObjectClass : registration.getConfiguredObjectClasses()) |
| { |
| if (categoriesRestriction.isEmpty() |
| || categoriesRestriction.contains(getCategory(configuredObjectClass))) |
| { |
| try |
| { |
| process(configuredObjectClass); |
| ManagedObject annotation = configuredObjectClass.getAnnotation(ManagedObject.class); |
| if (annotation.category()) |
| { |
| categories.add(configuredObjectClass); |
| } |
| else |
| { |
| Class<? extends ConfiguredObject> category = getCategory(configuredObjectClass); |
| if (category != null) |
| { |
| categories.add(category); |
| } |
| } |
| if (!"".equals(annotation.type())) |
| { |
| types.add(configuredObjectClass); |
| } |
| } |
| catch (NoClassDefFoundError ncdfe) |
| { |
| LOGGER.warn("A class definition could not be found while processing the model for '" |
| + configuredObjectClass.getName() |
| + "': " |
| + ncdfe.getMessage()); |
| } |
| } |
| } |
| } |
| for (Class<? extends ConfiguredObject> categoryClass : categories) |
| { |
| _knownTypes.put(categoryClass, new HashSet<Class<? extends ConfiguredObject>>()); |
| } |
| |
| for (Class<? extends ConfiguredObject> typeClass : types) |
| { |
| for (Class<? extends ConfiguredObject> categoryClass : categories) |
| { |
| if (categoryClass.isAssignableFrom(typeClass)) |
| { |
| ManagedObject annotation = typeClass.getAnnotation(ManagedObject.class); |
| String annotationType = annotation.type(); |
| if (ModelRoot.class.isAssignableFrom(categoryClass) || factoryExists(categoryClass, annotationType)) |
| { |
| _knownTypes.get(categoryClass).add(typeClass); |
| } |
| } |
| } |
| } |
| |
| for (Class<? extends ConfiguredObject> categoryClass : categories) |
| { |
| Set<Class<? extends ConfiguredObject>> typesForCategory = _knownTypes.get(categoryClass); |
| if (typesForCategory.isEmpty()) |
| { |
| typesForCategory.add(categoryClass); |
| _typeSpecificAttributes.put(categoryClass, Collections.<ConfiguredObjectAttribute<?, ?>>emptySet()); |
| } |
| else |
| { |
| Set<String> commonAttributes = new HashSet<>(); |
| for (ConfiguredObjectAttribute<?, ?> attribute : _allAttributes.get(categoryClass)) |
| { |
| commonAttributes.add(attribute.getName()); |
| } |
| for (Class<? extends ConfiguredObject> typeClass : typesForCategory) |
| { |
| Set<ConfiguredObjectAttribute<?, ?>> attributes = new HashSet<>(); |
| for (ConfiguredObjectAttribute<?, ?> attr : _allAttributes.get(typeClass)) |
| { |
| if (!commonAttributes.contains(attr.getName())) |
| { |
| attributes.add(attr); |
| } |
| } |
| _typeSpecificAttributes.put(typeClass, attributes); |
| } |
| } |
| |
| } |
| |
| for (Class<? extends ConfiguredObject> type : types) |
| { |
| final ManagedObject annotation = type.getAnnotation(ManagedObject.class); |
| String validChildren = annotation.validChildTypes(); |
| if (!"".equals(validChildren)) |
| { |
| Method validChildTypesMethod = getValidChildTypesFunction(validChildren, type); |
| if (validChildTypesMethod != null) |
| { |
| try |
| { |
| _validChildTypes.put(type, |
| (Map<String, Collection<String>>) validChildTypesMethod.invoke(null)); |
| } |
| catch (IllegalAccessException | InvocationTargetException e) |
| { |
| throw new IllegalArgumentException("Exception while evaluating valid child types for " |
| + type.getName(), e); |
| } |
| } |
| |
| } |
| } |
| |
| validateContextDependencies(); |
| |
| } |
| |
| private void validateContextDependencies() |
| { |
| for(Map.Entry<Class<? extends ConfiguredObject>, Set<String>> entry : _contextUses.entrySet()) |
| { |
| for (String dependency : entry.getValue()) |
| { |
| if(!_contextDefinitions.containsKey(dependency)) |
| { |
| throw new IllegalArgumentException("Class " |
| + entry.getKey().getSimpleName() |
| + " defines a context dependency on a context variable '" |
| + dependency |
| + "' which is never defined"); |
| } |
| } |
| } |
| } |
| |
| |
| |
| private boolean factoryExists(final Class<? extends ConfiguredObject> categoryClass, final String type) |
| { |
| try |
| { |
| final ConfiguredObjectTypeFactory factory = |
| _objectFactory.getConfiguredObjectTypeFactory(categoryClass.getSimpleName(), type); |
| |
| return factory != null && factory.getType().equals(type); |
| } |
| catch (NoFactoryForTypeException | NoFactoryForCategoryException e) |
| { |
| return false; |
| } |
| } |
| |
| private static Method getValidChildTypesFunction(final String validValue, |
| final Class<? extends ConfiguredObject> clazz) |
| { |
| if (validValue.matches("([\\w][\\w\\d_]+\\.)+[\\w][\\w\\d_\\$]*#[\\w\\d_]+\\s*\\(\\s*\\)")) |
| { |
| String function = validValue; |
| try |
| { |
| String className = function.split("#")[0].trim(); |
| String methodName = function.split("#")[1].split("\\(")[0].trim(); |
| Class<?> validValueCalculatingClass = Class.forName(className); |
| Method method = validValueCalculatingClass.getMethod(methodName); |
| if (Modifier.isStatic(method.getModifiers()) && Modifier.isPublic(method.getModifiers())) |
| { |
| if (Map.class.isAssignableFrom(method.getReturnType())) |
| { |
| if (method.getGenericReturnType() instanceof ParameterizedType) |
| { |
| Type keyType = |
| ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0]; |
| if (keyType == String.class) |
| { |
| Type valueType = |
| ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[1]; |
| if (valueType instanceof ParameterizedType) |
| { |
| ParameterizedType paramType = (ParameterizedType) valueType; |
| final Type rawType = paramType.getRawType(); |
| final Type[] args = paramType.getActualTypeArguments(); |
| if (Collection.class.isAssignableFrom((Class<?>) rawType) |
| && args.length == 1 |
| && args[0] == String.class) |
| { |
| return method; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| throw new IllegalArgumentException("The validChildTypes of the class " |
| + clazz.getSimpleName() |
| + " has value '" |
| + validValue |
| + "' but the method does not meet the requirements - is it public and static"); |
| |
| } |
| catch (ClassNotFoundException | NoSuchMethodException e) |
| { |
| throw new IllegalArgumentException("The validChildTypes of the class " |
| + clazz.getSimpleName() |
| + " has value '" |
| + validValue |
| + "' which looks like it should be a method," |
| + " but no such method could be used.", e); |
| } |
| } |
| else |
| { |
| throw new IllegalArgumentException("The validChildTypes of the class " |
| + clazz.getSimpleName() |
| + " has value '" |
| + validValue |
| + "' which does not match the required <package>.<class>#<method>() format."); |
| } |
| } |
| |
| public static Class<? extends ConfiguredObject> getCategory(final Class<?> clazz) |
| { |
| Class<? extends ConfiguredObject> category = CATEGORY_CACHE.get(clazz); |
| if (category == null) |
| { |
| category = calculateCategory(clazz); |
| if (category != null) |
| { |
| CATEGORY_CACHE.putIfAbsent(clazz, category); |
| } |
| } |
| return category; |
| } |
| |
| private static Class<? extends ConfiguredObject> calculateCategory(final Class<?> clazz) |
| { |
| ManagedObject annotation = clazz.getAnnotation(ManagedObject.class); |
| if (annotation != null && annotation.category()) |
| { |
| return (Class<? extends ConfiguredObject>) clazz; |
| } |
| for (Class<?> iface : clazz.getInterfaces()) |
| { |
| Class<? extends ConfiguredObject> cat = getCategory(iface); |
| if (cat != null) |
| { |
| return cat; |
| } |
| } |
| if (clazz.getSuperclass() != null) |
| { |
| return getCategory(clazz.getSuperclass()); |
| } |
| return null; |
| } |
| |
| public Class<? extends ConfiguredObject> getTypeClass(final Class<? extends ConfiguredObject> clazz) |
| { |
| String typeName = getType(clazz); |
| Class<? extends ConfiguredObject> typeClass = null; |
| if (typeName != null) |
| { |
| Class<? extends ConfiguredObject> category = getCategory(clazz); |
| Set<Class<? extends ConfiguredObject>> types = _knownTypes.get(category); |
| if (types != null) |
| { |
| for (Class<? extends ConfiguredObject> type : types) |
| { |
| ManagedObject annotation = type.getAnnotation(ManagedObject.class); |
| if (typeName.equals(annotation.type())) |
| { |
| typeClass = type; |
| break; |
| } |
| } |
| } |
| if (typeClass == null) |
| { |
| if (typeName.equals(category.getSimpleName())) |
| { |
| typeClass = category; |
| } |
| } |
| } |
| |
| return typeClass; |
| |
| } |
| |
| public Collection<Class<? extends ConfiguredObject>> getTypeSpecialisations(Class<? extends ConfiguredObject> clazz) |
| { |
| Class<? extends ConfiguredObject> categoryClass = getCategory(clazz); |
| if (categoryClass == null) |
| { |
| throw new IllegalArgumentException("Cannot locate ManagedObject information for " + clazz.getName()); |
| } |
| Set<Class<? extends ConfiguredObject>> classes = _knownTypes.get(categoryClass); |
| if (classes == null) |
| { |
| classes = (Set<Class<? extends ConfiguredObject>>) ((Set) Collections.singleton(clazz)); |
| } |
| return Collections.unmodifiableCollection(classes); |
| |
| } |
| |
| public Collection<ConfiguredObjectAttribute<?, ?>> getTypeSpecificAttributes(Class<? extends ConfiguredObject> clazz) |
| { |
| Class<? extends ConfiguredObject> typeClass = getTypeClass(clazz); |
| if (typeClass == null) |
| { |
| throw new IllegalArgumentException("Cannot locate ManagedObject information for " + clazz.getName()); |
| } |
| Collection<ConfiguredObjectAttribute<?, ?>> typeAttrs = _typeSpecificAttributes.get(typeClass); |
| return Collections.unmodifiableCollection(typeAttrs == null |
| ? Collections.<ConfiguredObjectAttribute<?, ?>>emptySet() |
| : typeAttrs); |
| } |
| |
| public static String getType(final Class<? extends ConfiguredObject> clazz) |
| { |
| String type = getActualType(clazz); |
| |
| if ("".equals(type)) |
| { |
| Class<? extends ConfiguredObject> category = getCategory(clazz); |
| if (category == null) |
| { |
| throw new IllegalArgumentException("No category for " + clazz.getSimpleName()); |
| } |
| ManagedObject annotation = category.getAnnotation(ManagedObject.class); |
| if (annotation == null) |
| { |
| throw new NullPointerException("No definition found for category " + category.getSimpleName()); |
| } |
| if (!"".equals(annotation.defaultType())) |
| { |
| type = annotation.defaultType(); |
| } |
| else |
| { |
| type = category.getSimpleName(); |
| } |
| } |
| return type; |
| } |
| |
| private static String getActualType(final Class<? extends ConfiguredObject> clazz) |
| { |
| ManagedObject annotation = clazz.getAnnotation(ManagedObject.class); |
| if (annotation != null) |
| { |
| if (!"".equals(annotation.type())) |
| { |
| return annotation.type(); |
| } |
| } |
| |
| |
| for (Class<?> iface : clazz.getInterfaces()) |
| { |
| if (ConfiguredObject.class.isAssignableFrom(iface)) |
| { |
| String type = getActualType((Class<? extends ConfiguredObject>) iface); |
| if (!"".equals(type)) |
| { |
| return type; |
| } |
| } |
| } |
| |
| if (clazz.getSuperclass() != null && ConfiguredObject.class.isAssignableFrom(clazz.getSuperclass())) |
| { |
| String type = getActualType((Class<? extends ConfiguredObject>) clazz.getSuperclass()); |
| if (!"".equals(type)) |
| { |
| return type; |
| } |
| } |
| |
| return ""; |
| } |
| |
| public Strings.Resolver getDefaultContextResolver() |
| { |
| return new Strings.MapResolver(_defaultContext); |
| } |
| |
| static class AutomatedField |
| { |
| private final Field _field; |
| private final Method _preSettingAction; |
| private final Method _postSettingAction; |
| |
| private AutomatedField(final Field field, final Method preSettingAction, final Method postSettingAction) |
| { |
| _field = field; |
| _preSettingAction = preSettingAction; |
| _postSettingAction = postSettingAction; |
| } |
| |
| public Field getField() |
| { |
| return _field; |
| } |
| |
| public Method getPreSettingAction() |
| { |
| return _preSettingAction; |
| } |
| |
| public Method getPostSettingAction() |
| { |
| return _postSettingAction; |
| } |
| } |
| |
| |
| private <X extends ConfiguredObject> void process(final Class<X> clazz) |
| { |
| synchronized (_allAttributes) |
| { |
| if (_allAttributes.containsKey(clazz)) |
| { |
| return; |
| } |
| doWithAllParents(clazz, new Action<Class<? extends ConfiguredObject>>() |
| { |
| @Override |
| public void performAction(final Class<? extends ConfiguredObject> parent) |
| { |
| process(parent); |
| } |
| }); |
| |
| final SortedSet<ConfiguredObjectAttribute<?, ?>> attributeSet = new TreeSet<>(OBJECT_NAME_COMPARATOR); |
| final SortedSet<ConfiguredObjectStatistic<?, ?>> statisticSet = new TreeSet<>(OBJECT_NAME_COMPARATOR); |
| final Set<Class<? extends ManagedInterface>> managedInterfaces = new HashSet<>(); |
| final Set<ConfiguredObjectOperation<?>> operationsSet = new HashSet<>(); |
| final Set<String> contextSet = new HashSet<>(); |
| |
| _allAttributes.put(clazz, attributeSet); |
| _allStatistics.put(clazz, statisticSet); |
| _allManagedInterfaces.put(clazz, managedInterfaces); |
| _allOperations.put(clazz, operationsSet); |
| _contextUses.put(clazz, contextSet); |
| |
| doWithAllParents(clazz, new Action<Class<? extends ConfiguredObject>>() |
| { |
| @Override |
| public void performAction(final Class<? extends ConfiguredObject> parent) |
| { |
| initialiseWithParentAttributes(attributeSet, |
| statisticSet, |
| managedInterfaces, |
| operationsSet, |
| contextSet, |
| parent); |
| |
| } |
| }); |
| |
| processMethods(clazz, attributeSet, statisticSet, operationsSet); |
| |
| processAttributesTypesAndFields(clazz); |
| |
| processDefaultContext(clazz, contextSet); |
| |
| processStateChangeMethods(clazz); |
| |
| processManagedInterfaces(clazz); |
| |
| processContextUages(clazz, contextSet); |
| } |
| } |
| |
| private <X extends ConfiguredObject> void processContextUages(final Class<X> clazz, final Set<String> contextSet) |
| { |
| if (clazz.isAnnotationPresent(ManagedContextDependency.class)) |
| { |
| ManagedContextDependency dependencies = clazz.getAnnotation(ManagedContextDependency.class); |
| for (String dependency : dependencies.value()) |
| { |
| contextSet.add(dependency); |
| } |
| } |
| } |
| |
| private static void doWithAllParents(Class<?> clazz, Action<Class<? extends ConfiguredObject>> action) |
| { |
| for (Class<?> parent : clazz.getInterfaces()) |
| { |
| if (ConfiguredObject.class.isAssignableFrom(parent)) |
| { |
| action.performAction((Class<? extends ConfiguredObject>) parent); |
| } |
| } |
| final Class<?> superclass = clazz.getSuperclass(); |
| if (superclass != null && ConfiguredObject.class.isAssignableFrom(superclass)) |
| { |
| action.performAction((Class<? extends ConfiguredObject>) superclass); |
| } |
| } |
| |
| private <X extends ConfiguredObject> void processMethods(final Class<X> clazz, |
| final SortedSet<ConfiguredObjectAttribute<?, ?>> attributeSet, |
| final SortedSet<ConfiguredObjectStatistic<?, ?>> statisticSet, |
| final Set<ConfiguredObjectOperation<?>> operationsSet) |
| { |
| for (Method method : clazz.getDeclaredMethods()) |
| { |
| processMethod(clazz, attributeSet, statisticSet, operationsSet, method); |
| } |
| |
| for (ConfiguredObjectAttributeInjector injector : _attributeInjectors) |
| { |
| for (ConfiguredObjectInjectedAttribute<?, ?> attr : injector.getInjectedAttributes()) |
| { |
| if (attr.appliesToConfiguredObjectType((Class<? extends ConfiguredObject<?>>) clazz)) |
| { |
| attributeSet.add(attr); |
| } |
| } |
| |
| for (ConfiguredObjectInjectedStatistic<?, ?> attr : injector.getInjectedStatistics()) |
| { |
| if (attr.appliesToConfiguredObjectType((Class<? extends ConfiguredObject<?>>) clazz)) |
| { |
| statisticSet.add(attr); |
| } |
| } |
| |
| for (ConfiguredObjectInjectedOperation<?> operation : injector.getInjectedOperations()) |
| { |
| if (operation.appliesToConfiguredObjectType((Class<? extends ConfiguredObject<?>>) clazz)) |
| { |
| operationsSet.add(operation); |
| } |
| } |
| } |
| } |
| |
| private <X extends ConfiguredObject> void processMethod(final Class<X> clazz, |
| final SortedSet<ConfiguredObjectAttribute<?, ?>> attributeSet, |
| final SortedSet<ConfiguredObjectStatistic<?, ?>> statisticSet, |
| final Set<ConfiguredObjectOperation<?>> operationsSet, |
| final Method m) |
| { |
| if (m.isAnnotationPresent(ManagedAttribute.class)) |
| { |
| processManagedAttribute(clazz, attributeSet, m); |
| } |
| else if (m.isAnnotationPresent(DerivedAttribute.class)) |
| { |
| processDerivedAttribute(clazz, attributeSet, m); |
| |
| } |
| else if (m.isAnnotationPresent(ManagedStatistic.class)) |
| { |
| processManagedStatistic(clazz, statisticSet, m); |
| } |
| else if (m.isAnnotationPresent(ManagedOperation.class)) |
| { |
| processManagedOperation(clazz, operationsSet, m); |
| } |
| } |
| |
| private <X extends ConfiguredObject> void processManagedStatistic(final Class<X> clazz, |
| final SortedSet<ConfiguredObjectStatistic<?, ?>> statisticSet, |
| final Method m) |
| { |
| ManagedStatistic statAnnotation = m.getAnnotation(ManagedStatistic.class); |
| if (!clazz.isInterface() || !ConfiguredObject.class.isAssignableFrom(clazz)) |
| { |
| throw new ServerScopedRuntimeException("Can only define ManagedStatistics on interfaces which extend " |
| + ConfiguredObject.class.getSimpleName() |
| + ". " |
| + clazz.getSimpleName() |
| + " does not meet these criteria."); |
| } |
| ConfiguredObjectStatistic statistic = new ConfiguredObjectMethodStatistic(clazz, m, statAnnotation); |
| if (statisticSet.contains(statistic)) |
| { |
| statisticSet.remove(statistic); |
| } |
| statisticSet.add(statistic); |
| } |
| |
| private <X extends ConfiguredObject> void processDerivedAttribute(final Class<X> clazz, |
| final SortedSet<ConfiguredObjectAttribute<?, ?>> attributeSet, |
| final Method m) |
| { |
| DerivedAttribute annotation = m.getAnnotation(DerivedAttribute.class); |
| |
| if (!clazz.isInterface() || !ConfiguredObject.class.isAssignableFrom(clazz)) |
| { |
| throw new ServerScopedRuntimeException("Can only define DerivedAttributes on interfaces which extend " |
| + ConfiguredObject.class.getSimpleName() |
| + ". " |
| + clazz.getSimpleName() |
| + " does not meet these criteria."); |
| } |
| |
| ConfiguredObjectAttribute<?, ?> attribute = new ConfiguredDerivedMethodAttribute<>(clazz, m, annotation); |
| if (attributeSet.contains(attribute)) |
| { |
| attributeSet.remove(attribute); |
| } |
| attributeSet.add(attribute); |
| } |
| |
| private <X extends ConfiguredObject> void processManagedAttribute(final Class<X> clazz, |
| final SortedSet<ConfiguredObjectAttribute<?, ?>> attributeSet, |
| final Method m) |
| { |
| ManagedAttribute annotation = m.getAnnotation(ManagedAttribute.class); |
| |
| if (!clazz.isInterface() || !ConfiguredObject.class.isAssignableFrom(clazz)) |
| { |
| throw new ServerScopedRuntimeException("Can only define ManagedAttributes on interfaces which extend " |
| + ConfiguredObject.class.getSimpleName() |
| + ". " |
| + clazz.getSimpleName() |
| + " does not meet these criteria."); |
| } |
| |
| ConfiguredObjectAttribute<?, ?> attribute = new ConfiguredAutomatedAttribute<>(clazz, m, annotation); |
| if (attributeSet.contains(attribute)) |
| { |
| attributeSet.remove(attribute); |
| } |
| attributeSet.add(attribute); |
| } |
| |
| private <X extends ConfiguredObject> void processManagedOperation(final Class<X> clazz, |
| final Set<ConfiguredObjectOperation<?>> operationSet, |
| final Method m) |
| { |
| ManagedOperation annotation = m.getAnnotation(ManagedOperation.class); |
| |
| if (!clazz.isInterface() || !ConfiguredObject.class.isAssignableFrom(clazz)) |
| { |
| throw new ServerScopedRuntimeException("Can only define ManagedOperations on interfaces which extend " |
| + ConfiguredObject.class.getSimpleName() |
| + ". " |
| + clazz.getSimpleName() |
| + " does not meet these criteria."); |
| } |
| |
| ConfiguredObjectOperation<?> operation = new ConfiguredObjectMethodOperation<>(clazz, m, this); |
| Iterator<ConfiguredObjectOperation<?>> iter = operationSet.iterator(); |
| while (iter.hasNext()) |
| { |
| final ConfiguredObjectOperation<?> existingOperation = iter.next(); |
| if (operation.getName().equals(existingOperation.getName())) |
| { |
| if (!operation.hasSameParameters(existingOperation)) |
| { |
| throw new IllegalArgumentException("Cannot redefine the operation " |
| + operation.getName() |
| + " with different parameters in " |
| + clazz.getSimpleName()); |
| } |
| iter.remove(); |
| break; |
| } |
| } |
| operationSet.add(operation); |
| } |
| |
| |
| private void initialiseWithParentAttributes(final SortedSet<ConfiguredObjectAttribute<?, ?>> attributeSet, |
| final SortedSet<ConfiguredObjectStatistic<?, ?>> statisticSet, |
| final Set<Class<? extends ManagedInterface>> managedInterfaces, |
| final Set<ConfiguredObjectOperation<?>> operationsSet, |
| final Set<String> contextSet, |
| final Class<? extends ConfiguredObject> parent) |
| { |
| attributeSet.addAll(_allAttributes.get(parent)); |
| statisticSet.addAll(_allStatistics.get(parent)); |
| managedInterfaces.addAll(_allManagedInterfaces.get(parent)); |
| operationsSet.addAll(_allOperations.get(parent)); |
| contextSet.addAll(_contextUses.get(parent)); |
| } |
| |
| private <X extends ConfiguredObject> void processAttributesTypesAndFields(final Class<X> clazz) |
| { |
| Map<String, ConfiguredObjectAttribute<?, ?>> attrMap = new TreeMap<>(NAME_COMPARATOR); |
| Map<String, AutomatedField> fieldMap = new HashMap<String, AutomatedField>(); |
| |
| |
| Collection<ConfiguredObjectAttribute<?, ?>> attrCol = _allAttributes.get(clazz); |
| for (ConfiguredObjectAttribute<?, ?> attr : attrCol) |
| { |
| attrMap.put(attr.getName(), attr); |
| if (attr.isAutomated()) |
| { |
| fieldMap.put(attr.getName(), findField(attr, clazz)); |
| } |
| |
| } |
| for (ConfiguredObjectAttributeInjector injector : _attributeInjectors) |
| { |
| for (ConfiguredObjectInjectedAttribute<?, ?> attr : injector.getInjectedAttributes()) |
| { |
| if (!attrMap.containsKey(attr.getName()) |
| && attr.appliesToConfiguredObjectType((Class<? extends ConfiguredObject<?>>) clazz)) |
| { |
| attrMap.put(attr.getName(), attr); |
| } |
| } |
| } |
| _allAttributeTypes.put(clazz, attrMap); |
| _allAutomatedFields.put(clazz, fieldMap); |
| } |
| |
| private <X extends ConfiguredObject> void processDefaultContext(final Class<X> clazz, final Set<String> contextSet) |
| { |
| for (Field field : clazz.getDeclaredFields()) |
| { |
| if (Modifier.isStatic(field.getModifiers()) |
| && Modifier.isFinal(field.getModifiers()) |
| && field.isAnnotationPresent(ManagedContextDefault.class)) |
| { |
| try |
| { |
| ManagedContextDefault annotation = field.getAnnotation(ManagedContextDefault.class); |
| String name = annotation.name(); |
| Object value = field.get(null); |
| if (!_defaultContext.containsKey(name)) |
| { |
| final String stringValue; |
| if (value instanceof Collection || value instanceof Map) |
| { |
| try |
| { |
| stringValue = ConfiguredObjectJacksonModule.newObjectMapper(false).writeValueAsString(value); |
| } |
| catch (JsonProcessingException e) |
| { |
| throw new ServerScopedRuntimeException("Unable to convert value of type '" |
| + value.getClass() |
| + "' to a JSON string for context variable ${" |
| + name |
| + "}"); |
| } |
| } |
| else |
| { |
| stringValue = String.valueOf(value); |
| } |
| _defaultContext.put(name, stringValue); |
| _contextDefinitions.put(name, annotation); |
| contextSet.add(name); |
| } |
| else |
| { |
| throw new IllegalArgumentException("Multiple definitions of the default context variable ${" |
| + name |
| + "}"); |
| } |
| } |
| catch (IllegalAccessException e) |
| { |
| throw new ServerScopedRuntimeException( |
| "Unexpected illegal access exception (only inspecting public static fields)", |
| e); |
| } |
| } |
| } |
| } |
| |
| private void processStateChangeMethods(Class<? extends ConfiguredObject> clazz) |
| { |
| final Map<State, Map<State, Method>> map = new HashMap<>(); |
| |
| _stateChangeMethods.put(clazz, map); |
| |
| addStateTransitions(clazz, map); |
| |
| doWithAllParents(clazz, new Action<Class<? extends ConfiguredObject>>() |
| { |
| @Override |
| public void performAction(final Class<? extends ConfiguredObject> parent) |
| { |
| inheritTransitions(parent, map); |
| } |
| }); |
| } |
| |
| private void inheritTransitions(final Class<? extends ConfiguredObject> parent, |
| final Map<State, Map<State, Method>> map) |
| { |
| Map<State, Map<State, Method>> parentMap = _stateChangeMethods.get(parent); |
| for (Map.Entry<State, Map<State, Method>> parentEntry : parentMap.entrySet()) |
| { |
| if (map.containsKey(parentEntry.getKey())) |
| { |
| Map<State, Method> methodMap = map.get(parentEntry.getKey()); |
| for (Map.Entry<State, Method> methodEntry : parentEntry.getValue().entrySet()) |
| { |
| if (!methodMap.containsKey(methodEntry.getKey())) |
| { |
| methodMap.put(methodEntry.getKey(), methodEntry.getValue()); |
| } |
| } |
| } |
| else |
| { |
| map.put(parentEntry.getKey(), new HashMap<State, Method>(parentEntry.getValue())); |
| } |
| } |
| } |
| |
| private void addStateTransitions(final Class<? extends ConfiguredObject> clazz, |
| final Map<State, Map<State, Method>> map) |
| { |
| for (Method m : clazz.getDeclaredMethods()) |
| { |
| if (m.isAnnotationPresent(StateTransition.class)) |
| { |
| if (ListenableFuture.class.isAssignableFrom(m.getReturnType())) |
| { |
| if (m.getParameterTypes().length == 0) |
| { |
| m.setAccessible(true); |
| StateTransition annotation = m.getAnnotation(StateTransition.class); |
| |
| for (State state : annotation.currentState()) |
| { |
| addStateTransition(state, annotation.desiredState(), m, map); |
| } |
| |
| } |
| else |
| { |
| throw new ServerScopedRuntimeException( |
| "A state transition method must have no arguments. Method " |
| + m.getName() |
| + " on " |
| + clazz.getName() |
| + " does not meet this criteria."); |
| } |
| } |
| else |
| { |
| throw new ServerScopedRuntimeException( |
| "A state transition method must return a ListenableFuture. Method " |
| + m.getName() |
| + " on " |
| + clazz.getName() |
| + " does not meet this criteria."); |
| } |
| } |
| } |
| } |
| |
| private void addStateTransition(final State fromState, |
| final State toState, |
| final Method method, |
| final Map<State, Map<State, Method>> map) |
| { |
| if (map.containsKey(fromState)) |
| { |
| Map<State, Method> toMap = map.get(fromState); |
| if (!toMap.containsKey(toState)) |
| { |
| toMap.put(toState, method); |
| } |
| } |
| else |
| { |
| HashMap<State, Method> toMap = new HashMap<>(); |
| toMap.put(toState, method); |
| map.put(fromState, toMap); |
| } |
| } |
| |
| private AutomatedField findField(final ConfiguredObjectAttribute<?, ?> attr, Class<?> objClass) |
| { |
| Class<?> clazz = objClass; |
| while (clazz != null) |
| { |
| for (Field field : clazz.getDeclaredFields()) |
| { |
| if (field.isAnnotationPresent(ManagedAttributeField.class) && field.getName() |
| .equals("_" + attr.getName().replace('.', '_'))) |
| { |
| try |
| { |
| ManagedAttributeField annotation = field.getAnnotation(ManagedAttributeField.class); |
| field.setAccessible(true); |
| Method beforeSet; |
| if (!"".equals(annotation.beforeSet())) |
| { |
| beforeSet = clazz.getDeclaredMethod(annotation.beforeSet()); |
| beforeSet.setAccessible(true); |
| } |
| else |
| { |
| beforeSet = null; |
| } |
| Method afterSet; |
| if (!"".equals(annotation.afterSet())) |
| { |
| afterSet = clazz.getDeclaredMethod(annotation.afterSet()); |
| afterSet.setAccessible(true); |
| } |
| else |
| { |
| afterSet = null; |
| } |
| return new AutomatedField(field, beforeSet, afterSet); |
| } |
| catch (NoSuchMethodException e) |
| { |
| throw new ServerScopedRuntimeException( |
| "Cannot find method referenced by annotation for pre/post setting action", |
| e); |
| } |
| |
| } |
| } |
| clazz = clazz.getSuperclass(); |
| } |
| if (objClass.isInterface() || Modifier.isAbstract(objClass.getModifiers())) |
| { |
| return null; |
| } |
| throw new ServerScopedRuntimeException("Unable to find field definition for automated field " |
| + attr.getName() |
| + " in class " |
| + objClass.getName()); |
| } |
| |
| public <X extends ConfiguredObject> Collection<String> getAttributeNames(Class<X> clazz) |
| { |
| final Collection<ConfiguredObjectAttribute<? super X, ?>> attrs = getAttributes(clazz); |
| |
| return new AbstractCollection<String>() |
| { |
| @Override |
| public Iterator<String> iterator() |
| { |
| final Iterator<ConfiguredObjectAttribute<? super X, ?>> underlyingIterator = attrs.iterator(); |
| return new Iterator<String>() |
| { |
| @Override |
| public boolean hasNext() |
| { |
| return underlyingIterator.hasNext(); |
| } |
| |
| @Override |
| public String next() |
| { |
| return underlyingIterator.next().getName(); |
| } |
| |
| @Override |
| public void remove() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } |
| |
| @Override |
| public int size() |
| { |
| return attrs.size(); |
| } |
| }; |
| |
| } |
| |
| protected <X extends ConfiguredObject> Collection<ConfiguredObjectAttribute<? super X, ?>> getAttributes(final Class<X> clazz) |
| { |
| processClassIfNecessary(clazz); |
| final Collection<ConfiguredObjectAttribute<? super X, ?>> attributes = (Collection) _allAttributes.get(clazz); |
| return attributes; |
| } |
| |
| private <X extends ConfiguredObject> void processClassIfNecessary(final Class<X> clazz) |
| { |
| if (!_allAttributes.containsKey(clazz)) |
| { |
| process(clazz); |
| } |
| } |
| |
| |
| public Collection<ConfiguredObjectStatistic> getStatistics(final Class<? extends ConfiguredObject> clazz) |
| { |
| processClassIfNecessary(clazz); |
| final Collection<ConfiguredObjectStatistic> statistics = (Collection) _allStatistics.get(clazz); |
| return statistics; |
| } |
| |
| public Map<String, ConfiguredObjectOperation<?>> getOperations(final Class<? extends ConfiguredObject> clazz) |
| { |
| processClassIfNecessary(clazz); |
| final Set<ConfiguredObjectOperation<?>> operations = _allOperations.get(clazz); |
| if (operations == null) |
| { |
| return Collections.emptyMap(); |
| } |
| else |
| { |
| Map<String, ConfiguredObjectOperation<?>> returnVal = new HashMap<>(); |
| for (ConfiguredObjectOperation<?> operation : operations) |
| { |
| returnVal.put(operation.getName(), operation); |
| } |
| return returnVal; |
| } |
| } |
| |
| public Map<String, ConfiguredObjectAttribute<?, ?>> getAttributeTypes(final Class<? extends ConfiguredObject> clazz) |
| { |
| processClassIfNecessary(clazz); |
| return _allAttributeTypes.get(clazz); |
| } |
| |
| Map<String, AutomatedField> getAutomatedFields(Class<? extends ConfiguredObject> clazz) |
| { |
| processClassIfNecessary(clazz); |
| return _allAutomatedFields.get(clazz); |
| } |
| |
| Map<State, Map<State, Method>> getStateChangeMethods(final Class<? extends ConfiguredObject> objectClass) |
| { |
| processClassIfNecessary(objectClass); |
| Map<State, Map<State, Method>> map = _stateChangeMethods.get(objectClass); |
| |
| return map != null ? Collections.unmodifiableMap(map) : Collections.<State, Map<State, Method>>emptyMap(); |
| } |
| |
| public Map<String, String> getDefaultContext() |
| { |
| return Collections.unmodifiableMap(_defaultContext); |
| } |
| |
| |
| public Set<Class<? extends ManagedInterface>> getManagedInterfaces(final Class<? extends ConfiguredObject> classObject) |
| { |
| processClassIfNecessary(classObject); |
| Set<Class<? extends ManagedInterface>> interfaces = _allManagedInterfaces.get(classObject); |
| return interfaces == null ? Collections.<Class<? extends ManagedInterface>>emptySet() : interfaces; |
| } |
| |
| |
| private <X extends ConfiguredObject> void processManagedInterfaces(Class<X> clazz) |
| { |
| final Set<Class<? extends ManagedInterface>> managedInterfaces = _allManagedInterfaces.get(clazz); |
| for (Class<?> iface : clazz.getInterfaces()) |
| { |
| if (iface.isAnnotationPresent(ManagedAnnotation.class) && ManagedInterface.class.isAssignableFrom(iface)) |
| { |
| managedInterfaces.add((Class<? extends ManagedInterface>) iface); |
| } |
| } |
| } |
| |
| public Collection<String> getValidChildTypes(Class<? extends ConfiguredObject> type, |
| Class<? extends ConfiguredObject> childType) |
| { |
| return getValidChildTypes(type, getCategory(childType).getSimpleName()); |
| } |
| |
| public Collection<String> getValidChildTypes(Class<? extends ConfiguredObject> type, |
| String childCategory) |
| { |
| final Map<String, Collection<String>> allValidChildTypes = _validChildTypes.get(getTypeClass(type)); |
| if (allValidChildTypes != null) |
| { |
| final Collection<String> validTypesForSpecificChild = |
| allValidChildTypes.get(childCategory); |
| return validTypesForSpecificChild == null |
| ? null |
| : Collections.unmodifiableCollection(validTypesForSpecificChild); |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| public Collection<ManagedContextDefault> getContextDependencies(Class<? extends ConfiguredObject> type) |
| { |
| Collection<String> dependencyNames = _contextUses.get(type); |
| if (dependencyNames != null) |
| { |
| List<ManagedContextDefault> dependencies = new ArrayList<>(dependencyNames.size()); |
| for (String dependencyName : dependencyNames) |
| { |
| dependencies.add(_contextDefinitions.get(dependencyName)); |
| } |
| return dependencies; |
| } |
| else |
| { |
| return Collections.emptySet(); |
| } |
| } |
| |
| public Collection<ManagedContextDefault> getTypeSpecificContextDependencies(Class<? extends ConfiguredObject> type) |
| { |
| final Collection<ManagedContextDefault> contextDependencies = getContextDependencies(type); |
| contextDependencies.removeAll(getContextDependencies(getCategory(type))); |
| |
| return contextDependencies; |
| } |
| } |