/* * * 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.Method; import java.lang.reflect.Modifier; import java.util.AbstractCollection; 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.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import org.apache.log4j.Logger; import org.apache.qpid.server.plugin.ConfiguredObjectRegistration; import org.apache.qpid.server.util.ServerScopedRuntimeException; import org.apache.qpid.util.Strings; public class ConfiguredObjectTypeRegistry { private static final Logger LOGGER = Logger.getLogger(ConfiguredObjectTypeRegistry.class); private static Map STANDARD_FIRST_FIELDS_ORDER = new HashMap<>(); 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 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> OBJECT_NAME_COMPARATOR = new Comparator>() { @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 NAME_COMPARATOR = new Comparator() { @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, Collection>> _allAttributes = Collections.synchronizedMap(new HashMap, Collection>>()); private final Map, Collection>> _allStatistics = Collections.synchronizedMap(new HashMap, Collection>>()); private final Map, Map>> _allAttributeTypes = Collections.synchronizedMap(new HashMap, Map>>()); private final Map, Map> _allAutomatedFields = Collections.synchronizedMap(new HashMap, Map>()); private final Map _defaultContext = Collections.synchronizedMap(new HashMap()); private final Map,Set>> _knownTypes = Collections.synchronizedMap(new HashMap, Set>>()); private final Map, Collection>> _typeSpecificAttributes = Collections.synchronizedMap(new HashMap, Collection>>()); private final Map, Map>> _stateChangeMethods = Collections.synchronizedMap(new HashMap, Map>>()); public ConfiguredObjectTypeRegistry(Iterable configuredObjectRegistrations, Collection> categoriesRestriction) { Set> categories = new HashSet<>(); Set> types = new HashSet<>(); for (ConfiguredObjectRegistration registration : configuredObjectRegistrations) { for (Class 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 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 categoryClass : categories) { _knownTypes.put(categoryClass, new HashSet>()); } for (Class typeClass : types) { for (Class categoryClass : categories) { if (categoryClass.isAssignableFrom(typeClass)) { _knownTypes.get(categoryClass).add(typeClass); } } } for (Class categoryClass : categories) { Set> typesForCategory = _knownTypes.get(categoryClass); if (typesForCategory.isEmpty()) { typesForCategory.add(categoryClass); _typeSpecificAttributes.put(categoryClass, Collections.>emptySet()); } else { Set commonAttributes = new HashSet<>(); for(ConfiguredObjectAttribute attribute : _allAttributes.get(categoryClass)) { commonAttributes.add(attribute.getName()); } for(Class typeClass : typesForCategory) { Set> attributes = new HashSet<>(); for(ConfiguredObjectAttribute attr : _allAttributes.get(typeClass)) { if(!commonAttributes.contains(attr.getName())) { attributes.add(attr); } } _typeSpecificAttributes.put(typeClass, attributes); } } } } public static Class getCategory(final Class clazz) { ManagedObject annotation = clazz.getAnnotation(ManagedObject.class); if(annotation != null && annotation.category()) { return (Class) clazz; } for(Class iface : clazz.getInterfaces() ) { Class cat = getCategory(iface); if(cat != null) { return cat; } } if(clazz.getSuperclass() != null) { return getCategory(clazz.getSuperclass()); } return null; } private Class getTypeClass(final Class clazz) { String typeName = getType(clazz); Class typeClass = null; if(typeName != null) { Class category = getCategory(clazz); Set> types = _knownTypes.get(category); if(types != null) { for (Class type : types) { ManagedObject annotation = type.getAnnotation(ManagedObject.class); if (typeName.equals(annotation.type())) { typeClass = type; break; } } } if(typeClass == null && typeName.equals(category.getSimpleName())) { typeClass = category; } } return typeClass; } public Collection> getTypeSpecialisations(Class clazz) { Class categoryClass = getCategory(clazz); if(categoryClass == null) { throw new IllegalArgumentException("Cannot locate ManagedObject information for " + clazz.getName()); } Set> classes = _knownTypes.get(categoryClass); if(classes == null) { classes = (Set>) ((Set)Collections.singleton(clazz)); } return Collections.unmodifiableCollection(classes); } public Collection> getTypeSpecificAttributes(Class clazz) { Class typeClass = getTypeClass(clazz); if(typeClass == null) { throw new IllegalArgumentException("Cannot locate ManagedObject information for " + clazz.getName()); } Collection> typeAttrs = _typeSpecificAttributes.get(typeClass); return Collections.unmodifiableCollection(typeAttrs == null ? Collections.>emptySet() : typeAttrs); } public static String getType(final Class clazz) { String type = getActualType(clazz); if("".equals(type)) { Class 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 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) iface); if(!"".equals(type)) { return type; } } } if(clazz.getSuperclass() != null && ConfiguredObject.class.isAssignableFrom(clazz.getSuperclass())) { String type = getActualType((Class) 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 void process(final Class clazz) { synchronized (_allAttributes) { if(_allAttributes.containsKey(clazz)) { return; } for(Class parent : clazz.getInterfaces()) { if(ConfiguredObject.class.isAssignableFrom(parent)) { process((Class) parent); } } final Class superclass = clazz.getSuperclass(); if(superclass != null && ConfiguredObject.class.isAssignableFrom(superclass)) { process((Class) superclass); } final SortedSet> attributeSet = new TreeSet<>(OBJECT_NAME_COMPARATOR); final SortedSet> statisticSet = new TreeSet<>(OBJECT_NAME_COMPARATOR); _allAttributes.put(clazz, attributeSet); _allStatistics.put(clazz, statisticSet); for(Class parent : clazz.getInterfaces()) { if(ConfiguredObject.class.isAssignableFrom(parent)) { initialiseWithParentAttributes(attributeSet, statisticSet, (Class) parent); } } if(superclass != null && ConfiguredObject.class.isAssignableFrom(superclass)) { initialiseWithParentAttributes(attributeSet, statisticSet, (Class) superclass); } for(Method m : clazz.getDeclaredMethods()) { if(m.isAnnotationPresent(ManagedAttribute.class)) { 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); } else if(m.isAnnotationPresent(DerivedAttribute.class)) { 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 ConfiguredDerivedAttribute<>(clazz, m, annotation); if(attributeSet.contains(attribute)) { attributeSet.remove(attribute); } attributeSet.add(attribute); } else if(m.isAnnotationPresent(ManagedStatistic.class)) { 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 ConfiguredObjectStatistic(clazz, m); if(statisticSet.contains(statistic)) { statisticSet.remove(statistic); } statisticSet.add(statistic); } } processAttributesTypesAndFields(clazz); processDefaultContext(clazz); processStateChangeMethods(clazz); } } private void initialiseWithParentAttributes(final SortedSet> attributeSet, final SortedSet> statisticSet, final Class parent) { Collection> attrs = _allAttributes.get(parent); for(ConfiguredObjectAttribute attr : attrs) { if(!attributeSet.contains(attr)) { attributeSet.add(attr); } } Collection> stats = _allStatistics.get(parent); for(ConfiguredObjectStatistic stat : stats) { if(!statisticSet.contains(stat)) { statisticSet.add(stat); } } } private void processAttributesTypesAndFields(final Class clazz) { Map> attrMap = new TreeMap<>(NAME_COMPARATOR); Map fieldMap = new HashMap(); Collection> attrCol = _allAttributes.get(clazz); for(ConfiguredObjectAttribute attr : attrCol) { attrMap.put(attr.getName(), attr); if(attr.isAutomated()) { fieldMap.put(attr.getName(), findField(attr, clazz)); } } _allAttributeTypes.put(clazz, attrMap); _allAutomatedFields.put(clazz, fieldMap); } private void processDefaultContext(final Class clazz) { for(Field field : clazz.getDeclaredFields()) { if(Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && field.isAnnotationPresent(ManagedContextDefault.class)) { try { String name = field.getAnnotation(ManagedContextDefault.class).name(); Object value = field.get(null); if(!_defaultContext.containsKey(name)) { _defaultContext.put(name,String.valueOf(value)); } 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 clazz) { Map> map = new HashMap<>(); _stateChangeMethods.put(clazz, map); addStateTransitions(clazz, map); for(Class parent : clazz.getInterfaces()) { if(ConfiguredObject.class.isAssignableFrom(parent)) { inheritTransitions((Class) parent, map); } } Class superclass = clazz.getSuperclass(); if(superclass != null && ConfiguredObject.class.isAssignableFrom(superclass)) { inheritTransitions((Class) superclass, map); } } private void inheritTransitions(final Class parent, final Map> map) { Map> parentMap = _stateChangeMethods.get(parent); for(Map.Entry> parentEntry : parentMap.entrySet()) { if(map.containsKey(parentEntry.getKey())) { Map methodMap = map.get(parentEntry.getKey()); for(Map.Entry methodEntry : parentEntry.getValue().entrySet()) { if(!methodMap.containsKey(methodEntry.getKey())) { methodMap.put(methodEntry.getKey(), methodEntry.getValue()); } } } else { map.put(parentEntry.getKey(), new HashMap(parentEntry.getValue())); } } } private void addStateTransitions(final Class clazz, final Map> map) { for(Method m : clazz.getDeclaredMethods()) { if(m.isAnnotationPresent(StateTransition.class)) { 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."); } } } } private void addStateTransition(final State fromState, final State toState, final Method method, final Map> map) { if(map.containsKey(fromState)) { Map toMap = map.get(fromState); if(!toMap.containsKey(toState)) { toMap.put(toState,method); } } else { HashMap 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 Collection getAttributeNames(Class clazz) { final Collection> attrs = getAttributes(clazz); return new AbstractCollection() { @Override public Iterator iterator() { final Iterator> underlyingIterator = attrs.iterator(); return new Iterator() { @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 Collection> getAttributes(final Class clazz) { if(!_allAttributes.containsKey(clazz)) { process(clazz); } final Collection> attributes = (Collection) _allAttributes.get(clazz); return attributes; } protected Collection getStatistics(final Class clazz) { if(!_allAttributes.containsKey(clazz)) { process(clazz); } final Collection statistics = (Collection) _allStatistics.get(clazz); return statistics; } public Map> getAttributeTypes(final Class clazz) { if(!_allAttributes.containsKey(clazz)) { process(clazz); } return _allAttributeTypes.get(clazz); } Map getAutomatedFields(Class clazz) { if(!_allAttributes.containsKey(clazz)) { process(clazz); } return _allAutomatedFields.get(clazz); } Map> getStateChangeMethods(final Class objectClass) { if(!_allAttributes.containsKey(objectClass)) { process(objectClass); } Map> map = _stateChangeMethods.get(objectClass); return map != null ? Collections.unmodifiableMap(map) : Collections.>emptyMap(); } }