diff options
Diffstat (limited to 'qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AbstractConfiguredObject.java')
-rw-r--r-- | qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AbstractConfiguredObject.java | 1892 |
1 files changed, 1892 insertions, 0 deletions
diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AbstractConfiguredObject.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AbstractConfiguredObject.java new file mode 100644 index 0000000000..a31a4f0ab7 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AbstractConfiguredObject.java @@ -0,0 +1,1892 @@ +/* + * + * 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 org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.updater.ChangeAttributesTask; +import org.apache.qpid.server.configuration.updater.ChangeStateTask; +import org.apache.qpid.server.configuration.updater.CreateChildTask; +import org.apache.qpid.server.configuration.updater.SetAttributeTask; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; +import org.apache.qpid.server.store.ConfiguredObjectRecord; +import org.apache.qpid.server.util.ServerScopedRuntimeException; + +import javax.security.auth.Subject; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.AccessControlException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.*; + +public abstract class AbstractConfiguredObject<X extends ConfiguredObject<X>> implements ConfiguredObject<X> +{ + private static final String ID = "id"; + + private static final Map<Class<? extends ConfiguredObject>, Collection<Attribute<?,?>>> _allAttributes = + Collections.synchronizedMap(new HashMap<Class<? extends ConfiguredObject>, Collection<Attribute<?, ?>>>()); + + private static final Map<Class<? extends ConfiguredObject>, Collection<Statistic<?,?>>> _allStatistics = + Collections.synchronizedMap(new HashMap<Class<? extends ConfiguredObject>, Collection<Statistic<?, ?>>>()); + + private static final Map<Class<? extends ConfiguredObject>, Map<String, Attribute<?,?>>> _allAttributeTypes = + Collections.synchronizedMap(new HashMap<Class<? extends ConfiguredObject>, Map<String, Attribute<?, ?>>>()); + + private static final Map<Class<? extends ConfiguredObject>, Map<String, Field>> _allAutomatedFields = + Collections.synchronizedMap(new HashMap<Class<? extends ConfiguredObject>, Map<String, Field>>()); + private static final Map<Class, Object> SECURE_VALUES; + static + { + Map<Class,Object> secureValues = new HashMap<Class, Object>(); + secureValues.put(String.class, "********"); + secureValues.put(Integer.class, 0); + secureValues.put(Long.class, 0l); + secureValues.put(Byte.class, (byte)0); + secureValues.put(Short.class, (short)0); + secureValues.put(Double.class, (double)0); + secureValues.put(Float.class, (float)0); + + SECURE_VALUES = Collections.unmodifiableMap(secureValues); + } + + + private final Map<String,Object> _attributes = new HashMap<String, Object>(); + private final Map<Class<? extends ConfiguredObject>, ConfiguredObject> _parents = + new HashMap<Class<? extends ConfiguredObject>, ConfiguredObject>(); + private final Collection<ConfigurationChangeListener> _changeListeners = + new ArrayList<ConfigurationChangeListener>(); + + @ManagedAttributeField + private final UUID _id; + + private final Map<String, Object> _defaultAttributes = new HashMap<String, Object>(); + private final TaskExecutor _taskExecutor; + + @ManagedAttributeField + private long _createdTime; + + @ManagedAttributeField + private String _createdBy; + + @ManagedAttributeField + private long _lastUpdatedTime; + + @ManagedAttributeField + private String _lastUpdatedBy; + + @ManagedAttributeField + private String _name; + + private final Map<String, Attribute<?,?>> _attributeTypes; + private final Map<String, Field> _automatedFields; + + protected AbstractConfiguredObject(UUID id, + Map<String, Object> defaults, + Map<String, Object> attributes, + TaskExecutor taskExecutor) + { + this(defaults, combineIdWithAttributes(id,attributes), taskExecutor); + } + + public static Map<String,Object> combineIdWithAttributes(UUID id, Map<String,Object> attributes) + { + Map<String,Object> combined = new HashMap<String, Object>(attributes); + combined.put(ID, id); + return combined; + } + + + protected AbstractConfiguredObject(UUID id, Map<String, Object> defaults, Map<String, Object> attributes, + TaskExecutor taskExecutor, boolean filterAttributes) + + { + this(Collections.<Class<? extends ConfiguredObject>, ConfiguredObject<?>>emptyMap(), + defaults, combineIdWithAttributes(id, attributes), taskExecutor, filterAttributes); + } + + protected AbstractConfiguredObject(Map<String, Object> defaults, + Map<String, Object> attributes, + TaskExecutor taskExecutor) + { + this(Collections.<Class<? extends ConfiguredObject>, ConfiguredObject<?>>emptyMap(), + defaults, attributes, taskExecutor, true); + } + + protected AbstractConfiguredObject(final Map<Class<? extends ConfiguredObject>, ConfiguredObject<?>> parents, + Map<String, Object> defaults, + Map<String, Object> attributes, + TaskExecutor taskExecutor) + { + this(parents, defaults, attributes, taskExecutor, true); + } + + protected AbstractConfiguredObject(final Map<Class<? extends ConfiguredObject>, ConfiguredObject<?>> parents, + Map<String, Object> defaults, + Map<String, Object> attributes, + TaskExecutor taskExecutor, + boolean filterAttributes) + { + _taskExecutor = taskExecutor; + final UUID uuid = (UUID) attributes.get(ID); + _id = uuid == null ? UUID.randomUUID() : uuid; + _attributeTypes = getAttributeTypes(getClass()); + _automatedFields = getAutomatedFields(getClass()); + + + for(Map.Entry<Class<? extends ConfiguredObject>, ConfiguredObject<?>> entry : parents.entrySet()) + { + addParent((Class<ConfiguredObject>) entry.getKey(), entry.getValue()); + } + + Collection<String> names = getAttributeNames(); + if(names!=null) + { + if(filterAttributes) + { + for (String name : names) + { + if (attributes.containsKey(name)) + { + final Object value = attributes.get(name); + if(value != null) + { + _attributes.put(name, value); + } + if(_automatedFields.containsKey(name)) + { + automatedSetValue(name, value); + } + } + } + } + else + { + for(Map.Entry<String, Object> entry : attributes.entrySet()) + { + if(entry.getValue()!=null) + { + _attributes.put(entry.getKey(),entry.getValue()); + if(_automatedFields.containsKey(entry.getKey())) + { + automatedSetValue(entry.getKey(), entry.getValue()); + } + } + } + } + + } + + if (defaults != null) + { + _defaultAttributes.putAll(defaults); + for(Map.Entry<String,Object> defaultedEntry : defaults.entrySet()) + { + if(_automatedFields.containsKey(defaultedEntry.getKey()) && !attributes.containsKey(defaultedEntry.getKey())) + { + automatedSetValue(defaultedEntry.getKey(),defaultedEntry.getValue()); + } + } + } + if(!_attributes.containsKey(CREATED_BY)) + { + final AuthenticatedPrincipal currentUser = SecurityManager.getCurrentUser(); + if(currentUser != null) + { + _attributes.put(CREATED_BY, currentUser.getName()); + } + } + if(!_attributes.containsKey(CREATED_TIME)) + { + _attributes.put(CREATED_TIME, System.currentTimeMillis()); + } + for(Attribute<?,?> attr : _attributeTypes.values()) + { + if(attr.getAnnotation().mandatory() && !(attributes.containsKey(attr.getName())|| defaults.containsKey(attr.getName()))) + { + throw new IllegalArgumentException("Mandatory attribute " + attr.getName() + " not supplied for instance of " + getClass().getName()); + } + } + + for(ConfiguredObject<?> parent : parents.values()) + { + if(parent instanceof AbstractConfiguredObject<?>) + { + ((AbstractConfiguredObject<?>)parent).instantiateChild(this); + } + } + + } + + private void automatedSetValue(final String name, final Object value) + { + try + { + final Attribute attribute = _attributeTypes.get(name); + _automatedFields.get(name).set(this, attribute.convert(value, this)); + } + catch (IllegalAccessException e) + { + throw new ServerScopedRuntimeException("Unable to set the automated attribute " + name + " on the configure object type " + getClass().getName(),e); + } + } + + protected AbstractConfiguredObject(UUID id, TaskExecutor taskExecutor) + { + this(id, Collections.<String,Object>emptyMap(), Collections.<String,Object>emptyMap(), taskExecutor); + } + + public final UUID getId() + { + return _id; + } + + public final String getName() + { + return _name; + } + + public Class<? extends ConfiguredObject> getCategoryClass() + { + return getCategory(getClass()); + } + + public State getDesiredState() + { + return null; //TODO + } + + @Override + public final State setDesiredState(final State currentState, final State desiredState) + throws IllegalStateTransitionException, AccessControlException + { + if (_taskExecutor.isTaskExecutorThread()) + { + authoriseSetDesiredState(currentState, desiredState); + if (setState(currentState, desiredState)) + { + notifyStateChanged(currentState, desiredState); + return desiredState; + } + else + { + return getState(); + } + } + else + { + return _taskExecutor.submitAndWait(new ChangeStateTask(this, currentState, desiredState)); + } + } + + /** + * @return true when the state has been successfully updated to desiredState or false otherwise + */ + protected abstract boolean setState(State currentState, State desiredState); + + protected void notifyStateChanged(final State currentState, final State desiredState) + { + synchronized (_changeListeners) + { + List<ConfigurationChangeListener> copy = new ArrayList<ConfigurationChangeListener>(_changeListeners); + for(ConfigurationChangeListener listener : copy) + { + listener.stateChanged(this, currentState, desiredState); + } + } + } + + public void addChangeListener(final ConfigurationChangeListener listener) + { + if(listener == null) + { + throw new NullPointerException("Cannot add a null listener"); + } + synchronized (_changeListeners) + { + if(!_changeListeners.contains(listener)) + { + _changeListeners.add(listener); + } + } + } + + public boolean removeChangeListener(final ConfigurationChangeListener listener) + { + if(listener == null) + { + throw new NullPointerException("Cannot remove a null listener"); + } + synchronized (_changeListeners) + { + return _changeListeners.remove(listener); + } + } + + protected void childAdded(ConfiguredObject child) + { + synchronized (_changeListeners) + { + List<ConfigurationChangeListener> copy = new ArrayList<ConfigurationChangeListener>(_changeListeners); + for(ConfigurationChangeListener listener : copy) + { + listener.childAdded(this, child); + } + } + } + + protected void childRemoved(ConfiguredObject child) + { + synchronized (_changeListeners) + { + List<ConfigurationChangeListener> copy = new ArrayList<ConfigurationChangeListener>(_changeListeners); + for(ConfigurationChangeListener listener : copy) + { + listener.childRemoved(this, child); + } + } + } + + protected void attributeSet(String attributeName, Object oldAttributeValue, Object newAttributeValue) + { + + final AuthenticatedPrincipal currentUser = SecurityManager.getCurrentUser(); + if(currentUser != null) + { + _attributes.put(LAST_UPDATED_BY, currentUser.getName()); + _lastUpdatedBy = currentUser.getName(); + } + final long currentTime = System.currentTimeMillis(); + _attributes.put(LAST_UPDATED_TIME, currentTime); + _lastUpdatedTime = currentTime; + + synchronized (_changeListeners) + { + List<ConfigurationChangeListener> copy = new ArrayList<ConfigurationChangeListener>(_changeListeners); + for(ConfigurationChangeListener listener : copy) + { + listener.attributeSet(this, attributeName, oldAttributeValue, newAttributeValue); + } + } + } + + private final Object getDefaultAttribute(String name) + { + return _defaultAttributes.get(name); + } + + @Override + public Object getAttribute(String name) + { + Attribute<X,?> attr = (Attribute<X, ?>) _attributeTypes.get(name); + if(attr != null && attr.getAnnotation().automate()) + { + Object value = attr.getValue((X)this); + if(value != null && attr.getAnnotation().secure() && + !SecurityManager.isSystemProcess()) + { + return SECURE_VALUES.get(value.getClass()); + } + else + { + return value; + } + } + else + { + Object value = getActualAttribute(name); + if (value == null) + { + value = getDefaultAttribute(name); + } + return value; + } + } + + protected void create() + { + } + + protected <T extends ConfiguredObject<?>> Object getAttribute(String name, T parent, String parentAttributeName) + { + Object value = getActualAttribute(name); + if (value != null ) + { + return value; + } + if (parent != null) + { + value = parent.getAttribute(parentAttributeName); + if (value != null) + { + return value; + } + } + return getDefaultAttribute(name); + } + + + @Override + public String getDescription() + { + return (String) getAttribute(DESCRIPTION); + } + + @Override + public <T> T getAttribute(final Attribute<? super X, T> attr) + { + return (T) getAttribute(attr.getName()); + } + + @Override + public final Map<String, Object> getActualAttributes() + { + synchronized (_attributes) + { + return new HashMap<String, Object>(_attributes); + } + } + + private Object getActualAttribute(final String name) + { + if(CREATED_BY.equals(name)) + { + return getCreatedBy(); + } + else if(CREATED_TIME.equals(name)) + { + return getCreatedTime(); + } + else + { + synchronized (_attributes) + { + return _attributes.get(name); + } + } + } + + public Object setAttribute(final String name, final Object expected, final Object desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + if (_taskExecutor.isTaskExecutorThread()) + { + authoriseSetAttribute(name, expected, desired); + if (changeAttribute(name, expected, desired)) + { + attributeSet(name, expected, desired); + return desired; + } + else + { + return getAttribute(name); + } + } + else + { + return _taskExecutor.submitAndWait(new SetAttributeTask(this, name, expected, desired)); + } + } + + protected boolean changeAttribute(final String name, final Object expected, final Object desired) + { + synchronized (_attributes) + { + Object currentValue = getAttribute(name); + if((currentValue == null && expected == null) + || (currentValue != null && currentValue.equals(expected))) + { + //TODO: don't put nulls + _attributes.put(name, desired); + Attribute<?,?> attr = _attributeTypes.get(name); + if(attr != null && attr.getAnnotation().automate()) + { + if(desired == null && _defaultAttributes.containsKey(name)) + { + automatedSetValue(name, _defaultAttributes.get(name)); + } + else + { + automatedSetValue(name, desired); + } + } + return true; + } + else + { + return false; + } + } + } + + public <T extends ConfiguredObject> T getParent(final Class<T> clazz) + { + synchronized (_parents) + { + return (T) _parents.get(clazz); + } + } + + private <T extends ConfiguredObject> void addParent(Class<T> clazz, T parent) + { + synchronized (_parents) + { + _parents.put(clazz, parent); + } + + } + + protected <T extends ConfiguredObject> void removeParent(Class<T> clazz) + { + synchronized (this) + { + _parents.remove(clazz); + } + } + + public Collection<String> getAttributeNames() + { + synchronized(_attributes) + { + return new ArrayList<String>(_attributes.keySet()); + } + } + + @Override + public String toString() + { + return getClass().getSimpleName() + " [id=" + _id + ", name=" + getName() + "]"; + } + + public ConfiguredObjectRecord asObjectRecord() + { + return new ConfiguredObjectRecord() + { + @Override + public UUID getId() + { + return AbstractConfiguredObject.this.getId(); + } + + @Override + public String getType() + { + return getCategoryClass().getSimpleName(); + } + + @Override + public Map<String, Object> getAttributes() + { + return Subject.doAs(SecurityManager.getSubjectWithAddedSystemRights(), new PrivilegedAction<Map<String, Object>>() + { + @Override + public Map<String, Object> run() + { + Map<String,Object> actualAttributes = new HashMap<String, Object>(getActualAttributes()); + for(Map.Entry<String,Object> entry : actualAttributes.entrySet()) + { + if(entry.getValue() instanceof ConfiguredObject) + { + entry.setValue(((ConfiguredObject)entry.getValue()).getId()); + } + } + actualAttributes.remove(ID); + return actualAttributes; + } + }); + } + + @Override + public Map<String, ConfiguredObjectRecord> getParents() + { + Map<String, ConfiguredObjectRecord> parents = new LinkedHashMap<String, ConfiguredObjectRecord>(); + for(Class<? extends ConfiguredObject> parentClass : Model.getInstance().getParentTypes(getCategoryClass())) + { + ConfiguredObject parent = getParent(parentClass); + if(parent != null) + { + parents.put(parentClass.getSimpleName(), parent.asObjectRecord()); + } + } + return parents; + } + + @Override + public String toString() + { + return getClass().getSimpleName() + "[name=" + getName() + ", categoryClass=" + getCategoryClass() + ", type=" + + getType() + ", id=" + getId() + "]"; + } + }; + } + + @SuppressWarnings("unchecked") + @Override + public <C extends ConfiguredObject> C createChild(Class<C> childClass, Map<String, Object> attributes, ConfiguredObject... otherParents) + { + if (_taskExecutor.isTaskExecutorThread()) + { + authoriseCreateChild(childClass, attributes, otherParents); + C child = addChild(childClass, attributes, otherParents); + if (child != null) + { + childAdded(child); + } + return child; + } + else + { + return (C)_taskExecutor.submitAndWait(new CreateChildTask(this, childClass, attributes, otherParents)); + } + } + + protected <C extends ConfiguredObject> C addChild(Class<C> childClass, Map<String, Object> attributes, ConfiguredObject... otherParents) + { + throw new UnsupportedOperationException(); + } + + protected <C extends ConfiguredObject> void instantiateChild(final C child) + { + + Class<? extends ConfiguredObject> childCategory = child.getCategoryClass(); + if(!Model.getInstance().getChildTypes(getCategoryClass()).contains(childCategory)) + { + throw new IllegalArgumentException("Cannot instantiate a child of category " + childCategory.getSimpleName() + + " to a parent of category " + getCategoryClass().getSimpleName()); + } + + try + { + final String methodName = "instantiate" + childCategory.getSimpleName(); + Method recoveryMethod = getClass().getMethod(methodName, childCategory); + recoveryMethod.setAccessible(true); + recoveryMethod.invoke(this, child); + } + catch (NoSuchMethodException e) + { + throw new IllegalArgumentException("Cannot instantiate a child of category " + childCategory.getSimpleName() + + " to a parent of category " + getCategoryClass().getSimpleName() + + ". No instatiation method defined. "); + } + catch (InvocationTargetException e) + { + throw new IllegalArgumentException("Error recovering child", e.getTargetException()); + } + catch (IllegalAccessException e) + { + throw new ServerScopedRuntimeException("Error recovering child, method for recovery cannot be called", e); + } + } + + public TaskExecutor getTaskExecutor() + { + return _taskExecutor; + } + + @Override + public void setAttributes(final Map<String, Object> attributes) throws IllegalStateException, AccessControlException, IllegalArgumentException + { + if (getTaskExecutor().isTaskExecutorThread()) + { + authoriseSetAttributes(attributes); + changeAttributes(attributes); + } + else + { + getTaskExecutor().submitAndWait(new ChangeAttributesTask(this, attributes)); + } + } + + protected void changeAttributes(final Map<String, Object> attributes) + { + validateChangeAttributes(attributes); + Collection<String> names = getAttributeNames(); + for (String name : names) + { + if (attributes.containsKey(name)) + { + Object desired = attributes.get(name); + Object expected = getAttribute(name); + if (changeAttribute(name, expected, desired)) + { + attributeSet(name, expected, desired); + } + } + } + } + + protected void validateChangeAttributes(final Map<String, Object> attributes) + { + if (attributes.containsKey(ID)) + { + UUID id = getId(); + Object idAttributeValue = attributes.get(ID); + if (idAttributeValue != null && !(idAttributeValue.equals(id) || idAttributeValue.equals(id.toString()))) + { + throw new IllegalConfigurationException("Cannot change existing configured object id"); + } + } + } + + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + // allowed by default + } + + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + // allowed by default + } + + protected <C extends ConfiguredObject> void authoriseCreateChild(Class<C> childClass, Map<String, Object> attributes, ConfiguredObject... otherParents) throws AccessControlException + { + // allowed by default + } + + protected void authoriseSetAttributes(Map<String, Object> attributes) throws AccessControlException + { + // allowed by default + } + + protected Map<String, Object> getDefaultAttributes() + { + return _defaultAttributes; + } + + /** + * Returns a map of effective attribute values that would result + * if applying the supplied changes. Does not apply the changes. + */ + protected Map<String, Object> generateEffectiveAttributes(Map<String,Object> changedValues) + { + //Build a new set of effective attributes that would be + //the result of applying the attribute changes, so we + //can validate the configuration that would result + + Map<String, Object> defaultValues = getDefaultAttributes(); + Map<String, Object> existingActualValues = getActualAttributes(); + + //create a new merged map, starting with the defaults + Map<String, Object> merged = new HashMap<String, Object>(defaultValues); + + for(String name : getAttributeNames()) + { + if(changedValues.containsKey(name)) + { + Object changedValue = changedValues.get(name); + if(changedValue != null) + { + //use the new non-null value for the merged values + merged.put(name, changedValue); + } + else + { + //we just use the default (if there was one) since the changed + //value is null and effectively clears any existing actual value + } + } + else if(existingActualValues.get(name) != null) + { + //Use existing non-null actual value for the merge + merged.put(name, existingActualValues.get(name)); + } + else + { + //There was neither a change or an existing non-null actual + //value, so just use the default value (if there was one). + } + } + + return merged; + } + + @Override + public String getLastUpdatedBy() + { + return _lastUpdatedBy; + } + + @Override + public long getLastUpdatedTime() + { + return _lastUpdatedTime; + } + + @Override + public String getCreatedBy() + { + return _createdBy; + } + + protected String getCurrentUserName() + { + Subject currentSubject = Subject.getSubject(AccessController.getContext()); + Set<AuthenticatedPrincipal> principals = + currentSubject == null ? null : currentSubject.getPrincipals(AuthenticatedPrincipal.class); + if(principals == null || principals.isEmpty()) + { + return null; + } + else + { + return principals.iterator().next().getName(); + } + } + + @Override + public long getCreatedTime() + { + return _createdTime; + } + + @Override + public String getType() + { + return (String)getAttribute(TYPE); + } + + + @Override + public Map<String,Number> getStatistics() + { + Collection<Statistic> stats = getStatistics(getClass()); + Map<String,Number> map = new HashMap<String,Number>(); + for(Statistic stat : stats) + { + map.put(stat.getName(), (Number) stat.getValue(this)); + } + return map; + } + + + public <Y extends ConfiguredObject<Y>> Y findConfiguredObject(Class<Y> clazz, String name) + { + Collection<Y> reachable = getReachableObjects(this,clazz); + for(Y candidate : reachable) + { + if(candidate.getName().equals(name)) + { + return candidate; + } + } + return null; + } + + //========================================================================================= + + private static abstract class AttributeOrStatistic<C extends ConfiguredObject, T> + { + + protected final String _name; + protected final Class<T> _type; + protected final Converter<T> _converter; + protected final Method _getter; + + private AttributeOrStatistic( + String name, final Method getter, Class<T> type) + { + _name = name; + _getter = getter; + _type = type; + _converter = getConverter(type); + + } + + public String getName() + { + return _name; + } + + public Class<T> getType() + { + return _type; + } + + public T getValue(C configuredObject) + { + try + { + return (T) _getter.invoke(configuredObject); + } + catch (IllegalAccessException e) + { + Object o = configuredObject.getAttribute(_name); + return _converter.convert(o, configuredObject); + } + catch (InvocationTargetException e) + { + Object o = configuredObject.getAttribute(_name); + return _converter.convert(o, configuredObject); + } + + } + + public Method getGetter() + { + return _getter; + } + } + + private static final class Statistic<C extends ConfiguredObject, T extends Number> extends AttributeOrStatistic<C,T> + { + private Statistic(Class<C> clazz, String name, Class<T> type, final Method getter) + { + super(name, getter, type); + addToStatisticsSet(clazz, this); + } + } + + public static final class Attribute<C extends ConfiguredObject, T> extends AttributeOrStatistic<C,T> + { + + private final ManagedAttribute _annotation; + + private Attribute(Class<C> clazz, + String name, + Class<T> type, + final Method getter, + final ManagedAttribute annotation) + { + super(name, getter, type); + _annotation = annotation; + addToAttributesSet(clazz, this); + } + + public ManagedAttribute getAnnotation() + { + return _annotation; + } + + public T convert(final Object value, C object) + { + return _converter.convert(value, object); + } + } + + + private static interface Converter<T> + { + T convert(Object value, final ConfiguredObject object); + } + + private static final Converter<String> STRING_CONVERTER = new Converter<String>() + { + @Override + public String convert(final Object value, final ConfiguredObject object) + { + return value == null ? null : value.toString(); + } + }; + + private static final Converter<UUID> UUID_CONVERTER = new Converter<UUID>() + { + @Override + public UUID convert(final Object value, final ConfiguredObject object) + { + if(value instanceof UUID) + { + return (UUID) value; + } + else if(value instanceof String) + { + return UUID.fromString((String) value); + } + else if(value == null) + { + return null; + } + else + { + throw new IllegalArgumentException("Cannot convert type " + value.getClass() + " to a UUID"); + } + } + }; + + private static final Converter<Long> LONG_CONVERTER = new Converter<Long>() + { + + @Override + public Long convert(final Object value, final ConfiguredObject object) + { + if(value instanceof Long) + { + return (Long) value; + } + else if(value instanceof Number) + { + return ((Number) value).longValue(); + } + else if(value instanceof String) + { + return Long.valueOf((String) value); + } + else if(value == null) + { + return null; + } + else + { + throw new IllegalArgumentException("Cannot convert type " + value.getClass() + " to a Long"); + } + } + }; + + private static final Converter<Integer> INT_CONVERTER = new Converter<Integer>() + { + + @Override + public Integer convert(final Object value, final ConfiguredObject object) + { + if(value instanceof Integer) + { + return (Integer) value; + } + else if(value instanceof Number) + { + return ((Number) value).intValue(); + } + else if(value instanceof String) + { + return Integer.valueOf((String) value); + } + else if(value == null) + { + return null; + } + else + { + throw new IllegalArgumentException("Cannot convert type " + value.getClass() + " to an Integer"); + } + } + }; + + + private static final Converter<Short> SHORT_CONVERTER = new Converter<Short>() + { + + @Override + public Short convert(final Object value, final ConfiguredObject object) + { + if(value instanceof Short) + { + return (Short) value; + } + else if(value instanceof Number) + { + return ((Number) value).shortValue(); + } + else if(value instanceof String) + { + return Short.valueOf((String) value); + } + else if(value == null) + { + return null; + } + else + { + throw new IllegalArgumentException("Cannot convert type " + value.getClass() + " to a Short"); + } + } + }; + + private static final Converter<Boolean> BOOLEAN_CONVERTER = new Converter<Boolean>() + { + + @Override + public Boolean convert(final Object value, final ConfiguredObject object) + { + if(value instanceof Boolean) + { + return (Boolean) value; + } + else if(value instanceof String) + { + return Boolean.valueOf((String) value); + } + else if(value == null) + { + return null; + } + else + { + throw new IllegalArgumentException("Cannot convert type " + value.getClass() + " to a Boolean"); + } + } + }; + + private static final Converter<List> LIST_CONVERTER = new Converter<List>() + { + @Override + public List convert(final Object value, final ConfiguredObject object) + { + if(value instanceof List) + { + return (List) value; + } + else if(value == null) + { + return null; + } + else + { + throw new IllegalArgumentException("Cannot convert type " + value.getClass() + " to a List"); + } + } + }; + + private static final Converter<Collection> COLLECTION_CONVERTER = new Converter<Collection>() + { + @Override + public Collection convert(final Object value, final ConfiguredObject object) + { + if(value instanceof Collection) + { + return (Collection) value; + } + else if(value == null) + { + return null; + } + else + { + throw new IllegalArgumentException("Cannot convert type " + value.getClass() + " to a List"); + } + } + }; + + private static final Converter<Map> MAP_CONVERTER = new Converter<Map>() + { + @Override + public Map convert(final Object value, final ConfiguredObject object) + { + if(value instanceof Map) + { + return (Map) value; + } + else if(value == null) + { + return null; + } + else + { + throw new IllegalArgumentException("Cannot convert type " + value.getClass() + " to a Map"); + } + } + }; + + private static final class EnumConverter<X extends Enum<X>> implements Converter<X> + { + private final Class<X> _klazz; + + private EnumConverter(final Class<X> klazz) + { + _klazz = klazz; + } + + @Override + public X convert(final Object value, final ConfiguredObject object) + { + if(value == null) + { + return null; + } + else if(_klazz.isInstance(value)) + { + return (X) value; + } + else if(value instanceof String) + { + return Enum.valueOf(_klazz,(String) value); + } + else + { + throw new IllegalArgumentException("Cannot convert type " + value.getClass() + " to a " + _klazz.getName()); + } + } + } + + + private static final class ConfiguredObjectConverter<X extends ConfiguredObject<X>> implements Converter<X> + { + private final Class<X> _klazz; + + private ConfiguredObjectConverter(final Class<X> klazz) + { + _klazz = klazz; + } + + @Override + public X convert(final Object value, final ConfiguredObject object) + { + if(value == null) + { + return null; + } + else if(_klazz.isInstance(value)) + { + return (X) value; + } + else if(value instanceof UUID) + { + Collection<X> reachable = getReachableObjects(object,_klazz); + for(X candidate : reachable) + { + if(candidate.getId().equals(value)) + { + return candidate; + } + } + throw new IllegalArgumentException("Cannot find a " + _klazz.getName() + " with id " + value); + } + else if(value instanceof String) + { + Collection<X> reachable = getReachableObjects(object,_klazz); + for(X candidate : reachable) + { + if(candidate.getName().equals(value)) + { + return candidate; + } + } + try + { + UUID id = UUID.fromString((String)value); + return convert(id, object); + } + catch (IllegalArgumentException e) + { + throw new IllegalArgumentException("Cannot find a " + _klazz.getSimpleName() + " with name '" + value + "'"); + } + } + else + { + throw new IllegalArgumentException("Cannot convert type " + value.getClass() + " to a " + _klazz.getName()); + } + } + + } + + private static <X> Converter<X> getConverter(final Class<X> type) + { + if(type == String.class) + { + return (Converter<X>) STRING_CONVERTER; + } + else if(type == Integer.class) + { + return (Converter<X>) INT_CONVERTER; + } + else if(type == Short.class) + { + return (Converter<X>) SHORT_CONVERTER; + } + else if(type == Long.class) + { + return (Converter<X>) LONG_CONVERTER; + } + else if(type == Boolean.class) + { + return (Converter<X>) BOOLEAN_CONVERTER; + } + else if(type == UUID.class) + { + return (Converter<X>) UUID_CONVERTER; + } + else if(Enum.class.isAssignableFrom(type)) + { + return (Converter<X>) new EnumConverter((Class<? extends Enum>)type); + } + else if(List.class.isAssignableFrom(type)) + { + return (Converter<X>) LIST_CONVERTER; + } + else if(Map.class.isAssignableFrom(type)) + { + return (Converter<X>) MAP_CONVERTER; + } + else if(Collection.class.isAssignableFrom(type)) + { + return (Converter<X>) COLLECTION_CONVERTER; + } + else if(ConfiguredObject.class.isAssignableFrom(type)) + { + return (Converter<X>) new ConfiguredObjectConverter(type); + } + throw new IllegalArgumentException("Cannot create attributes of type " + type.getName()); + } + + private static void addToAttributesSet(final Class<? extends ConfiguredObject> clazz, final Attribute<?, ?> attribute) + { + synchronized (_allAttributes) + { + Collection<Attribute<?,?>> classAttributes = _allAttributes.get(clazz); + if(classAttributes == null) + { + classAttributes = new ArrayList<Attribute<?, ?>>(); + for(Map.Entry<Class<? extends ConfiguredObject>, Collection<Attribute<?,?>>> entry : _allAttributes.entrySet()) + { + if(entry.getKey().isAssignableFrom(clazz)) + { + classAttributes.addAll(entry.getValue()); + } + } + _allAttributes.put(clazz, classAttributes); + + } + for(Map.Entry<Class<? extends ConfiguredObject>, Collection<Attribute<?,?>>> entry : _allAttributes.entrySet()) + { + if(clazz.isAssignableFrom(entry.getKey())) + { + entry.getValue().add(attribute); + } + } + + } + } + private static void addToStatisticsSet(final Class<? extends ConfiguredObject> clazz, final Statistic<?, ?> statistic) + { + synchronized (_allStatistics) + { + Collection<Statistic<?,?>> classAttributes = _allStatistics.get(clazz); + if(classAttributes == null) + { + classAttributes = new ArrayList<Statistic<?, ?>>(); + for(Map.Entry<Class<? extends ConfiguredObject>, Collection<Statistic<?,?>>> entry : _allStatistics.entrySet()) + { + if(entry.getKey().isAssignableFrom(clazz)) + { + classAttributes.addAll(entry.getValue()); + } + } + _allStatistics.put(clazz, classAttributes); + + } + for(Map.Entry<Class<? extends ConfiguredObject>, Collection<Statistic<?,?>>> entry : _allStatistics.entrySet()) + { + if(clazz.isAssignableFrom(entry.getKey())) + { + entry.getValue().add(statistic); + } + } + + } + } + + + private static <X extends ConfiguredObject> void processAttributes(final Class<X> clazz) + { + synchronized (_allAttributes) + { + if(_allAttributes.containsKey(clazz)) + { + return; + } + + + for(Class<?> parent : clazz.getInterfaces()) + { + if(ConfiguredObject.class.isAssignableFrom(parent)) + { + processAttributes((Class<? extends ConfiguredObject>)parent); + } + } + final Class<? super X> superclass = clazz.getSuperclass(); + if(superclass != null && ConfiguredObject.class.isAssignableFrom(superclass)) + { + processAttributes((Class<? extends ConfiguredObject>) superclass); + } + + final ArrayList<Attribute<?, ?>> attributeList = new ArrayList<Attribute<?, ?>>(); + final ArrayList<Statistic<?, ?>> statisticList = new ArrayList<Statistic<?, ?>>(); + + _allAttributes.put(clazz, attributeList); + _allStatistics.put(clazz, statisticList); + + for(Class<?> parent : clazz.getInterfaces()) + { + if(ConfiguredObject.class.isAssignableFrom(parent)) + { + Collection<Attribute<?, ?>> attrs = _allAttributes.get(parent); + for(Attribute<?,?> attr : attrs) + { + if(!attributeList.contains(attr)) + { + attributeList.add(attr); + } + } + Collection<Statistic<?, ?>> stats = _allStatistics.get(parent); + for(Statistic<?,?> stat : stats) + { + if(!statisticList.contains(stat)) + { + statisticList.add(stat); + } + } + } + } + if(superclass != null && ConfiguredObject.class.isAssignableFrom(superclass)) + { + Collection<Attribute<?, ?>> attrs = _allAttributes.get(superclass); + Collection<Statistic<?, ?>> stats = _allStatistics.get(superclass); + for(Attribute<?,?> attr : attrs) + { + if(!attributeList.contains(attr)) + { + attributeList.add(attr); + } + } + for(Statistic<?,?> stat : stats) + { + if(!statisticList.contains(stat)) + { + statisticList.add(stat); + } + } + } + + + for(Method m : clazz.getDeclaredMethods()) + { + ManagedAttribute annotation = m.getAnnotation(ManagedAttribute.class); + if(annotation != null) + { + if(m.getParameterTypes().length != 0) + { + throw new IllegalArgumentException("ManagedAttribute annotation should only be added to no-arg getters"); + } + Class<?> type = getType(m); + String name = getName(m, type); + Attribute<X,?> newAttr = new Attribute(clazz,name,type,m, annotation); + + } + else + { + ManagedStatistic statAnnotation = m.getAnnotation(ManagedStatistic.class); + if(statAnnotation != null) + { + if(m.getParameterTypes().length != 0) + { + throw new IllegalArgumentException("ManagedStatistic annotation should only be added to no-arg getters"); + } + Class<?> type = getType(m); + if(!Number.class.isAssignableFrom(type)) + { + throw new IllegalArgumentException("ManagedStatistic annotation should only be added to getters returning a Number type"); + } + String name = getName(m, type); + Statistic<X,?> newStat = new Statistic(clazz,name,type,m); + } + } + } + + Map<String,Attribute<?,?>> attrMap = new HashMap<String, Attribute<?, ?>>(); + Map<String,Field> fieldMap = new HashMap<String, Field>(); + + + Collection<Attribute<?, ?>> attrCol = _allAttributes.get(clazz); + for(Attribute<?,?> attr : attrCol) + { + attrMap.put(attr.getName(), attr); + if(attr.getAnnotation().automate()) + { + fieldMap.put(attr.getName(), findField(attr, clazz)); + } + + } + _allAttributeTypes.put(clazz, attrMap); + _allAutomatedFields.put(clazz, fieldMap); + } + } + + private static Field findField(final Attribute<?, ?> attr, Class<?> objClass) + { + Class<?> clazz = objClass; + while(clazz != null) + { + for(Field field : clazz.getDeclaredFields()) + { + if(field.getAnnotation(ManagedAttributeField.class) != null && field.getName().equals("_" + attr.getName().replace('.','_'))) + { + field.setAccessible(true); + return field; + } + } + 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()); + } + + private static String getName(final Method m, final Class<?> type) + { + String methodName = m.getName(); + String baseName; + + if(type == Boolean.class ) + { + if((methodName.startsWith("get") || methodName.startsWith("has")) && methodName.length() >= 4) + { + baseName = methodName.substring(3); + } + else if(methodName.startsWith("is") && methodName.length() >= 3) + { + baseName = methodName.substring(2); + } + else + { + throw new IllegalArgumentException("Method name " + methodName + " does not conform to the required pattern for ManagedAttributes"); + } + } + else + { + if(methodName.startsWith("get") && methodName.length() >= 4) + { + baseName = methodName.substring(3); + } + else + { + throw new IllegalArgumentException("Method name " + methodName + " does not conform to the required pattern for ManagedAttributes"); + } + } + + String name = baseName.length() == 1 ? baseName.toLowerCase() : baseName.substring(0,1).toLowerCase() + baseName.substring(1); + name = name.replace('_','.'); + return name; + } + + private static Class<?> getType(final Method m) + { + Class<?> type = m.getReturnType(); + if(type.isPrimitive()) + { + if(type == Boolean.TYPE) + { + type = Boolean.class; + } + else if(type == Byte.TYPE) + { + type = Byte.class; + } + else if(type == Short.TYPE) + { + type = Short.class; + } + else if(type == Integer.TYPE) + { + type = Integer.class; + } + else if(type == Long.TYPE) + { + type = Long.class; + } + else if(type == Float.TYPE) + { + type = Float.class; + } + else if(type == Double.TYPE) + { + type = Double.class; + } + else if(type == Character.TYPE) + { + type = Character.class; + } + } + return type; + } + + public static <X extends ConfiguredObject> Collection<String> getAttributeNames(Class<X> clazz) + { + final Collection<Attribute<? super X, ?>> attrs = getAttributes(clazz); + + return new AbstractCollection<String>() + { + @Override + public Iterator<String> iterator() + { + final Iterator<Attribute<? 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 static <X extends ConfiguredObject> Collection<Attribute<? super X, ?>> getAttributes(final Class<X> clazz) + { + if(!_allAttributes.containsKey(clazz)) + { + processAttributes(clazz); + } + final Collection<Attribute<? super X, ?>> attributes = (Collection) _allAttributes.get(clazz); + return attributes; + } + + + protected static Collection<Statistic> getStatistics(final Class<? extends ConfiguredObject> clazz) + { + if(!_allStatistics.containsKey(clazz)) + { + processAttributes(clazz); + } + final Collection<Statistic> statistics = (Collection) _allStatistics.get(clazz); + return statistics; + } + + + private static Map<String, Attribute<?, ?>> getAttributeTypes(final Class<? extends ConfiguredObject> clazz) + { + if(!_allAttributeTypes.containsKey(clazz)) + { + processAttributes(clazz); + } + return _allAttributeTypes.get(clazz); + } + + private static Map<String, Field> getAutomatedFields(Class<? extends ConfiguredObject> clazz) + { + if(!_allAutomatedFields.containsKey(clazz)) + { + processAttributes(clazz); + } + return _allAutomatedFields.get(clazz); + } + + private static <X extends ConfiguredObject<X>> Collection<X> getReachableObjects(final ConfiguredObject<?> object, + final Class<X> clazz) + { + Class<? extends ConfiguredObject> category = getCategory(object.getClass()); + Class<? extends ConfiguredObject> ancestorClass = getAncestorClassWithGivenDescendant(category, clazz); + if(ancestorClass != null) + { + ConfiguredObject ancestor = getAncestor(ancestorClass, category, object); + if(ancestor != null) + { + return getAllDescendants(ancestor, ancestorClass, clazz); + } + } + return null; + } + + private static <X extends ConfiguredObject<X>> Collection<X> getAllDescendants(final ConfiguredObject ancestor, + final Class<? extends ConfiguredObject> ancestorClass, + final Class<X> clazz) + { + Set<X> descendants = new HashSet<X>(); + for(Class<? extends ConfiguredObject> childClass : Model.getInstance().getChildTypes(ancestorClass)) + { + Collection<? extends ConfiguredObject> children = ancestor.getChildren(childClass); + if(childClass == clazz) + { + + if(children != null) + { + descendants.addAll((Collection<X>)children); + } + } + else + { + if(children != null) + { + for(ConfiguredObject child : children) + { + descendants.addAll(getAllDescendants(child, childClass, clazz)); + } + } + } + } + return descendants; + } + + private static ConfiguredObject getAncestor(final Class<? extends ConfiguredObject> ancestorClass, + final Class<? extends ConfiguredObject> category, + final ConfiguredObject<?> object) + { + if(ancestorClass.isInstance(object)) + { + return object; + } + else + { + for(Class<? extends ConfiguredObject> parentClass : Model.getInstance().getParentTypes(category)) + { + ConfiguredObject parent = object.getParent(parentClass); + if(parent == null) + { + System.err.println(parentClass.getSimpleName()); + } + ConfiguredObject ancestor = getAncestor(ancestorClass, parentClass, parent); + if(ancestor != null) + { + return ancestor; + } + } + } + return null; + } + + private static Class<? extends ConfiguredObject> getAncestorClassWithGivenDescendant( + final Class<? extends ConfiguredObject> category, + final Class<? extends ConfiguredObject> descendantClass) + { + Model model = Model.getInstance(); + Collection<Class<? extends ConfiguredObject>> candidateClasses = + Collections.<Class<? extends ConfiguredObject>>singleton(category); + while(!candidateClasses.isEmpty()) + { + for(Class<? extends ConfiguredObject> candidate : candidateClasses) + { + if(hasDescendant(candidate, descendantClass)) + { + return candidate; + } + } + Set<Class<? extends ConfiguredObject>> previous = new HashSet<Class<? extends ConfiguredObject>>(candidateClasses); + candidateClasses = new HashSet<Class<? extends ConfiguredObject>>(); + for(Class<? extends ConfiguredObject> prev : previous) + { + candidateClasses.addAll(model.getParentTypes(prev)); + } + } + return null; + } + + private static boolean hasDescendant(final Class<? extends ConfiguredObject> candidate, + final Class<? extends ConfiguredObject> descendantClass) + { + int oldSize = 0; + Model model = Model.getInstance(); + + Set<Class<? extends ConfiguredObject>> allDescendants = new HashSet<Class<? extends ConfiguredObject>>(Collections.singleton(candidate)); + while(allDescendants.size() > oldSize) + { + oldSize = allDescendants.size(); + Set<Class<? extends ConfiguredObject>> prev = new HashSet<Class<? extends ConfiguredObject>>(allDescendants); + for(Class<? extends ConfiguredObject> clazz : prev) + { + allDescendants.addAll(model.getChildTypes(clazz)); + } + } + return allDescendants.contains(descendantClass); + } + + static Class<? extends ConfiguredObject> getCategory(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; + } + + + protected static String getType(final Class<? extends ConfiguredObject> clazz) + { + ManagedObject annotation = clazz.getAnnotation(ManagedObject.class); + if(annotation != null) + { + if(!"".equals(annotation.type())) + { + return annotation.type(); + } + } + + if(clazz.getSuperclass() != null && ConfiguredObject.class.isAssignableFrom(clazz.getSuperclass())) + { + String type = getType((Class<? extends ConfiguredObject>) clazz.getSuperclass()); + if(!"".equals(type)) + { + return type; + } + } + + for(Class<?> iface : clazz.getInterfaces() ) + { + if(ConfiguredObject.class.isAssignableFrom(iface)) + { + String type = getType((Class<? extends ConfiguredObject>) iface); + if(!"".equals(type)) + { + return type; + } + } + } + Class<? extends ConfiguredObject> category = getCategory(clazz); + if(category == null) + { + return ""; + } + annotation = category.getAnnotation(ManagedObject.class); + if(annotation == null) + { + throw new NullPointerException("No definition found for category " + category.getSimpleName()); + } + if(!"".equals(annotation.defaultType())) + { + return annotation.defaultType(); + } + return category.getSimpleName(); + } +} |