| /* |
| * |
| * 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.io.IOException; |
| import java.io.StringWriter; |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.lang.reflect.Proxy; |
| import java.security.AccessControlException; |
| import java.security.PrivilegedAction; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.UUID; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import javax.security.auth.Subject; |
| |
| import org.apache.log4j.Logger; |
| import org.codehaus.jackson.JsonGenerator; |
| import org.codehaus.jackson.JsonProcessingException; |
| import org.codehaus.jackson.Version; |
| import org.codehaus.jackson.map.JsonSerializer; |
| import org.codehaus.jackson.map.Module; |
| import org.codehaus.jackson.map.ObjectMapper; |
| import org.codehaus.jackson.map.SerializerProvider; |
| import org.codehaus.jackson.map.module.SimpleModule; |
| |
| import org.apache.qpid.server.configuration.IllegalConfigurationException; |
| import org.apache.qpid.server.configuration.updater.Task; |
| import org.apache.qpid.server.configuration.updater.TaskExecutor; |
| import org.apache.qpid.server.configuration.updater.TaskWithException; |
| import org.apache.qpid.server.configuration.updater.VoidTask; |
| import org.apache.qpid.server.configuration.updater.VoidTaskWithException; |
| import org.apache.qpid.server.security.SecurityManager; |
| import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; |
| import org.apache.qpid.server.security.encryption.ConfigurationSecretEncrypter; |
| import org.apache.qpid.server.store.ConfiguredObjectRecord; |
| import org.apache.qpid.server.util.Action; |
| import org.apache.qpid.server.util.ServerScopedRuntimeException; |
| import org.apache.qpid.util.Strings; |
| |
| public abstract class AbstractConfiguredObject<X extends ConfiguredObject<X>> implements ConfiguredObject<X> |
| { |
| private static final Logger LOGGER = Logger.getLogger(AbstractConfiguredObject.class); |
| |
| private static final Map<Class, Object> SECURE_VALUES; |
| |
| public static final String SECURED_STRING_VALUE = "********"; |
| |
| static |
| { |
| Map<Class,Object> secureValues = new HashMap<Class, Object>(); |
| secureValues.put(String.class, SECURED_STRING_VALUE); |
| 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 ConfigurationSecretEncrypter _encrypter; |
| |
| private enum DynamicState { UNINIT, OPENED, CLOSED }; |
| private final AtomicReference<DynamicState> _dynamicState = new AtomicReference<>(DynamicState.UNINIT); |
| |
| |
| |
| 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>(); |
| |
| private final Map<Class<? extends ConfiguredObject>, Collection<ConfiguredObject<?>>> _children = |
| new ConcurrentHashMap<Class<? extends ConfiguredObject>, Collection<ConfiguredObject<?>>>(); |
| private final Map<Class<? extends ConfiguredObject>, Map<UUID,ConfiguredObject<?>>> _childrenById = |
| new ConcurrentHashMap<Class<? extends ConfiguredObject>, Map<UUID,ConfiguredObject<?>>>(); |
| private final Map<Class<? extends ConfiguredObject>, Map<String,ConfiguredObject<?>>> _childrenByName = |
| new ConcurrentHashMap<Class<? extends ConfiguredObject>, Map<String,ConfiguredObject<?>>>(); |
| |
| |
| @ManagedAttributeField |
| private final UUID _id; |
| |
| private final TaskExecutor _taskExecutor; |
| |
| private final Class<? extends ConfiguredObject> _category; |
| private final Class<? extends ConfiguredObject> _bestFitInterface; |
| private final Model _model; |
| |
| @ManagedAttributeField |
| private long _createdTime; |
| |
| @ManagedAttributeField |
| private String _createdBy; |
| |
| @ManagedAttributeField |
| private long _lastUpdatedTime; |
| |
| @ManagedAttributeField |
| private String _lastUpdatedBy; |
| |
| @ManagedAttributeField |
| private String _name; |
| |
| @ManagedAttributeField |
| private Map<String,String> _context; |
| |
| @ManagedAttributeField |
| private boolean _durable; |
| |
| @ManagedAttributeField |
| private String _description; |
| |
| @ManagedAttributeField |
| private LifetimePolicy _lifetimePolicy; |
| |
| private final Map<String, ConfiguredObjectAttribute<?,?>> _attributeTypes; |
| private final Map<String, ConfiguredObjectTypeRegistry.AutomatedField> _automatedFields; |
| private final Map<State, Map<State, Method>> _stateChangeMethods; |
| |
| @ManagedAttributeField |
| private String _type; |
| |
| private final OwnAttributeResolver _attributeResolver = new OwnAttributeResolver(this); |
| |
| @ManagedAttributeField( afterSet = "attainStateIfOpenedOrReopenFailed" ) |
| private State _desiredState; |
| private boolean _openComplete; |
| private boolean _openFailed; |
| private volatile State _state = State.UNINITIALIZED; |
| |
| protected static Map<Class<? extends ConfiguredObject>, ConfiguredObject<?>> parentsMap(ConfiguredObject<?>... parents) |
| { |
| final Map<Class<? extends ConfiguredObject>, ConfiguredObject<?>> parentsMap = |
| new HashMap<Class<? extends ConfiguredObject>, ConfiguredObject<?>>(); |
| |
| for(ConfiguredObject<?> parent : parents) |
| { |
| parentsMap.put(parent.getCategoryClass(), parent); |
| } |
| return parentsMap; |
| } |
| |
| protected AbstractConfiguredObject(final Map<Class<? extends ConfiguredObject>, ConfiguredObject<?>> parents, |
| Map<String, Object> attributes) |
| { |
| this(parents, attributes, parents.values().iterator().next().getTaskExecutor()); |
| } |
| |
| |
| protected AbstractConfiguredObject(final Map<Class<? extends ConfiguredObject>, ConfiguredObject<?>> parents, |
| Map<String, Object> attributes, |
| TaskExecutor taskExecutor) |
| { |
| this(parents, attributes, taskExecutor, parents.values().iterator().next().getModel()); |
| } |
| |
| protected AbstractConfiguredObject(final Map<Class<? extends ConfiguredObject>, ConfiguredObject<?>> parents, |
| Map<String, Object> attributes, |
| TaskExecutor taskExecutor, |
| Model model) |
| { |
| _taskExecutor = taskExecutor; |
| if(taskExecutor == null) |
| { |
| throw new NullPointerException("task executor is null"); |
| } |
| _model = model; |
| |
| _category = ConfiguredObjectTypeRegistry.getCategory(getClass()); |
| |
| _attributeTypes = model.getTypeRegistry().getAttributeTypes(getClass()); |
| _automatedFields = model.getTypeRegistry().getAutomatedFields(getClass()); |
| _stateChangeMethods = model.getTypeRegistry().getStateChangeMethods(getClass()); |
| |
| |
| for(ConfiguredObject<?> parent : parents.values()) |
| { |
| if(parent instanceof AbstractConfiguredObject && ((AbstractConfiguredObject)parent)._encrypter != null) |
| { |
| _encrypter = ((AbstractConfiguredObject)parent)._encrypter; |
| break; |
| } |
| } |
| |
| Object idObj = attributes.get(ID); |
| |
| UUID uuid; |
| if(idObj == null) |
| { |
| uuid = UUID.randomUUID(); |
| attributes = new HashMap<String, Object>(attributes); |
| attributes.put(ID, uuid); |
| } |
| else |
| { |
| uuid = AttributeValueConverter.UUID_CONVERTER.convert(idObj, this); |
| } |
| _id = uuid; |
| _name = AttributeValueConverter.STRING_CONVERTER.convert(attributes.get(NAME),this); |
| if(_name == null) |
| { |
| throw new IllegalArgumentException("The name attribute is mandatory for " + getClass().getSimpleName() + " creation."); |
| } |
| |
| _type = ConfiguredObjectTypeRegistry.getType(getClass()); |
| _bestFitInterface = calculateBestFitInterface(); |
| |
| if(attributes.get(TYPE) != null && !_type.equals(attributes.get(TYPE))) |
| { |
| throw new IllegalConfigurationException("Provided type is " + attributes.get(TYPE) |
| + " but calculated type is " + _type); |
| } |
| |
| for (Class<? extends ConfiguredObject> childClass : getModel().getChildTypes(getCategoryClass())) |
| { |
| _children.put(childClass, new CopyOnWriteArrayList<ConfiguredObject<?>>()); |
| _childrenById.put(childClass, new ConcurrentHashMap<UUID, ConfiguredObject<?>>()); |
| _childrenByName.put(childClass, new ConcurrentHashMap<String, ConfiguredObject<?>>()); |
| } |
| |
| for(Map.Entry<Class<? extends ConfiguredObject>, ConfiguredObject<?>> entry : parents.entrySet()) |
| { |
| addParent((Class<ConfiguredObject<?>>) entry.getKey(), entry.getValue()); |
| } |
| |
| Object durableObj = attributes.get(DURABLE); |
| _durable = AttributeValueConverter.BOOLEAN_CONVERTER.convert(durableObj == null |
| ? ((ConfiguredAutomatedAttribute) (_attributeTypes |
| .get(DURABLE))).defaultValue() |
| : durableObj, this); |
| |
| for (String name : getAttributeNames()) |
| { |
| if (attributes.containsKey(name)) |
| { |
| final Object value = attributes.get(name); |
| if (value != null) |
| { |
| _attributes.put(name, value); |
| } |
| } |
| } |
| |
| 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(ConfiguredObjectAttribute<?,?> attr : _attributeTypes.values()) |
| { |
| if(attr.isAutomated()) |
| { |
| ConfiguredAutomatedAttribute<?,?> autoAttr = (ConfiguredAutomatedAttribute<?,?>)attr; |
| if (autoAttr.isMandatory() && !(_attributes.containsKey(attr.getName()) |
| || !"".equals(autoAttr.defaultValue()))) |
| { |
| deleted(); |
| throw new IllegalArgumentException("Mandatory attribute " |
| + attr.getName() |
| + " not supplied for instance of " |
| + getClass().getName()); |
| } |
| } |
| } |
| } |
| |
| private Class<? extends ConfiguredObject> calculateBestFitInterface() |
| { |
| Set<Class<? extends ConfiguredObject>> candidates = new HashSet<Class<? extends ConfiguredObject>>(); |
| findBestFitInterface(getClass(), candidates); |
| switch(candidates.size()) |
| { |
| case 0: |
| throw new ServerScopedRuntimeException("The configured object class " + getClass().getSimpleName() + " does not seem to implement an interface"); |
| case 1: |
| return candidates.iterator().next(); |
| default: |
| ArrayList<Class<? extends ConfiguredObject>> list = new ArrayList<>(candidates); |
| |
| throw new ServerScopedRuntimeException("The configured object class " + getClass().getSimpleName() |
| + " implements no single common interface which extends ConfiguredObject" |
| + " Identified candidates were : " + Arrays.toString(list.toArray())); |
| } |
| } |
| |
| private static final void findBestFitInterface(Class<? extends ConfiguredObject> clazz, Set<Class<? extends ConfiguredObject>> candidates) |
| { |
| for(Class<?> interfaceClass : clazz.getInterfaces()) |
| { |
| if(ConfiguredObject.class.isAssignableFrom(interfaceClass)) |
| { |
| checkCandidate((Class<? extends ConfiguredObject>) interfaceClass, candidates); |
| } |
| } |
| if(clazz.getSuperclass() != null && ConfiguredObject.class.isAssignableFrom(clazz.getSuperclass())) |
| { |
| findBestFitInterface((Class<? extends ConfiguredObject>) clazz.getSuperclass(), candidates); |
| } |
| } |
| |
| private static void checkCandidate(final Class<? extends ConfiguredObject> interfaceClass, |
| final Set<Class<? extends ConfiguredObject>> candidates) |
| { |
| if(!candidates.contains(interfaceClass)) |
| { |
| Iterator<Class<? extends ConfiguredObject>> candidateIterator = candidates.iterator(); |
| |
| while(candidateIterator.hasNext()) |
| { |
| Class<? extends ConfiguredObject> existingCandidate = candidateIterator.next(); |
| if(existingCandidate.isAssignableFrom(interfaceClass)) |
| { |
| candidateIterator.remove(); |
| } |
| else if(interfaceClass.isAssignableFrom(existingCandidate)) |
| { |
| return; |
| } |
| } |
| |
| candidates.add(interfaceClass); |
| |
| } |
| } |
| |
| private void automatedSetValue(final String name, Object value) |
| { |
| try |
| { |
| final ConfiguredAutomatedAttribute attribute = (ConfiguredAutomatedAttribute) _attributeTypes.get(name); |
| if(value == null && !"".equals(attribute.defaultValue())) |
| { |
| value = attribute.defaultValue(); |
| } |
| ConfiguredObjectTypeRegistry.AutomatedField field = _automatedFields.get(name); |
| |
| if(field.getPreSettingAction() != null) |
| { |
| field.getPreSettingAction().invoke(this); |
| } |
| field.getField().set(this, attribute.convert(value, this)); |
| |
| if(field.getPostSettingAction() != null) |
| { |
| field.getPostSettingAction().invoke(this); |
| } |
| } |
| catch (IllegalAccessException e) |
| { |
| throw new ServerScopedRuntimeException("Unable to set the automated attribute " + name + " on the configure object type " + getClass().getName(),e); |
| } |
| catch (InvocationTargetException e) |
| { |
| if(e.getCause() instanceof RuntimeException) |
| { |
| throw (RuntimeException) e.getCause(); |
| } |
| throw new ServerScopedRuntimeException("Unable to set the automated attribute " + name + " on the configure object type " + getClass().getName(),e); |
| } |
| } |
| |
| @Override |
| public final void open() |
| { |
| if(_dynamicState.compareAndSet(DynamicState.UNINIT, DynamicState.OPENED)) |
| { |
| _openFailed = false; |
| OpenExceptionHandler exceptionHandler = new OpenExceptionHandler(); |
| try |
| { |
| doResolution(true, exceptionHandler); |
| doValidation(true, exceptionHandler); |
| doOpening(true, exceptionHandler); |
| doAttainState(exceptionHandler); |
| } |
| catch(RuntimeException e) |
| { |
| exceptionHandler.handleException(e, this); |
| } |
| } |
| } |
| |
| public void registerWithParents() |
| { |
| for(ConfiguredObject<?> parent : _parents.values()) |
| { |
| if(parent instanceof AbstractConfiguredObject<?>) |
| { |
| ((AbstractConfiguredObject<?>)parent).registerChild(this); |
| } |
| } |
| } |
| |
| protected void closeChildren() |
| { |
| applyToChildren(new Action<ConfiguredObject<?>>() |
| { |
| @Override |
| public void performAction(final ConfiguredObject<?> child) |
| { |
| child.close(); |
| } |
| }); |
| |
| for(Collection<ConfiguredObject<?>> childList : _children.values()) |
| { |
| childList.clear(); |
| } |
| |
| for(Map<UUID,ConfiguredObject<?>> childIdMap : _childrenById.values()) |
| { |
| childIdMap.clear(); |
| } |
| |
| for(Map<String,ConfiguredObject<?>> childNameMap : _childrenByName.values()) |
| { |
| childNameMap.clear(); |
| } |
| |
| } |
| |
| @Override |
| public final void close() |
| { |
| if(_dynamicState.compareAndSet(DynamicState.OPENED, DynamicState.CLOSED)) |
| { |
| closeChildren(); |
| onClose(); |
| unregister(false); |
| |
| } |
| } |
| |
| protected void onClose() |
| { |
| } |
| |
| public final void create() |
| { |
| if(_dynamicState.compareAndSet(DynamicState.UNINIT, DynamicState.OPENED)) |
| { |
| final AuthenticatedPrincipal currentUser = SecurityManager.getCurrentUser(); |
| if(currentUser != null) |
| { |
| String currentUserName = currentUser.getName(); |
| _attributes.put(LAST_UPDATED_BY, currentUserName); |
| _attributes.put(CREATED_BY, currentUserName); |
| _lastUpdatedBy = currentUserName; |
| _createdBy = currentUserName; |
| } |
| final long currentTime = System.currentTimeMillis(); |
| _attributes.put(LAST_UPDATED_TIME, currentTime); |
| _attributes.put(CREATED_TIME, currentTime); |
| _lastUpdatedTime = currentTime; |
| _createdTime = currentTime; |
| |
| CreateExceptionHandler createExceptionHandler = new CreateExceptionHandler(); |
| try |
| { |
| doResolution(true, createExceptionHandler); |
| doValidation(true, createExceptionHandler); |
| validateOnCreate(); |
| registerWithParents(); |
| } |
| catch(RuntimeException e) |
| { |
| createExceptionHandler.handleException(e, this); |
| } |
| |
| AbstractConfiguredObjectExceptionHandler unregisteringExceptionHandler = new CreateExceptionHandler(true); |
| try |
| { |
| doCreation(true, unregisteringExceptionHandler); |
| doOpening(true, unregisteringExceptionHandler); |
| doAttainState(unregisteringExceptionHandler); |
| } |
| catch(RuntimeException e) |
| { |
| unregisteringExceptionHandler.handleException(e, this); |
| } |
| } |
| } |
| |
| protected void validateOnCreate() |
| { |
| } |
| |
| protected final void handleExceptionOnOpen(RuntimeException e) |
| { |
| if (e instanceof ServerScopedRuntimeException) |
| { |
| throw e; |
| } |
| |
| LOGGER.error("Failed to open object with name '" + getName() + "'. Object will be put into ERROR state.", e); |
| |
| try |
| { |
| onExceptionInOpen(e); |
| } |
| catch (RuntimeException re) |
| { |
| LOGGER.error("Unexpected exception while handling exception on open for " + getName(), e); |
| } |
| |
| if (!_openComplete) |
| { |
| _openFailed = true; |
| _dynamicState.compareAndSet(DynamicState.OPENED, DynamicState.UNINIT); |
| } |
| |
| //TODO: children of ERRORED CO will continue to remain in ACTIVE state |
| setState(State.ERRORED); |
| } |
| |
| /** |
| * Callback method to perform ConfiguredObject specific exception handling on exception in open. |
| * <p/> |
| * The method is not expected to throw any runtime exception. |
| * @param e open exception |
| */ |
| protected void onExceptionInOpen(RuntimeException e) |
| { |
| } |
| |
| private void doAttainState(final AbstractConfiguredObjectExceptionHandler exceptionHandler) |
| { |
| applyToChildren(new Action<ConfiguredObject<?>>() |
| { |
| @Override |
| public void performAction(final ConfiguredObject<?> child) |
| { |
| if (child instanceof AbstractConfiguredObject) |
| { |
| AbstractConfiguredObject configuredObject = (AbstractConfiguredObject) child; |
| if (configuredObject._dynamicState.get() == DynamicState.OPENED) |
| { |
| try |
| { |
| configuredObject.doAttainState(exceptionHandler); |
| } |
| catch (RuntimeException e) |
| { |
| exceptionHandler.handleException(e, configuredObject); |
| } |
| } |
| } |
| } |
| }); |
| attainState(); |
| } |
| |
| protected void doOpening(boolean skipCheck, final AbstractConfiguredObjectExceptionHandler exceptionHandler) |
| { |
| if(skipCheck || _dynamicState.compareAndSet(DynamicState.UNINIT,DynamicState.OPENED)) |
| { |
| onOpen(); |
| notifyStateChanged(State.UNINITIALIZED, getState()); |
| applyToChildren(new Action<ConfiguredObject<?>>() |
| { |
| @Override |
| public void performAction(final ConfiguredObject<?> child) |
| { |
| if (child.getState() != State.ERRORED && child instanceof AbstractConfiguredObject) |
| { |
| AbstractConfiguredObject configuredObject = (AbstractConfiguredObject) child; |
| try |
| { |
| configuredObject.doOpening(false, exceptionHandler); |
| } |
| catch (RuntimeException e) |
| { |
| exceptionHandler.handleException(e, configuredObject); |
| } |
| } |
| } |
| }); |
| _openComplete = true; |
| } |
| } |
| |
| protected final void doValidation(final boolean skipCheck, final AbstractConfiguredObjectExceptionHandler exceptionHandler) |
| { |
| if(skipCheck || _dynamicState.get() != DynamicState.OPENED) |
| { |
| applyToChildren(new Action<ConfiguredObject<?>>() |
| { |
| @Override |
| public void performAction(final ConfiguredObject<?> child) |
| { |
| if (child.getState() != State.ERRORED && child instanceof AbstractConfiguredObject) |
| { |
| AbstractConfiguredObject configuredObject = (AbstractConfiguredObject) child; |
| try |
| { |
| configuredObject.doValidation(false, exceptionHandler); |
| } |
| catch (RuntimeException e) |
| { |
| exceptionHandler.handleException(e, configuredObject); |
| } |
| } |
| } |
| }); |
| onValidate(); |
| } |
| } |
| |
| protected final void doResolution(boolean skipCheck, final AbstractConfiguredObjectExceptionHandler exceptionHandler) |
| { |
| if(skipCheck || _dynamicState.get() != DynamicState.OPENED) |
| { |
| onResolve(); |
| postResolve(); |
| applyToChildren(new Action() |
| { |
| @Override |
| public void performAction(Object child) |
| { |
| if (child instanceof AbstractConfiguredObject) |
| { |
| AbstractConfiguredObject configuredObject = (AbstractConfiguredObject) child; |
| try |
| { |
| configuredObject.doResolution(false, exceptionHandler); |
| } |
| catch (RuntimeException e) |
| { |
| exceptionHandler.handleException(e, configuredObject); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| protected void postResolve() |
| { |
| } |
| |
| protected final void doCreation(final boolean skipCheck, final AbstractConfiguredObjectExceptionHandler exceptionHandler) |
| { |
| if(skipCheck || _dynamicState.get() != DynamicState.OPENED) |
| { |
| onCreate(); |
| applyToChildren(new Action<ConfiguredObject<?>>() |
| { |
| @Override |
| public void performAction(final ConfiguredObject<?> child) |
| { |
| if (child instanceof AbstractConfiguredObject) |
| { |
| AbstractConfiguredObject configuredObject =(AbstractConfiguredObject) child; |
| try |
| { |
| configuredObject.doCreation(false, exceptionHandler); |
| } |
| catch (RuntimeException e) |
| { |
| exceptionHandler.handleException(e, configuredObject); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| protected void applyToChildren(Action<ConfiguredObject<?>> action) |
| { |
| for (Class<? extends ConfiguredObject> childClass : getModel().getChildTypes(getCategoryClass())) |
| { |
| Collection<? extends ConfiguredObject> children = getChildren(childClass); |
| if (children != null) |
| { |
| for (ConfiguredObject<?> child : children) |
| { |
| action.performAction(child); |
| } |
| } |
| } |
| } |
| |
| public void onValidate() |
| { |
| } |
| |
| protected void setEncrypter(final ConfigurationSecretEncrypter encrypter) |
| { |
| _encrypter = encrypter; |
| } |
| |
| protected void onResolve() |
| { |
| Set<ConfiguredObjectAttribute<?,?>> unresolved = new HashSet<>(); |
| Set<ConfiguredObjectAttribute<?,?>> derived = new HashSet<>(); |
| |
| |
| for (ConfiguredObjectAttribute<?, ?> attr : _attributeTypes.values()) |
| { |
| if (attr.isAutomated()) |
| { |
| unresolved.add(attr); |
| } |
| else if(attr.isDerived()) |
| { |
| derived.add(attr); |
| } |
| } |
| |
| // If there is a context attribute, resolve it first, so that other attribute values |
| // may support values containing references to context keys. |
| ConfiguredObjectAttribute<?, ?> contextAttribute = _attributeTypes.get("context"); |
| if (contextAttribute != null && contextAttribute.isAutomated()) |
| { |
| resolveAutomatedAttribute((ConfiguredAutomatedAttribute<?, ?>) contextAttribute); |
| unresolved.remove(contextAttribute); |
| } |
| |
| boolean changed = true; |
| while(!unresolved.isEmpty() || !changed) |
| { |
| changed = false; |
| Iterator<ConfiguredObjectAttribute<?,?>> attrIter = unresolved.iterator(); |
| |
| while (attrIter.hasNext()) |
| { |
| ConfiguredObjectAttribute<?, ?> attr = attrIter.next(); |
| |
| if(!(dependsOn(attr, unresolved) || (!derived.isEmpty() && dependsOn(attr, derived)))) |
| { |
| resolveAutomatedAttribute((ConfiguredAutomatedAttribute<?, ?>) attr); |
| attrIter.remove(); |
| changed = true; |
| } |
| } |
| // TODO - really we should define with meta data which attributes any given derived attr is dependent upon |
| // and only remove the derived attr as an obstacle when those fields are themselves resolved |
| if(!changed && !derived.isEmpty()) |
| { |
| changed = true; |
| derived.clear(); |
| } |
| } |
| } |
| |
| private boolean dependsOn(final ConfiguredObjectAttribute<?, ?> attr, |
| final Set<ConfiguredObjectAttribute<?, ?>> unresolved) |
| { |
| Object value = _attributes.get(attr.getName()); |
| if(value == null && !"".equals(((ConfiguredAutomatedAttribute)attr).defaultValue())) |
| { |
| value = ((ConfiguredAutomatedAttribute)attr).defaultValue(); |
| } |
| if(value instanceof String) |
| { |
| String interpolated = interpolate(this, (String)value); |
| if(interpolated.contains("${this:")) |
| { |
| for(ConfiguredObjectAttribute<?,?> unresolvedAttr : unresolved) |
| { |
| if(interpolated.contains("${this:"+unresolvedAttr.getName())) |
| { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| private void resolveAutomatedAttribute(final ConfiguredAutomatedAttribute<?, ?> autoAttr) |
| { |
| String attrName = autoAttr.getName(); |
| if (_attributes.containsKey(attrName)) |
| { |
| automatedSetValue(attrName, _attributes.get(attrName)); |
| } |
| else if (!"".equals(autoAttr.defaultValue())) |
| { |
| automatedSetValue(attrName, autoAttr.defaultValue()); |
| } |
| } |
| |
| private void attainStateIfOpenedOrReopenFailed() |
| { |
| if (_openComplete || getDesiredState() == State.DELETED) |
| { |
| attainState(); |
| } |
| else if (_openFailed) |
| { |
| open(); |
| } |
| } |
| |
| protected void onOpen() |
| { |
| |
| } |
| |
| protected void attainState() |
| { |
| State currentState = getState(); |
| State desiredState = getDesiredState(); |
| if(currentState != desiredState) |
| { |
| Method stateChangingMethod = getStateChangeMethod(currentState, desiredState); |
| if(stateChangingMethod != null) |
| { |
| try |
| { |
| stateChangingMethod.invoke(this); |
| } |
| catch (IllegalAccessException e) |
| { |
| throw new ServerScopedRuntimeException("Unexpected access exception when calling state transition", e); |
| } |
| catch (InvocationTargetException e) |
| { |
| Throwable underlying = e.getTargetException(); |
| if(underlying instanceof RuntimeException) |
| { |
| throw (RuntimeException)underlying; |
| } |
| if(underlying instanceof Error) |
| { |
| throw (Error) underlying; |
| } |
| throw new ServerScopedRuntimeException("Unexpected checked exception when calling state transition", underlying); |
| } |
| } |
| } |
| } |
| |
| private Method getStateChangeMethod(final State currentState, final State desiredState) |
| { |
| Map<State, Method> stateChangeMethodMap = _stateChangeMethods.get(currentState); |
| Method method = null; |
| if(stateChangeMethodMap != null) |
| { |
| method = stateChangeMethodMap.get(desiredState); |
| } |
| return method; |
| } |
| |
| |
| protected void onCreate() |
| { |
| } |
| |
| public final UUID getId() |
| { |
| return _id; |
| } |
| |
| public final String getName() |
| { |
| return _name; |
| } |
| |
| public final boolean isDurable() |
| { |
| return _durable; |
| } |
| |
| @Override |
| public final ConfiguredObjectFactory getObjectFactory() |
| { |
| return _model.getObjectFactory(); |
| } |
| |
| @Override |
| public final Model getModel() |
| { |
| return _model; |
| } |
| |
| public Class<? extends ConfiguredObject> getCategoryClass() |
| { |
| return _category; |
| } |
| |
| public Map<String,String> getContext() |
| { |
| return _context == null ? Collections.<String,String>emptyMap() : Collections.unmodifiableMap(_context); |
| } |
| |
| public State getDesiredState() |
| { |
| return _desiredState; |
| } |
| |
| |
| private State setDesiredState(final State desiredState) |
| throws IllegalStateTransitionException, AccessControlException |
| { |
| |
| return runTask(new Task<State>() |
| { |
| @Override |
| public State execute() |
| { |
| |
| State state = getState(); |
| if(desiredState == getDesiredState() && desiredState != state) |
| { |
| attainStateIfOpenedOrReopenFailed(); |
| final State currentState = getState(); |
| if (currentState != state) |
| { |
| notifyStateChanged(state, currentState); |
| } |
| return currentState; |
| } |
| else |
| { |
| authoriseSetDesiredState(desiredState); |
| |
| setAttributes(Collections.<String, Object>singletonMap(DESIRED_STATE, |
| desiredState)); |
| |
| if (getState() == desiredState) |
| { |
| notifyStateChanged(state, desiredState); |
| return desiredState; |
| } |
| else |
| { |
| return getState(); |
| } |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public State getState() |
| { |
| return _state; |
| } |
| |
| protected void setState(State state) |
| { |
| _state = state; |
| } |
| |
| |
| 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); |
| } |
| } |
| } |
| |
| @Override |
| public final Object getAttribute(String name) |
| { |
| ConfiguredObjectAttribute<X,?> attr = (ConfiguredObjectAttribute<X, ?>) _attributeTypes.get(name); |
| if(attr != null && (attr.isAutomated() || attr.isDerived())) |
| { |
| Object value = attr.getValue((X)this); |
| if(value != null && attr.isSecure() && |
| !SecurityManager.isSystemProcess()) |
| { |
| return SECURE_VALUES.get(value.getClass()); |
| } |
| else |
| { |
| return value; |
| } |
| } |
| else if(attr != null) |
| { |
| Object value = getActualAttribute(name); |
| return value; |
| } |
| else |
| { |
| throw new IllegalArgumentException("Unknown attribute: '" + name + "'"); |
| } |
| } |
| |
| @Override |
| public String getDescription() |
| { |
| return _description; |
| } |
| |
| @Override |
| public LifetimePolicy getLifetimePolicy() |
| { |
| return _lifetimePolicy; |
| } |
| |
| @Override |
| public final Map<String, Object> getActualAttributes() |
| { |
| synchronized (_attributes) |
| { |
| return new HashMap<String, Object>(_attributes); |
| } |
| } |
| |
| private Object getActualAttribute(final String name) |
| { |
| synchronized (_attributes) |
| { |
| return _attributes.get(name); |
| } |
| } |
| |
| public Object setAttribute(final String name, final Object expected, final Object desired) |
| throws IllegalStateException, AccessControlException, IllegalArgumentException |
| { |
| return _taskExecutor.run(new Task<Object>() |
| { |
| @Override |
| public Object execute() |
| { |
| authoriseSetAttributes(createProxyForValidation(Collections.singletonMap(name, desired)), |
| Collections.singleton(name)); |
| |
| if (changeAttribute(name, expected, desired)) |
| { |
| attributeSet(name, expected, desired); |
| return desired; |
| } |
| else |
| { |
| return getAttribute(name); |
| } |
| } |
| }); |
| } |
| |
| 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); |
| ConfiguredObjectAttribute<?,?> attr = _attributeTypes.get(name); |
| if(attr != null && attr.isAutomated()) |
| { |
| automatedSetValue(name, desired); |
| } |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| } |
| |
| public <T extends ConfiguredObject> T getParent(final Class<T> clazz) |
| { |
| return (T) _parents.get(clazz); |
| } |
| |
| private <T extends ConfiguredObject> void addParent(Class<T> clazz, T parent) |
| { |
| synchronized (_parents) |
| { |
| _parents.put(clazz, parent); |
| } |
| |
| } |
| |
| public final Collection<String> getAttributeNames() |
| { |
| return _model.getTypeRegistry().getAttributeNames(getClass()); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return getClass().getSimpleName() + " [id=" + _id + ", name=" + getName() + "]"; |
| } |
| |
| public final 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> attributes = new LinkedHashMap<String, Object>(); |
| Map<String,Object> actualAttributes = getActualAttributes(); |
| for(ConfiguredObjectAttribute<?,?> attr : _attributeTypes.values()) |
| { |
| if(attr.isPersisted()) |
| { |
| if(attr.isDerived()) |
| { |
| attributes.put(attr.getName(), getAttribute(attr.getName())); |
| } |
| else if(actualAttributes.containsKey(attr.getName())) |
| { |
| Object value = actualAttributes.get(attr.getName()); |
| if(value instanceof ConfiguredObject) |
| { |
| value = ((ConfiguredObject)value).getId(); |
| } |
| if(attr.isSecure() && _encrypter != null && value != null) |
| { |
| if(value instanceof Collection || value instanceof Map) |
| { |
| ObjectMapper mapper = new ObjectMapper(); |
| try(StringWriter stringWriter = new StringWriter()) |
| { |
| mapper.writeValue(stringWriter, value); |
| value = _encrypter.encrypt(stringWriter.toString()); |
| } |
| catch (IOException e) |
| { |
| throw new IllegalConfigurationException("Failure when encrypting a secret value", e); |
| } |
| } |
| else |
| { |
| value = _encrypter.encrypt(value.toString()); |
| } |
| } |
| attributes.put(attr.getName(), value); |
| } |
| } |
| } |
| attributes.remove(ID); |
| return attributes; |
| } |
| }); |
| } |
| |
| @Override |
| public Map<String, UUID> getParents() |
| { |
| Map<String, UUID> parents = new LinkedHashMap<>(); |
| for(Class<? extends ConfiguredObject> parentClass : getModel().getParentTypes(getCategoryClass())) |
| { |
| ConfiguredObject parent = getParent(parentClass); |
| if(parent != null) |
| { |
| parents.put(parentClass.getSimpleName(), parent.getId()); |
| } |
| } |
| 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(final Class<C> childClass, final Map<String, Object> attributes, |
| final ConfiguredObject... otherParents) |
| { |
| return _taskExecutor.run(new Task<C>() { |
| |
| @Override |
| public C execute() |
| { |
| authoriseCreateChild(childClass, attributes, otherParents); |
| C child = addChild(childClass, attributes, otherParents); |
| if (child != null) |
| { |
| childAdded(child); |
| } |
| return child; |
| } |
| }); |
| } |
| |
| protected <C extends ConfiguredObject> C addChild(Class<C> childClass, Map<String, Object> attributes, ConfiguredObject... otherParents) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| private <C extends ConfiguredObject> void registerChild(final C child) |
| { |
| |
| Class categoryClass = child.getCategoryClass(); |
| UUID childId = child.getId(); |
| String name = child.getName(); |
| if(_childrenById.get(categoryClass).containsKey(childId)) |
| { |
| throw new DuplicateIdException(child); |
| } |
| if(getModel().getParentTypes(categoryClass).size() == 1) |
| { |
| if (_childrenByName.get(categoryClass).containsKey(name)) |
| { |
| throw new DuplicateNameException(child); |
| } |
| _childrenByName.get(categoryClass).put(name, child); |
| } |
| _children.get(categoryClass).add(child); |
| _childrenById.get(categoryClass).put(childId,child); |
| |
| } |
| |
| public final void stop() |
| { |
| setDesiredState(State.STOPPED); |
| } |
| |
| public final void delete() |
| { |
| if(getState() == State.UNINITIALIZED) |
| { |
| _desiredState = State.DELETED; |
| } |
| setDesiredState(State.DELETED); |
| |
| } |
| |
| public final void start() { setDesiredState(State.ACTIVE); } |
| |
| protected void deleted() |
| { |
| unregister(true); |
| } |
| |
| private void unregister(boolean removed) |
| { |
| for (ConfiguredObject<?> parent : _parents.values()) |
| { |
| if (parent instanceof AbstractConfiguredObject<?>) |
| { |
| AbstractConfiguredObject<?> parentObj = (AbstractConfiguredObject<?>) parent; |
| parentObj.unregisterChild(this); |
| if(removed) |
| { |
| parentObj.childRemoved(this); |
| } |
| } |
| } |
| } |
| |
| |
| private <C extends ConfiguredObject> void unregisterChild(final C child) |
| { |
| Class categoryClass = child.getCategoryClass(); |
| _children.get(categoryClass).remove(child); |
| _childrenById.get(categoryClass).remove(child.getId()); |
| _childrenByName.get(categoryClass).remove(child.getName()); |
| } |
| |
| @Override |
| public final <C extends ConfiguredObject> C getChildById(final Class<C> clazz, final UUID id) |
| { |
| return (C) _childrenById.get(ConfiguredObjectTypeRegistry.getCategory(clazz)).get(id); |
| } |
| |
| @Override |
| public final <C extends ConfiguredObject> C getChildByName(final Class<C> clazz, final String name) |
| { |
| Class<? extends ConfiguredObject> categoryClass = ConfiguredObjectTypeRegistry.getCategory(clazz); |
| if(getModel().getParentTypes(categoryClass).size() != 1) |
| { |
| throw new UnsupportedOperationException("Cannot use getChildByName for objects of category " |
| + categoryClass.getSimpleName() + " as it has more than one parent"); |
| } |
| return (C) _childrenByName.get(categoryClass).get(name); |
| } |
| |
| @Override |
| public <C extends ConfiguredObject> Collection<C> getChildren(final Class<C> clazz) |
| { |
| return Collections.unmodifiableList((List<? extends C>) _children.get(clazz)); |
| } |
| |
| @Override |
| public final TaskExecutor getTaskExecutor() |
| { |
| return _taskExecutor; |
| } |
| |
| protected final <C> C runTask(Task<C> task) |
| { |
| return _taskExecutor.run(task); |
| } |
| |
| protected void runTask(VoidTask task) |
| { |
| _taskExecutor.run(task); |
| } |
| |
| protected final <T, E extends Exception> T runTask(TaskWithException<T,E> task) throws E |
| { |
| return _taskExecutor.run(task); |
| } |
| |
| protected final <E extends Exception> void runTask(VoidTaskWithException<E> task) throws E |
| { |
| _taskExecutor.run(task); |
| } |
| |
| |
| @Override |
| public void setAttributes(final Map<String, Object> attributes) throws IllegalStateException, AccessControlException, IllegalArgumentException |
| { |
| runTask(new VoidTask() |
| { |
| @Override |
| public void execute() |
| { |
| authoriseSetAttributes(createProxyForValidation(attributes), attributes.keySet()); |
| changeAttributes(attributes); |
| } |
| }); |
| } |
| |
| protected void authoriseSetAttributes(final ConfiguredObject<?> proxyForValidation, |
| final Set<String> modifiedAttributes) |
| { |
| |
| } |
| |
| protected void changeAttributes(final Map<String, Object> attributes) |
| { |
| validateChange(createProxyForValidation(attributes), attributes.keySet()); |
| Collection<String> names = getAttributeNames(); |
| for (String name : names) |
| { |
| if (attributes.containsKey(name)) |
| { |
| Object desired = attributes.get(name); |
| Object expected = getAttribute(name); |
| if(((_attributes.get(name) != null && !_attributes.get(name).equals(attributes.get(name))) |
| || attributes.get(name) != null) |
| && changeAttribute(name, expected, desired)) |
| { |
| attributeSet(name, expected, desired); |
| } |
| } |
| } |
| } |
| |
| protected void validateChange(final ConfiguredObject<?> proxyForValidation, final Set<String> changedAttributes) |
| { |
| if(!getId().equals(proxyForValidation.getId())) |
| { |
| throw new IllegalConfigurationException("Cannot change existing configured object id"); |
| } |
| } |
| |
| private ConfiguredObject<?> createProxyForValidation(final Map<String, Object> attributes) |
| { |
| return (ConfiguredObject<?>) Proxy.newProxyInstance(getClass().getClassLoader(), |
| new Class<?>[]{_bestFitInterface}, |
| new AttributeGettingHandler(attributes)); |
| } |
| |
| protected void authoriseSetDesiredState(State desiredState) 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 |
| } |
| |
| @Override |
| public final String getLastUpdatedBy() |
| { |
| return _lastUpdatedBy; |
| } |
| |
| @Override |
| public final long getLastUpdatedTime() |
| { |
| return _lastUpdatedTime; |
| } |
| |
| @Override |
| public final String getCreatedBy() |
| { |
| return _createdBy; |
| } |
| |
| @Override |
| public final long getCreatedTime() |
| { |
| return _createdTime; |
| } |
| |
| @Override |
| public final String getType() |
| { |
| return _type; |
| } |
| |
| |
| @Override |
| public Map<String,Number> getStatistics() |
| { |
| Collection<ConfiguredObjectStatistic> stats = _model.getTypeRegistry().getStatistics(getClass()); |
| Map<String,Number> map = new HashMap<String,Number>(); |
| for(ConfiguredObjectStatistic 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 = getModel().getReachableObjects(this, clazz); |
| for(Y candidate : reachable) |
| { |
| if(candidate.getName().equals(name)) |
| { |
| return candidate; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public final <T> T getContextValue(Class<T> clazz, String propertyName) |
| { |
| AttributeValueConverter<T> converter = AttributeValueConverter.getConverter(clazz, clazz); |
| return converter.convert("${" + propertyName + "}", this); |
| } |
| |
| @Override |
| public Set<String> getContextKeys(final boolean excludeSystem) |
| { |
| Map<String,String> inheritedContext = new HashMap<>(_model.getTypeRegistry().getDefaultContext()); |
| if(!excludeSystem) |
| { |
| inheritedContext.putAll(System.getenv()); |
| inheritedContext.putAll((Map) System.getProperties()); |
| } |
| generateInheritedContext(getModel(), this, inheritedContext); |
| return Collections.unmodifiableSet(inheritedContext.keySet()); |
| } |
| |
| private OwnAttributeResolver getOwnAttributeResolver() |
| { |
| return _attributeResolver; |
| } |
| |
| protected boolean isAttributePersisted(String name) |
| { |
| ConfiguredObjectAttribute<X,?> attr = (ConfiguredObjectAttribute<X, ?>) _attributeTypes.get(name); |
| if(attr != null) |
| { |
| return attr.isPersisted(); |
| } |
| return false; |
| } |
| |
| @Override |
| public void decryptSecrets() |
| { |
| if(_encrypter != null) |
| { |
| for (Map.Entry<String, Object> entry : _attributes.entrySet()) |
| { |
| ConfiguredObjectAttribute<X, ?> attr = |
| (ConfiguredObjectAttribute<X, ?>) _attributeTypes.get(entry.getKey()); |
| if (attr != null |
| && attr.isSecure() |
| && entry.getValue() instanceof String) |
| { |
| String decrypt = _encrypter.decrypt((String) entry.getValue()); |
| entry.setValue(decrypt); |
| } |
| |
| } |
| } |
| } |
| |
| //========================================================================================= |
| |
| static String interpolate(ConfiguredObject<?> object, String value) |
| { |
| if(object == null) |
| { |
| return value; |
| } |
| else |
| { |
| Map<String, String> inheritedContext = new HashMap<String, String>(); |
| generateInheritedContext(object.getModel(), object, inheritedContext); |
| return Strings.expand(value, false, |
| JSON_SUBSTITUTION_RESOLVER, |
| getOwnAttributeResolver(object), |
| new Strings.MapResolver(inheritedContext), |
| Strings.JAVA_SYS_PROPS_RESOLVER, |
| Strings.ENV_VARS_RESOLVER, |
| object.getModel().getTypeRegistry().getDefaultContextResolver()); |
| } |
| } |
| |
| private static OwnAttributeResolver getOwnAttributeResolver(final ConfiguredObject<?> object) |
| { |
| return object instanceof AbstractConfiguredObject |
| ? ((AbstractConfiguredObject)object).getOwnAttributeResolver() |
| : new OwnAttributeResolver(object); |
| } |
| |
| static void generateInheritedContext(final Model model, final ConfiguredObject<?> object, |
| final Map<String, String> inheritedContext) |
| { |
| Collection<Class<? extends ConfiguredObject>> parents = |
| model.getParentTypes(object.getCategoryClass()); |
| if(parents != null && !parents.isEmpty()) |
| { |
| ConfiguredObject parent = object.getParent(parents.iterator().next()); |
| if(parent != null) |
| { |
| generateInheritedContext(model, parent, inheritedContext); |
| } |
| } |
| if(object.getContext() != null) |
| { |
| inheritedContext.putAll(object.getContext()); |
| } |
| } |
| |
| |
| private static final Strings.Resolver JSON_SUBSTITUTION_RESOLVER = |
| Strings.createSubstitutionResolver("json:", |
| new LinkedHashMap<String, String>() |
| { |
| { |
| put("\\","\\\\"); |
| put("\"","\\\""); |
| } |
| }); |
| |
| private static class OwnAttributeResolver implements Strings.Resolver |
| { |
| private static final Module _module; |
| static |
| { |
| SimpleModule module= new SimpleModule("ConfiguredObjectSerializer", new Version(1,0,0,null)); |
| |
| final JsonSerializer<ConfiguredObject> serializer = new JsonSerializer<ConfiguredObject>() |
| { |
| @Override |
| public void serialize(final ConfiguredObject value, |
| final JsonGenerator jgen, |
| final SerializerProvider provider) |
| throws IOException, JsonProcessingException |
| { |
| jgen.writeString(value.getId().toString()); |
| } |
| }; |
| module.addSerializer(ConfiguredObject.class, serializer); |
| |
| _module = module; |
| } |
| |
| |
| public static final String PREFIX = "this:"; |
| private final ThreadLocal<Set<String>> _stack = new ThreadLocal<>(); |
| private final ConfiguredObject<?> _object; |
| private final ObjectMapper _objectMapper; |
| |
| public OwnAttributeResolver(final ConfiguredObject<?> object) |
| { |
| _object = object; |
| _objectMapper = new ObjectMapper(); |
| _objectMapper.registerModule(_module); |
| } |
| |
| @Override |
| public String resolve(final String variable, final Strings.Resolver resolver) |
| { |
| boolean clearStack = false; |
| Set<String> currentStack = _stack.get(); |
| if(currentStack == null) |
| { |
| currentStack = new HashSet<>(); |
| _stack.set(currentStack); |
| clearStack = true; |
| } |
| |
| try |
| { |
| if(variable.startsWith(PREFIX)) |
| { |
| String attrName = variable.substring(PREFIX.length()); |
| if(currentStack.contains(attrName)) |
| { |
| throw new IllegalArgumentException("The value of attribute " + attrName + " is defined recursively"); |
| } |
| else |
| { |
| currentStack.add(attrName); |
| Object returnVal = _object.getAttribute(attrName); |
| String returnString; |
| if(returnVal == null) |
| { |
| returnString = null; |
| } |
| else if(returnVal instanceof Map || returnVal instanceof Collection) |
| { |
| try |
| { |
| StringWriter writer = new StringWriter(); |
| |
| _objectMapper.writeValue(writer, returnVal); |
| |
| returnString = writer.toString(); |
| } |
| catch (IOException e) |
| { |
| throw new IllegalArgumentException(e); |
| } |
| } |
| else if(returnVal instanceof ConfiguredObject) |
| { |
| returnString = ((ConfiguredObject)returnVal).getId().toString(); |
| } |
| else |
| { |
| returnString = returnVal.toString(); |
| } |
| |
| return returnString; |
| } |
| } |
| else |
| { |
| return null; |
| } |
| } |
| finally |
| { |
| if(clearStack) |
| { |
| _stack.remove(); |
| } |
| |
| } |
| } |
| } |
| |
| |
| private class AttributeGettingHandler implements InvocationHandler |
| { |
| private Map<String,Object> _attributes; |
| |
| AttributeGettingHandler(final Map<String, Object> modifiedAttributes) |
| { |
| Map<String,Object> combinedAttributes = new HashMap<String, Object>(getActualAttributes()); |
| combinedAttributes.putAll(modifiedAttributes); |
| _attributes = combinedAttributes; |
| } |
| |
| @Override |
| public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable |
| { |
| |
| if(method.isAnnotationPresent(ManagedAttribute.class)) |
| { |
| ConfiguredObjectAttribute attribute = getAttributeFromMethod(method); |
| return getValue(attribute); |
| } |
| else if(method.getName().equals("getAttribute") && args != null && args.length == 1 && args[0] instanceof String) |
| { |
| ConfiguredObjectAttribute attribute = _attributeTypes.get((String)args[0]); |
| if(attribute != null) |
| { |
| return getValue(attribute); |
| } |
| else |
| { |
| return null; |
| } |
| } |
| throw new UnsupportedOperationException("This class is only intended for value validation, and only getters on managed attributes are permitted."); |
| } |
| |
| protected Object getValue(final ConfiguredObjectAttribute attribute) |
| { |
| if(attribute.isAutomated()) |
| { |
| ConfiguredAutomatedAttribute autoAttr = (ConfiguredAutomatedAttribute)attribute; |
| Object value = _attributes.get(attribute.getName()); |
| return attribute.convert(value == null && !"".equals(autoAttr.defaultValue()) ? autoAttr.defaultValue() : value , AbstractConfiguredObject.this); |
| } |
| else |
| { |
| return _attributes.get(attribute.getName()); |
| } |
| } |
| |
| private ConfiguredObjectAttribute getAttributeFromMethod(final Method method) |
| { |
| for(ConfiguredObjectAttribute attribute : _attributeTypes.values()) |
| { |
| if(attribute.getGetter().getName().equals(method.getName()) |
| && !Modifier.isStatic(method.getModifiers())) |
| { |
| return attribute; |
| } |
| } |
| throw new ServerScopedRuntimeException("Unable to find attribute definition for method " + method.getName()); |
| } |
| } |
| |
| protected final static class DuplicateIdException extends IllegalArgumentException |
| { |
| public DuplicateIdException(final ConfiguredObject<?> child) |
| { |
| super("Child of type " + child.getClass().getSimpleName() + " already exists with id of " + child.getId()); |
| } |
| } |
| |
| protected final static class DuplicateNameException extends IllegalArgumentException |
| { |
| private final String _name; |
| public DuplicateNameException(final ConfiguredObject<?> child) |
| { |
| super("Child of type " + child.getClass().getSimpleName() + " already exists with name of " + child.getName()); |
| _name = child.getName(); |
| } |
| |
| public String getName() |
| { |
| return _name; |
| } |
| } |
| |
| interface AbstractConfiguredObjectExceptionHandler |
| { |
| void handleException(RuntimeException exception, AbstractConfiguredObject<?> source); |
| } |
| |
| private static class OpenExceptionHandler implements AbstractConfiguredObjectExceptionHandler |
| { |
| @Override |
| public void handleException(RuntimeException exception, AbstractConfiguredObject<?> source) |
| { |
| source.handleExceptionOnOpen(exception); |
| } |
| } |
| |
| private static class CreateExceptionHandler implements AbstractConfiguredObjectExceptionHandler |
| { |
| private final boolean _unregister; |
| |
| private CreateExceptionHandler() |
| { |
| this(false); |
| } |
| |
| private CreateExceptionHandler(boolean unregister) |
| { |
| _unregister = unregister; |
| } |
| |
| @Override |
| |
| public void handleException(RuntimeException exception, AbstractConfiguredObject<?> source) |
| { |
| try |
| { |
| if (source.getState() != State.DELETED) |
| { |
| source.delete(); |
| } |
| } |
| finally |
| { |
| if (_unregister) |
| { |
| source.unregister(false); |
| } |
| throw exception; |
| } |
| } |
| } |
| } |