Java tutorial
/* * * 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.ParameterizedType; import java.lang.reflect.Proxy; import java.lang.reflect.Type; import java.security.AccessControlContext; import java.security.AccessControlException; import java.security.AccessController; import java.security.Principal; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; 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.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; import javax.security.auth.Subject; import javax.security.auth.SubjectDomainCombiner; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.util.concurrent.AbstractFuture; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.logging.EventLogger; import org.apache.qpid.server.logging.EventLoggerProvider; import org.apache.qpid.server.logging.OperationLogMessage; import org.apache.qpid.server.model.preferences.UserPreferences; import org.apache.qpid.server.security.AccessControl; import org.apache.qpid.server.security.Result; import org.apache.qpid.server.security.SecurityToken; import org.apache.qpid.server.security.access.Operation; import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; import org.apache.qpid.server.security.auth.TaskPrincipal; import org.apache.qpid.server.security.encryption.ConfigurationSecretEncrypter; import org.apache.qpid.server.store.ConfiguredObjectRecord; import org.apache.qpid.server.store.preferences.UserPreferencesCreator; 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 = LoggerFactory.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 AccessControl _parentAccessControl; private Principal _systemPrincipal; private UserPreferences _userPreferences; private enum DynamicState { UNINIT, OPENED, CLOSED }; private static class DynamicStateWithFuture { private final DynamicState _dynamicState; private final ListenableFuture<Void> _future; private DynamicStateWithFuture(final DynamicState dynamicState, final ListenableFuture<Void> future) { _dynamicState = dynamicState; _future = future; } public DynamicState getDynamicState() { return _dynamicState; } public ListenableFuture<Void> getFuture() { return _future; } } private static final DynamicStateWithFuture UNINIT = new DynamicStateWithFuture(DynamicState.UNINIT, Futures.<Void>immediateFuture(null)); private static final DynamicStateWithFuture OPENED = new DynamicStateWithFuture(DynamicState.OPENED, Futures.<Void>immediateFuture(null)); private final AtomicReference<DynamicStateWithFuture> _dynamicState = new AtomicReference<>(UNINIT); private final Map<String, Object> _attributes = new HashMap<>(); private final ConfiguredObject<?> _parent; private final Collection<ConfigurationChangeListener> _changeListeners = new ArrayList<>(); private final Map<Class<? extends ConfiguredObject>, Collection<ConfiguredObject<?>>> _children = new ConcurrentHashMap<>(); private final Map<Class<? extends ConfiguredObject>, ConcurrentMap<UUID, ConfiguredObject<?>>> _childrenById = new ConcurrentHashMap<>(); private final Map<Class<? extends ConfiguredObject>, ConcurrentMap<String, ConfiguredObject<?>>> _childrenByName = new ConcurrentHashMap<>(); @ManagedAttributeField private final UUID _id; private final TaskExecutor _taskExecutor; private final Class<? extends ConfiguredObject> _category; private final Class<? extends ConfiguredObject> _typeClass; private final Class<? extends ConfiguredObject> _bestFitInterface; private volatile Model _model; private final boolean _managesChildStorage; @ManagedAttributeField private Date _createdTime; @ManagedAttributeField private String _createdBy; @ManagedAttributeField private Date _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 _ownAttributeResolver = new OwnAttributeResolver(this); private final AncestorAttributeResolver _ancestorAttributeResolver = new AncestorAttributeResolver(this); @ManagedAttributeField private State _desiredState; private volatile SettableFuture<ConfiguredObject<X>> _attainStateFuture = SettableFuture.create(); private boolean _openComplete; private boolean _openFailed; private volatile State _state = State.UNINITIALIZED; private volatile Date _lastOpenedTime; private volatile int _awaitAttainmentTimeout; protected AbstractConfiguredObject(final ConfiguredObject<?> parent, Map<String, Object> attributes) { this(parent, attributes, parent.getChildExecutor()); } protected AbstractConfiguredObject(final ConfiguredObject<?> parent, Map<String, Object> attributes, TaskExecutor taskExecutor) { this(parent, attributes, taskExecutor, parent.getModel()); } protected AbstractConfiguredObject(final ConfiguredObject<?> parent, Map<String, Object> attributes, TaskExecutor taskExecutor, Model model) { _taskExecutor = taskExecutor; if (taskExecutor == null) { throw new NullPointerException("task executor is null"); } _model = model; _parent = parent; _category = ConfiguredObjectTypeRegistry.getCategory(getClass()); Class<? extends ConfiguredObject> typeClass = model.getTypeRegistry().getTypeClass(getClass()); _typeClass = typeClass == null ? _category : typeClass; _attributeTypes = model.getTypeRegistry().getAttributeTypes(getClass()); _automatedFields = model.getTypeRegistry().getAutomatedFields(getClass()); _stateChangeMethods = model.getTypeRegistry().getStateChangeMethods(getClass()); if (_parent instanceof AbstractConfiguredObject && ((AbstractConfiguredObject) _parent)._encrypter != null) { _encrypter = ((AbstractConfiguredObject) _parent)._encrypter; } else if (_parent instanceof ConfigurationSecretEncrypterSource && ((ConfigurationSecretEncrypterSource) _parent).getEncrypter() != null) { _encrypter = ((ConfigurationSecretEncrypterSource) _parent).getEncrypter(); } if (_parent instanceof AbstractConfiguredObject && ((AbstractConfiguredObject) _parent).getAccessControl() != null) { _parentAccessControl = ((AbstractConfiguredObject) _parent).getAccessControl(); } else if (_parent instanceof AccessControlSource && ((AccessControlSource) _parent).getAccessControl() != null) { _parentAccessControl = ((AccessControlSource) _parent).getAccessControl(); } if (_parent instanceof AbstractConfiguredObject && ((AbstractConfiguredObject) _parent).getSystemPrincipal() != null) { _systemPrincipal = ((AbstractConfiguredObject) _parent).getSystemPrincipal(); } else if (_parent instanceof SystemPrincipalSource && ((SystemPrincipalSource) _parent).getSystemPrincipal() != null) { _systemPrincipal = ((SystemPrincipalSource) _parent).getSystemPrincipal(); } Object idObj = attributes.get(ID); UUID uuid; if (idObj == null) { uuid = UUID.randomUUID(); attributes = new LinkedHashMap<>(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()); _managesChildStorage = managesChildren(_category) || managesChildren(_typeClass); _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); } else if (attributes.get(TYPE) == null) { attributes = new LinkedHashMap<>(attributes); attributes.put(TYPE, _type); } populateChildTypeMaps(); Object durableObj = attributes.get(DURABLE); _durable = AttributeValueConverter.BOOLEAN_CONVERTER.convert( durableObj == null ? ((ConfiguredSettableAttribute) (_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 = AuthenticatedPrincipal.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.isDerived()) { ConfiguredSettableAttribute<?, ?> autoAttr = (ConfiguredSettableAttribute<?, ?>) 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()); } } } } protected final void updateModel(Model model) { if (this instanceof DynamicModel && _children.isEmpty() && _model.getChildTypes(getCategoryClass()).isEmpty() && Model.isSpecialization(_model, model, getCategoryClass())) { _model = model; populateChildTypeMaps(); } else { throw new IllegalStateException( "Cannot change the model of a class which does not implement DynamicModel, or has defined child types"); } } private void populateChildTypeMaps() { if (!(_children.isEmpty() && _childrenById.isEmpty() && _childrenByName.isEmpty())) { throw new IllegalStateException( "Cannot update the child type maps on a class with pre-existing child types"); } 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<?>>()); } } private boolean managesChildren(final Class<? extends ConfiguredObject> clazz) { return clazz.getAnnotation(ManagedObject.class).managesChildren(); } 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); } Object desiredValue = attribute.convert(value, this); field.getField().set(this, desiredValue); 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); } } private boolean checkValidValues(final ConfiguredSettableAttribute attribute, final Object desiredValue) { for (Object validValue : attribute.validValues()) { Object convertedValidValue = attribute.getConverter().convert(validValue, this); if (convertedValidValue.equals(desiredValue)) { return true; } } return false; } private boolean checkValidValuePattern(final ConfiguredSettableAttribute attribute, final Object desiredValue) { Collection<String> valuesToCheck; if (attribute.getType().equals(String.class)) { valuesToCheck = Collections.singleton(desiredValue.toString()); } else if (Collection.class.isAssignableFrom(attribute.getType()) && attribute.getGenericType() instanceof ParameterizedType) { ParameterizedType paramType = (ParameterizedType) attribute.getGenericType(); if (paramType.getActualTypeArguments().length == 1 && paramType.getActualTypeArguments()[0] == String.class) { valuesToCheck = (Collection<String>) desiredValue; } else { valuesToCheck = Collections.emptySet(); } } else { valuesToCheck = Collections.emptySet(); } Pattern pattern = Pattern.compile(attribute.validValuePattern()); for (String value : valuesToCheck) { if (!pattern.matcher(value).matches()) { return false; } } return true; } @Override public final void open() { doSync(openAsync()); } public final ListenableFuture<Void> openAsync() { return doOnConfigThread(new Task<ListenableFuture<Void>, RuntimeException>() { @Override public ListenableFuture<Void> execute() { if (_dynamicState.compareAndSet(UNINIT, OPENED)) { _openFailed = false; OpenExceptionHandler exceptionHandler = new OpenExceptionHandler(); try { doResolution(true, exceptionHandler); doValidation(true, exceptionHandler); doOpening(true, exceptionHandler); return doAttainState(exceptionHandler); } catch (RuntimeException e) { exceptionHandler.handleException(e, AbstractConfiguredObject.this); return Futures.immediateFuture(null); } } else { return Futures.immediateFuture(null); } } @Override public String getObject() { return AbstractConfiguredObject.this.toString(); } @Override public String getAction() { return "open"; } @Override public String getArguments() { return null; } }); } protected final <T, E extends Exception> ListenableFuture<T> doOnConfigThread( final Task<ListenableFuture<T>, E> task) { final SettableFuture<T> returnVal = SettableFuture.create(); _taskExecutor.submit(new Task<Void, RuntimeException>() { @Override public Void execute() { try { addFutureCallback(task.execute(), new FutureCallback<T>() { @Override public void onSuccess(final T result) { returnVal.set(result); } @Override public void onFailure(final Throwable t) { returnVal.setException(t); } }, getTaskExecutor()); } catch (Throwable t) { returnVal.setException(t); } return null; } @Override public String getObject() { return task.getObject(); } @Override public String getAction() { return task.getAction(); } @Override public String getArguments() { return task.getArguments(); } }); return returnVal; } public void registerWithParents() { if (_parent instanceof AbstractConfiguredObject<?>) { ((AbstractConfiguredObject<?>) _parent).registerChild(this); } else if (_parent instanceof AbstractConfiguredObjectProxy) { ((AbstractConfiguredObjectProxy) _parent).registerChild(this); } } protected final ListenableFuture<Void> closeChildren() { final List<ListenableFuture<Void>> childCloseFutures = new ArrayList<>(); applyToChildren(new Action<ConfiguredObject<?>>() { @Override public void performAction(final ConfiguredObject<?> child) { ListenableFuture<Void> childCloseFuture = child.closeAsync(); addFutureCallback(childCloseFuture, new FutureCallback<Void>() { @Override public void onSuccess(final Void result) { } @Override public void onFailure(final Throwable t) { LOGGER.error("Exception occurred while closing {} : {}", child.getClass().getSimpleName(), child.getName(), t); } }, getTaskExecutor()); childCloseFutures.add(childCloseFuture); } }); ListenableFuture<List<Void>> combinedFuture = Futures.allAsList(childCloseFutures); return doAfter(combinedFuture, new Runnable() { @Override public void run() { // TODO consider removing each child from the parent as each child close completes, rather // than awaiting the completion of the combined future. This would make it easy to give // clearer debug that would highlight the children that have failed to closed. 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(); } LOGGER.debug("All children closed {} : {}", AbstractConfiguredObject.this.getClass().getSimpleName(), getName()); } }); } @Override public void close() { doSync(closeAsync()); } @Override public final ListenableFuture<Void> closeAsync() { return doOnConfigThread(new Task<ListenableFuture<Void>, RuntimeException>() { @Override public ListenableFuture<Void> execute() { LOGGER.debug( "Closing " + AbstractConfiguredObject.this.getClass().getSimpleName() + " : " + getName()); final SettableFuture<Void> returnFuture = SettableFuture.create(); DynamicStateWithFuture desiredStateWithFuture = new DynamicStateWithFuture(DynamicState.CLOSED, returnFuture); DynamicStateWithFuture currentStateWithFuture; while ((currentStateWithFuture = _dynamicState.get()) == OPENED) { if (_dynamicState.compareAndSet(OPENED, desiredStateWithFuture)) { final ChainedListenableFuture<Void> future = doAfter(beforeClose(), new Callable<ListenableFuture<Void>>() { @Override public ListenableFuture<Void> call() throws Exception { return closeChildren(); } }).then(new Callable<ListenableFuture<Void>>() { @Override public ListenableFuture<Void> call() throws Exception { return onClose(); } }).then(new Callable<ListenableFuture<Void>>() { @Override public ListenableFuture<Void> call() throws Exception { unregister(false); LOGGER.debug( "Closed " + AbstractConfiguredObject.this.getClass().getSimpleName() + " : " + getName()); return Futures.immediateFuture(null); } }); addFutureCallback(future, new FutureCallback<Void>() { @Override public void onSuccess(final Void result) { returnFuture.set(null); } @Override public void onFailure(final Throwable t) { returnFuture.setException(t); } }, MoreExecutors.directExecutor()); return returnFuture; } } return currentStateWithFuture.getFuture(); } @Override public String getObject() { return AbstractConfiguredObject.this.toString(); } @Override public String getAction() { return "close"; } @Override public String getArguments() { return null; } }); } protected ListenableFuture<Void> beforeClose() { return Futures.immediateFuture(null); } protected ListenableFuture<Void> onClose() { return Futures.immediateFuture(null); } public final void create() { doSync(createAsync()); } public final ListenableFuture<Void> createAsync() { return doOnConfigThread(new Task<ListenableFuture<Void>, RuntimeException>() { @Override public ListenableFuture<Void> execute() { if (_dynamicState.compareAndSet(UNINIT, OPENED)) { initializeAttributes(); CreateExceptionHandler createExceptionHandler = new CreateExceptionHandler(); try { doResolution(true, createExceptionHandler); doValidation(true, createExceptionHandler); validateOnCreate(); registerWithParents(); createUserPreferences(); } catch (RuntimeException e) { createExceptionHandler.handleException(e, AbstractConfiguredObject.this); } final AbstractConfiguredObjectExceptionHandler unregisteringExceptionHandler = new CreateExceptionHandler( true); try { doCreation(true, unregisteringExceptionHandler); doOpening(true, unregisteringExceptionHandler); return doAttainState(unregisteringExceptionHandler); } catch (RuntimeException e) { unregisteringExceptionHandler.handleException(e, AbstractConfiguredObject.this); } } return Futures.immediateFuture(null); } @Override public String getObject() { return AbstractConfiguredObject.this.toString(); } @Override public String getAction() { return "create"; } @Override public String getArguments() { return null; } }); } private void createUserPreferences() { if (this instanceof UserPreferencesCreator) { return; } UserPreferencesCreator preferenceCreator = getAncestor(UserPreferencesCreator.class); if (preferenceCreator != null) { UserPreferences userPreferences = preferenceCreator.createUserPreferences(this); setUserPreferences(userPreferences); } } private void initializeAttributes() { final AuthenticatedPrincipal currentUser = AuthenticatedPrincipal.getCurrentUser(); if (currentUser != null) { String currentUserName = currentUser.getName(); _attributes.put(LAST_UPDATED_BY, currentUserName); _attributes.put(CREATED_BY, currentUserName); } final Date currentTime = new Date(); _attributes.put(LAST_UPDATED_TIME, currentTime); _attributes.put(CREATED_TIME, currentTime); ConfiguredObject<?> proxyForInitialization = null; for (ConfiguredObjectAttribute<?, ?> attr : _attributeTypes.values()) { if (!attr.isDerived()) { ConfiguredSettableAttribute autoAttr = (ConfiguredSettableAttribute) attr; final boolean isPresent = _attributes.containsKey(attr.getName()); final boolean hasDefault = !"".equals(autoAttr.defaultValue()); if (!isPresent && hasDefault) { switch (autoAttr.getInitialization()) { case copy: _attributes.put(autoAttr.getName(), autoAttr.defaultValue()); break; case materialize: if (proxyForInitialization == null) { proxyForInitialization = createProxyForInitialization(_attributes); } _attributes.put(autoAttr.getName(), autoAttr.convert(autoAttr.defaultValue(), proxyForInitialization)); break; } } } } } protected void validateOnCreate() { } protected boolean rethrowRuntimeExceptionsOnOpen() { return false; } protected final void handleExceptionOnOpen(RuntimeException e) { if (rethrowRuntimeExceptionsOnOpen() || 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(OPENED, 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 ListenableFuture<Void> doAttainState(final AbstractConfiguredObjectExceptionHandler exceptionHandler) { final List<ListenableFuture<Void>> childStateFutures = new ArrayList<>(); applyToChildren(new Action<ConfiguredObject<?>>() { @Override public void performAction(final ConfiguredObject<?> child) { if (child instanceof AbstractConfiguredObject) { AbstractConfiguredObject<?> abstractConfiguredChild = (AbstractConfiguredObject<?>) child; if (abstractConfiguredChild._dynamicState.get().getDynamicState() == DynamicState.OPENED) { final AbstractConfiguredObject configuredObject = abstractConfiguredChild; childStateFutures.add(configuredObject.doAttainState(exceptionHandler)); } } else if (child instanceof AbstractConfiguredObjectProxy && ((AbstractConfiguredObjectProxy) child).getDynamicState() == DynamicState.OPENED) { final AbstractConfiguredObjectProxy configuredObject = (AbstractConfiguredObjectProxy) child; childStateFutures.add(configuredObject.doAttainState(exceptionHandler)); } } }); ListenableFuture<List<Void>> combinedChildStateFuture = Futures.allAsList(childStateFutures); final SettableFuture<Void> returnVal = SettableFuture.create(); addFutureCallback(combinedChildStateFuture, new FutureCallback<List<Void>>() { @Override public void onSuccess(final List<Void> result) { try { addFutureCallback(attainState(), new FutureCallback<Void>() { @Override public void onSuccess(final Void result1) { returnVal.set(null); } @Override public void onFailure(final Throwable t) { try { if (t instanceof RuntimeException) { try { exceptionHandler.handleException((RuntimeException) t, AbstractConfiguredObject.this); returnVal.set(null); } catch (RuntimeException r) { returnVal.setException(r); } } } finally { if (!returnVal.isDone()) { returnVal.setException(t); } } } }, getTaskExecutor()); } catch (RuntimeException e) { try { exceptionHandler.handleException(e, AbstractConfiguredObject.this); returnVal.set(null); } catch (Throwable t) { returnVal.setException(t); } } } @Override public void onFailure(final Throwable t) { // One or more children failed to attain state but the error could not be handled by the handler returnVal.setException(t); } }, getTaskExecutor()); return returnVal; } protected final void doOpening(boolean skipCheck, final AbstractConfiguredObjectExceptionHandler exceptionHandler) { if (skipCheck || _dynamicState.compareAndSet(UNINIT, OPENED)) { onOpen(); notifyStateChanged(State.UNINITIALIZED, getState()); applyToChildren(new Action<ConfiguredObject<?>>() { @Override public void performAction(final ConfiguredObject<?> child) { if (child.getState() != State.ERRORED) { try { if (child instanceof AbstractConfiguredObject) { AbstractConfiguredObject configuredObject = (AbstractConfiguredObject) child; configuredObject.doOpening(false, exceptionHandler); } else if (child instanceof AbstractConfiguredObjectProxy) { AbstractConfiguredObjectProxy configuredObject = (AbstractConfiguredObjectProxy) child; configuredObject.doOpening(false, exceptionHandler); } } catch (RuntimeException e) { exceptionHandler.handleException(e, child); } } } }); _openComplete = true; _lastOpenedTime = new Date(); } } protected final void doValidation(final boolean skipCheck, final AbstractConfiguredObjectExceptionHandler exceptionHandler) { if (skipCheck || _dynamicState.get().getDynamicState() != DynamicState.OPENED) { applyToChildren(new Action<ConfiguredObject<?>>() { @Override public void performAction(final ConfiguredObject<?> child) { if (child.getState() != State.ERRORED) { try { if (child instanceof AbstractConfiguredObject) { AbstractConfiguredObject configuredObject = (AbstractConfiguredObject) child; configuredObject.doValidation(false, exceptionHandler); } else if (child instanceof AbstractConfiguredObjectProxy) { AbstractConfiguredObjectProxy configuredObject = (AbstractConfiguredObjectProxy) child; configuredObject.doValidation(false, exceptionHandler); } } catch (RuntimeException e) { exceptionHandler.handleException(e, child); } } } }); onValidate(); } } protected final void doResolution(boolean skipCheck, final AbstractConfiguredObjectExceptionHandler exceptionHandler) { if (skipCheck || _dynamicState.get().getDynamicState() != DynamicState.OPENED) { onResolve(); postResolve(); applyToChildren(new Action() { @Override public void performAction(Object child) { try { if (child instanceof AbstractConfiguredObject) { AbstractConfiguredObject configuredObject = (AbstractConfiguredObject) child; configuredObject.doResolution(false, exceptionHandler); } else if (child instanceof AbstractConfiguredObjectProxy) { AbstractConfiguredObjectProxy configuredObject = (AbstractConfiguredObjectProxy) child; configuredObject.doResolution(false, exceptionHandler); } } catch (RuntimeException e) { exceptionHandler.handleException(e, (ConfiguredObject) child); } } }); postResolveChildren(); } } protected void postResolveChildren() { } protected void postResolve() { if (getActualAttributes().get(CREATED_BY) != null) { _createdBy = (String) getActualAttributes().get(CREATED_BY); } if (getActualAttributes().get(CREATED_TIME) != null) { _createdTime = AttributeValueConverter.DATE_CONVERTER.convert(getActualAttributes().get(CREATED_TIME), this); } if (getActualAttributes().get(LAST_UPDATED_BY) != null) { _lastUpdatedBy = (String) getActualAttributes().get(LAST_UPDATED_BY); } if (getActualAttributes().get(LAST_UPDATED_TIME) != null) { _lastUpdatedTime = AttributeValueConverter.DATE_CONVERTER .convert(getActualAttributes().get(LAST_UPDATED_TIME), this); } } protected final void doCreation(final boolean skipCheck, final AbstractConfiguredObjectExceptionHandler exceptionHandler) { if (skipCheck || _dynamicState.get().getDynamicState() != DynamicState.OPENED) { onCreate(); applyToChildren(new Action<ConfiguredObject<?>>() { @Override public void performAction(final ConfiguredObject<?> child) { try { if (child instanceof AbstractConfiguredObject) { AbstractConfiguredObject configuredObject = (AbstractConfiguredObject) child; configuredObject.doCreation(false, exceptionHandler); } else if (child instanceof AbstractConfiguredObjectProxy) { AbstractConfiguredObjectProxy configuredObject = (AbstractConfiguredObjectProxy) child; configuredObject.doCreation(false, exceptionHandler); } } catch (RuntimeException e) { exceptionHandler.handleException(e, child); } } }); } } 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); } } } } /** * Validation performed for configured object creation and opening. * * @throws IllegalConfigurationException indicates invalid configuration */ public void onValidate() { for (ConfiguredObjectAttribute<?, ?> attr : _attributeTypes.values()) { if (!attr.isDerived()) { ConfiguredSettableAttribute autoAttr = (ConfiguredSettableAttribute) attr; if (autoAttr.hasValidValues()) { Object desiredValueOrDefault = autoAttr.getValue(this); if (desiredValueOrDefault != null && !checkValidValues(autoAttr, desiredValueOrDefault)) { throw new IllegalConfigurationException("Attribute '" + autoAttr.getName() + "' instance of " + getClass().getName() + " named '" + getName() + "'" + " cannot have value '" + desiredValueOrDefault + "'" + ". Valid values are: " + autoAttr.validValues()); } } else if (!"".equals(autoAttr.validValuePattern())) { Object desiredValueOrDefault = autoAttr.getValue(this); if (desiredValueOrDefault != null && !checkValidValuePattern(autoAttr, desiredValueOrDefault)) { throw new IllegalConfigurationException("Attribute '" + autoAttr.getName() + "' instance of " + getClass().getName() + " named '" + getName() + "'" + " cannot have value '" + desiredValueOrDefault + "'" + ". Valid values pattern is: " + autoAttr.validValuePattern()); } } if (autoAttr.isMandatory() && autoAttr.getValue(this) == null) { throw new IllegalConfigurationException( "Attribute '" + autoAttr.getName() + "' instance of " + getClass().getName() + " named '" + getName() + "'" + " cannot be null, as it is mandatory"); } } } } protected final void setEncrypter(final ConfigurationSecretEncrypter encrypter) { _encrypter = encrypter; applyToChildren(new Action<ConfiguredObject<?>>() { @Override public void performAction(final ConfiguredObject<?> object) { if (object instanceof AbstractConfiguredObject) { ((AbstractConfiguredObject) object).setEncrypter(encrypter); } } }); } protected void onResolve() { Set<ConfiguredObjectAttribute<?, ?>> unresolved = new HashSet<>(); Set<ConfiguredObjectAttribute<?, ?>> derived = new HashSet<>(); for (ConfiguredObjectAttribute<?, ?> attr : _attributeTypes.values()) { if (attr.isDerived()) { derived.add(attr); } else { unresolved.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.isDerived()) { if (contextAttribute.isAutomated()) { resolveAutomatedAttribute((ConfiguredSettableAttribute<?, ?>) 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)))) { if (attr.isAutomated()) { resolveAutomatedAttribute((ConfiguredSettableAttribute<?, ?>) 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(((ConfiguredSettableAttribute) attr).defaultValue())) { value = ((ConfiguredSettableAttribute) 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 ConfiguredSettableAttribute<?, ?> autoAttr) { String attrName = autoAttr.getName(); if (_attributes.containsKey(attrName)) { automatedSetValue(attrName, _attributes.get(attrName)); } else if (!"".equals(autoAttr.defaultValue())) { automatedSetValue(attrName, autoAttr.defaultValue()); } } private ListenableFuture<Void> attainStateIfOpenedOrReopenFailed() { if (_openComplete || getDesiredState() == State.DELETED) { return attainState(); } else if (_openFailed) { return openAsync(); } return Futures.immediateFuture(null); } protected void onOpen() { } protected ListenableFuture<Void> attainState() { return attainState(getDesiredState()); } private ListenableFuture<Void> attainState(State desiredState) { final State currentState = getState(); ListenableFuture<Void> returnVal; if (_attainStateFuture.isDone()) { _attainStateFuture = SettableFuture.create(); } if (currentState != desiredState) { Method stateChangingMethod = getStateChangeMethod(currentState, desiredState); if (stateChangingMethod != null) { try { final SettableFuture<Void> stateTransitionResult = SettableFuture.create(); ListenableFuture<Void> stateTransitionFuture = (ListenableFuture<Void>) stateChangingMethod .invoke(this); addFutureCallback(stateTransitionFuture, new FutureCallback<Void>() { @Override public void onSuccess(Void result) { try { if (getState() != currentState) { notifyStateChanged(currentState, getState()); } stateTransitionResult.set(null); } catch (Throwable e) { stateTransitionResult.setException(e); } finally { _attainStateFuture.set(AbstractConfiguredObject.this); } } @Override public void onFailure(Throwable t) { // state transition failed to attain desired state // setting the _attainStateFuture, so, object relying on it could get the configured object _attainStateFuture.set(AbstractConfiguredObject.this); stateTransitionResult.setException(t); } }, getTaskExecutor()); returnVal = stateTransitionResult; } catch (IllegalAccessException e) { throw new ServerScopedRuntimeException( "Unexpected access exception when calling state transition", e); } catch (InvocationTargetException e) { // state transition failed to attain desired state // setting the _attainStateFuture, so, object relying on it could get the configured object _attainStateFuture.set(this); 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); } } else { returnVal = Futures.immediateFuture(null); _attainStateFuture.set(this); } } else { returnVal = Futures.immediateFuture(null); _attainStateFuture.set(this); } return returnVal; } 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 getModel().getObjectFactory(); } @Override public final Model getModel() { return _model; } @Override public Class<? extends ConfiguredObject> getCategoryClass() { return _category; } @Override public Class<? extends ConfiguredObject> getTypeClass() { return _typeClass; } @Override public boolean managesChildStorage() { return _managesChildStorage; } public Map<String, String> getContext() { return _context == null ? Collections.<String, String>emptyMap() : Collections.unmodifiableMap(_context); } public State getDesiredState() { return _desiredState; } private ListenableFuture<Void> setDesiredState(final State desiredState) throws IllegalStateTransitionException, AccessControlException { return doOnConfigThread(new Task<ListenableFuture<Void>, RuntimeException>() { @Override public ListenableFuture<Void> execute() { final State state = getState(); final State currentDesiredState = getDesiredState(); if (desiredState == currentDesiredState && desiredState != state) { return doAfter(attainStateIfOpenedOrReopenFailed(), new Runnable() { @Override public void run() { final State currentState = getState(); if (currentState != state) { notifyStateChanged(state, currentState); } } }); } else { ConfiguredObject<?> proxyForValidation = createProxyForValidation( Collections.<String, Object>singletonMap(ConfiguredObject.DESIRED_STATE, desiredState)); Set<String> desiredStateOnlySet = Collections .unmodifiableSet(Collections.singleton(ConfiguredObject.DESIRED_STATE)); authoriseSetAttributes(proxyForValidation, desiredStateOnlySet); validateChange(proxyForValidation, desiredStateOnlySet); if (desiredState == State.DELETED) { // for DELETED state we should invoke transition method first to make sure that object can be deleted. // If method results in exception being thrown due to various integrity violations // then object cannot be deleted without prior resolving of integrity violations. // The state transition should be disallowed. if (desiredState != currentDesiredState) { if (_parent instanceof AbstractConfiguredObject) { ((AbstractConfiguredObject<?>) _parent) .validateChildDelete(AbstractConfiguredObject.this); } else if (_parent instanceof AbstractConfiguredObjectProxy) { ((AbstractConfiguredObjectProxy) _parent) .validateChildDelete(AbstractConfiguredObject.this); } return doAfter(attainState(desiredState), new Runnable() { @Override public void run() { // state transition notification should be already issued. // changing attribute value and notifying listeners about attribute change // in case when any listener relies on attribute change rather then on state change changeAttribute(ConfiguredObject.DESIRED_STATE, desiredState); attributeSet(ConfiguredObject.DESIRED_STATE, currentDesiredState, desiredState); } }); } else { return Futures.immediateFuture(null); } } else { if (changeAttribute(ConfiguredObject.DESIRED_STATE, desiredState)) { attributeSet(ConfiguredObject.DESIRED_STATE, currentDesiredState, desiredState); return attainStateIfOpenedOrReopenFailed(); } else { return Futures.immediateFuture(null); } } } } @Override public String getObject() { return AbstractConfiguredObject.this.toString(); } @Override public String getAction() { return "set desired state"; } @Override public String getArguments() { return String.valueOf(desiredState); } }); } protected void validateChildDelete(final ConfiguredObject<?> child) { } @Override public State getState() { return _state; } protected void setState(State state) { _state = state; } protected void notifyStateChanged(final State currentState, final State desiredState) { List<ConfigurationChangeListener> copy; synchronized (_changeListeners) { 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 final void childAdded(ConfiguredObject<?> child) { synchronized (_changeListeners) { List<ConfigurationChangeListener> copy = new ArrayList<>(_changeListeners); for (ConfigurationChangeListener listener : copy) { listener.childAdded(this, child); } } } protected final void childRemoved(ConfiguredObject<?> child) { synchronized (_changeListeners) { List<ConfigurationChangeListener> copy = new ArrayList<>(_changeListeners); for (ConfigurationChangeListener listener : copy) { listener.childRemoved(this, child); } } } protected void attributeSet(String attributeName, Object oldAttributeValue, Object newAttributeValue) { final AuthenticatedPrincipal currentUser = AuthenticatedPrincipal.getCurrentUser(); if (currentUser != null) { _attributes.put(LAST_UPDATED_BY, currentUser.getName()); _lastUpdatedBy = currentUser.getName(); } final Date currentTime = new Date(); _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) { Object value = attr.getValue((X) this); if (value != null && !isSystemProcess() && attr.isSecureValue(value)) { return SECURE_VALUES.get(value.getClass()); } else { 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); } } protected boolean changeAttribute(final String name, final Object desired) { synchronized (_attributes) { Object actualValue = _attributes.get(name); ConfiguredObjectAttribute<?, ?> attr = _attributeTypes.get(name); if (attr.updateAttributeDespiteUnchangedValue() || ((actualValue != null && !actualValue.equals(desired)) || (actualValue == null && desired != null))) { //TODO: don't put nulls _attributes.put(name, desired); if (attr != null && attr.isAutomated()) { automatedSetValue(name, desired); } return true; } else { return false; } } } @Override public ConfiguredObject<?> getParent() { return _parent; } public final <T> T getAncestor(final Class<T> clazz) { return getModel().getAncestor(clazz, this); } public final Collection<String> getAttributeNames() { return getTypeRegistry().getAttributeNames(getClass()); } @Override public String toString() { return getCategoryClass().getSimpleName() + "[id=" + _id + ", name=" + getName() + ", type=" + getType() + "]"; } 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(getSubjectWithAddedSystemRights(), new PrivilegedAction<Map<String, Object>>() { @Override public Map<String, Object> run() { Map<String, Object> attributes = new LinkedHashMap<>(); Map<String, Object> actualAttributes = getActualAttributes(); for (ConfiguredObjectAttribute<?, ?> attr : _attributeTypes.values()) { if (attr.isPersisted() && !ID.equals(attr.getName())) { if (attr.isDerived()) { Object value = getAttribute(attr.getName()); attributes.put(attr.getName(), toRecordedForm(attr, value)); } else if (actualAttributes.containsKey(attr.getName())) { Object value = actualAttributes.get(attr.getName()); attributes.put(attr.getName(), toRecordedForm(attr, value)); } } } return attributes; } }); } public Object toRecordedForm(final ConfiguredObjectAttribute<?, ?> attr, Object value) { if (value instanceof ConfiguredObject) { value = ((ConfiguredObject) value).getId(); } if (attr.isSecure() && _encrypter != null && value != null) { if (value instanceof Collection || value instanceof Map) { ObjectMapper mapper = ConfiguredObjectJacksonModule.newObjectMapper(false); 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()); } } return value; } @Override public Map<String, UUID> getParents() { Map<String, UUID> parents = new LinkedHashMap<>(); Class<? extends ConfiguredObject> parentClass = getModel().getParentType(getCategoryClass()); ConfiguredObject parent = (ConfiguredObject) getParent(); if (parent != null) { parents.put(parentClass.getSimpleName(), parent.getId()); } return parents; } @Override public String toString() { return AbstractConfiguredObject.this.getClass().getSimpleName() + "[name=" + getName() + ", categoryClass=" + getCategoryClass() + ", type=" + getType() + ", id=" + getId() + ", attributes=" + getAttributes() + "]"; } }; } @SuppressWarnings("unchecked") @Override public <C extends ConfiguredObject> C createChild(final Class<C> childClass, final Map<String, Object> attributes) { return doSync(createChildAsync(childClass, attributes)); } @SuppressWarnings("unchecked") @Override public <C extends ConfiguredObject> ListenableFuture<C> createChildAsync(final Class<C> childClass, final Map<String, Object> attributes) { return doOnConfigThread(new Task<ListenableFuture<C>, RuntimeException>() { @Override public ListenableFuture<C> execute() { authoriseCreateChild(childClass, attributes); return doAfter(addChildAsync(childClass, attributes), new CallableWithArgument<ListenableFuture<C>, C>() { @Override public ListenableFuture<C> call(final C child) throws Exception { if (child != null) { childAdded(child); } return Futures.immediateFuture(child); } }); } @Override public String getObject() { return AbstractConfiguredObject.this.toString(); } @Override public String getAction() { return "create child"; } @Override public String getArguments() { if (attributes != null) { return "childClass=" + childClass.getSimpleName() + ", name=" + attributes.get(NAME) + ", type=" + attributes.get(TYPE); } return "childClass=" + childClass.getSimpleName(); } }); } protected <C extends ConfiguredObject> ListenableFuture<C> addChildAsync(Class<C> childClass, Map<String, Object> attributes) { throw new UnsupportedOperationException(); } private <C extends ConfiguredObject> void registerChild(final C child) { synchronized (_children) { Class categoryClass = child.getCategoryClass(); UUID childId = child.getId(); String name = child.getName(); ConfiguredObject<?> existingWithSameId = _childrenById.get(categoryClass).get(childId); if (existingWithSameId != null) { throw new DuplicateIdException(existingWithSameId); } ConfiguredObject<?> existingWithSameName = _childrenByName.get(categoryClass).putIfAbsent(name, child); if (existingWithSameName != null) { throw new DuplicateNameException(existingWithSameName); } _childrenByName.get(categoryClass).put(name, child); _children.get(categoryClass).add(child); _childrenById.get(categoryClass).put(childId, child); } } public final void stop() { doSync(setDesiredState(State.STOPPED)); } public final void delete() { doSync(deleteAsync()); } protected final <R> R doSync(ListenableFuture<R> async) { try { return async.get(); } catch (InterruptedException e) { throw new ServerScopedRuntimeException(e); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else if (cause instanceof Error) { throw (Error) cause; } else if (cause != null) { throw new ServerScopedRuntimeException(cause); } else { throw new ServerScopedRuntimeException(e); } } } protected final <R> R doSync(ListenableFuture<R> async, long timeout, TimeUnit units) throws TimeoutException { try { return async.get(timeout, units); } catch (InterruptedException e) { throw new ServerScopedRuntimeException(e); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else if (cause instanceof Error) { throw (Error) cause; } else if (cause != null) { throw new ServerScopedRuntimeException(cause); } else { throw new ServerScopedRuntimeException(e); } } } public final ListenableFuture<Void> deleteAsync() { return setDesiredState(State.DELETED); } public final void start() { doSync(startAsync()); } public ListenableFuture<Void> startAsync() { return setDesiredState(State.ACTIVE); } protected void deleted() { unregister(true); } private void unregister(boolean removed) { if (_parent instanceof AbstractConfiguredObject<?>) { AbstractConfiguredObject<?> parentObj = (AbstractConfiguredObject<?>) _parent; parentObj.unregisterChild(this); if (removed) { parentObj.childRemoved(this); } } else if (_parent instanceof AbstractConfiguredObjectProxy) { AbstractConfiguredObjectProxy parentObj = (AbstractConfiguredObjectProxy) _parent; parentObj.unregisterChild(this); if (removed) { parentObj.childRemoved(this); } } } private <C extends ConfiguredObject> void unregisterChild(final C child) { Class categoryClass = child.getCategoryClass(); synchronized (_children) { _children.get(categoryClass).remove(child); _childrenById.get(categoryClass).remove(child.getId(), child); _childrenByName.get(categoryClass).remove(child.getName(), child); } } @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); return (C) _childrenByName.get(categoryClass).get(name); } @Override public <C extends ConfiguredObject> Collection<C> getChildren(final Class<C> clazz) { Collection<ConfiguredObject<?>> children = _children.get(clazz); if (children == null) { return Collections.EMPTY_LIST; } return Collections.unmodifiableList((List<? extends C>) children); } @Override public <C extends ConfiguredObject> ListenableFuture<C> getAttainedChildByName(final Class<C> childClass, final String name) { C child = getChildByName(childClass, name); if (child instanceof AbstractConfiguredObject) { return ((AbstractConfiguredObject) child).getAttainStateFuture(); } else if (child instanceof AbstractConfiguredObjectProxy) { return ((AbstractConfiguredObjectProxy) child).getAttainStateFuture(); } else { return Futures.immediateFuture(child); } } @Override public <C extends ConfiguredObject> ListenableFuture<C> getAttainedChildById(final Class<C> childClass, final UUID id) { C child = getChildById(childClass, id); if (child instanceof AbstractConfiguredObject) { return ((AbstractConfiguredObject) child).getAttainStateFuture(); } else if (child instanceof AbstractConfiguredObjectProxy) { return ((AbstractConfiguredObjectProxy) child).getAttainStateFuture(); } else { return Futures.immediateFuture(child); } } private <C extends ConfiguredObject> ListenableFuture<C> getAttainStateFuture() { return (ListenableFuture<C>) _attainStateFuture; } @Override public final TaskExecutor getTaskExecutor() { return _taskExecutor; } @Override public TaskExecutor getChildExecutor() { return getTaskExecutor(); } protected final <T, E extends Exception> T runTask(Task<T, E> task) throws E { return _taskExecutor.run(task); } @Override public void setAttributes(Map<String, Object> attributes) throws IllegalStateException, AccessControlException, IllegalArgumentException { doSync(setAttributesAsync(attributes)); } protected final ChainedListenableFuture<Void> doAfter(ListenableFuture<?> first, final Runnable second) { return doAfter(getTaskExecutor(), first, second); } protected static <V> ChainedListenableFuture<Void> doAfter(Executor executor, ListenableFuture<V> first, final Runnable second) { final ChainedSettableFuture<Void> returnVal = new ChainedSettableFuture<Void>(executor); addFutureCallback(first, new FutureCallback<V>() { @Override public void onSuccess(final V result) { try { second.run(); returnVal.set(null); } catch (Throwable e) { returnVal.setException(e); } } @Override public void onFailure(final Throwable t) { returnVal.setException(t); } }, executor); return returnVal; } public interface CallableWithArgument<V, A> { V call(A argument) throws Exception; } public static interface ChainedListenableFuture<V> extends ListenableFuture<V> { ChainedListenableFuture<Void> then(Runnable r); ChainedListenableFuture<V> then(Callable<ListenableFuture<V>> r); <A> ChainedListenableFuture<A> then(CallableWithArgument<ListenableFuture<A>, V> r); } public static class ChainedSettableFuture<V> extends AbstractFuture<V> implements ChainedListenableFuture<V> { private final Executor _exector; public ChainedSettableFuture(final Executor executor) { _exector = executor; } @Override public boolean set(V value) { return super.set(value); } @Override public boolean setException(Throwable throwable) { return super.setException(throwable); } @Override public ChainedListenableFuture<Void> then(final Runnable r) { return doAfter(_exector, this, r); } @Override public ChainedListenableFuture<V> then(final Callable<ListenableFuture<V>> r) { return doAfter(_exector, this, r); } @Override public <A> ChainedListenableFuture<A> then(final CallableWithArgument<ListenableFuture<A>, V> r) { return doAfter(_exector, this, r); } } protected final <V> ChainedListenableFuture<V> doAfter(ListenableFuture<V> first, final Callable<ListenableFuture<V>> second) { return doAfter(getTaskExecutor(), first, second); } protected final <V, A> ChainedListenableFuture<V> doAfter(ListenableFuture<A> first, final CallableWithArgument<ListenableFuture<V>, A> second) { return doAfter(getTaskExecutor(), first, second); } protected static <V> ChainedListenableFuture<V> doAfter(final Executor executor, ListenableFuture<V> first, final Callable<ListenableFuture<V>> second) { final ChainedSettableFuture<V> returnVal = new ChainedSettableFuture<V>(executor); addFutureCallback(first, new FutureCallback<V>() { @Override public void onSuccess(final V result) { try { final ListenableFuture<V> future = second.call(); addFutureCallback(future, new FutureCallback<V>() { @Override public void onSuccess(final V result) { returnVal.set(result); } @Override public void onFailure(final Throwable t) { returnVal.setException(t); } }, executor); } catch (Throwable e) { returnVal.setException(e); } } @Override public void onFailure(final Throwable t) { returnVal.setException(t); } }, executor); return returnVal; } protected static <V, A> ChainedListenableFuture<V> doAfter(final Executor executor, ListenableFuture<A> first, final CallableWithArgument<ListenableFuture<V>, A> second) { final ChainedSettableFuture<V> returnVal = new ChainedSettableFuture<>(executor); addFutureCallback(first, new FutureCallback<A>() { @Override public void onSuccess(final A result) { try { final ListenableFuture<V> future = second.call(result); addFutureCallback(future, new FutureCallback<V>() { @Override public void onSuccess(final V result) { returnVal.set(result); } @Override public void onFailure(final Throwable t) { returnVal.setException(t); } }, executor); } catch (Throwable e) { returnVal.setException(e); } } @Override public void onFailure(final Throwable t) { returnVal.setException(t); } }, executor); return returnVal; } protected <V> ChainedListenableFuture<Void> doAfterAlways(ListenableFuture<V> future, Runnable after) { return doAfterAlways(getTaskExecutor(), future, after); } protected static <V> ChainedListenableFuture<Void> doAfterAlways(Executor executor, ListenableFuture<V> future, final Runnable after) { final ChainedSettableFuture<Void> returnVal = new ChainedSettableFuture<Void>(executor); addFutureCallback(future, new FutureCallback<V>() { @Override public void onSuccess(final V result) { try { after.run(); returnVal.set(null); } catch (Throwable e) { returnVal.setException(e); } } @Override public void onFailure(final Throwable t) { try { after.run(); } finally { returnVal.setException(t); } } }, executor); return returnVal; } protected static <V> void addFutureCallback(ListenableFuture<V> future, final FutureCallback<V> callback, Executor taskExecutor) { final Subject subject = Subject.getSubject(AccessController.getContext()); Futures.addCallback(future, new FutureCallback<V>() { @Override public void onSuccess(final V result) { Subject.doAs(subject, new PrivilegedAction<Void>() { @Override public Void run() { callback.onSuccess(result); return null; } }); } @Override public void onFailure(final Throwable t) { Subject.doAs(subject, new PrivilegedAction<Void>() { @Override public Void run() { callback.onFailure(t); return null; } }); } }, taskExecutor); } @Override public ListenableFuture<Void> setAttributesAsync(final Map<String, Object> attributes) throws IllegalStateException, AccessControlException, IllegalArgumentException { final Map<String, Object> updateAttributes = new HashMap<>(attributes); Object desiredState = updateAttributes.remove(ConfiguredObject.DESIRED_STATE); runTask(new Task<Void, RuntimeException>() { @Override public Void execute() { authoriseSetAttributes(createProxyForValidation(attributes), attributes.keySet()); if (!isSystemProcess()) { validateChange(createProxyForValidation(attributes), attributes.keySet()); } changeAttributes(updateAttributes); return null; } @Override public String getObject() { return AbstractConfiguredObject.this.toString(); } @Override public String getAction() { return "set attributes"; } @Override public String getArguments() { return "attributes number=" + attributes.size(); } }); if (desiredState != null) { State state; if (desiredState instanceof State) { state = (State) desiredState; } else if (desiredState instanceof String) { state = State.valueOf((String) desiredState); } else { throw new IllegalArgumentException( "Cannot convert an object of type " + desiredState.getClass().getName() + " to a State"); } return setDesiredState(state); } else { return Futures.immediateFuture(null); } } public void forceUpdateAllSecureAttributes() { applyToChildren(new Action<ConfiguredObject<?>>() { @Override public void performAction(final ConfiguredObject<?> object) { if (object instanceof AbstractConfiguredObject) { ((AbstractConfiguredObject) object).forceUpdateAllSecureAttributes(); } else if (object instanceof AbstractConfiguredObjectProxy) { ((AbstractConfiguredObjectProxy) object).forceUpdateAllSecureAttributes(); } } }); doUpdateSecureAttributes(); } private void doUpdateSecureAttributes() { Map<String, Object> secureAttributeValues = getSecureAttributeValues(); if (!secureAttributeValues.isEmpty()) { bulkChangeStart(); for (Map.Entry<String, Object> attribute : secureAttributeValues.entrySet()) { synchronized (_changeListeners) { List<ConfigurationChangeListener> copy = new ArrayList<>(_changeListeners); for (ConfigurationChangeListener listener : copy) { listener.attributeSet(this, attribute.getKey(), attribute.getValue(), attribute.getValue()); } } } bulkChangeEnd(); } } private Map<String, Object> getSecureAttributeValues() { Map<String, Object> secureAttributeValues = new HashMap<>(); for (Map.Entry<String, ConfiguredObjectAttribute<?, ?>> attribute : _attributeTypes.entrySet()) { if (attribute.getValue().isSecure() && _attributes.containsKey(attribute.getKey())) { secureAttributeValues.put(attribute.getKey(), _attributes.get(attribute.getKey())); } } return secureAttributeValues; } private void bulkChangeStart() { synchronized (_changeListeners) { List<ConfigurationChangeListener> copy = new ArrayList<ConfigurationChangeListener>(_changeListeners); for (ConfigurationChangeListener listener : copy) { listener.bulkChangeStart(this); } } } private void bulkChangeEnd() { synchronized (_changeListeners) { List<ConfigurationChangeListener> copy = new ArrayList<ConfigurationChangeListener>(_changeListeners); for (ConfigurationChangeListener listener : copy) { listener.bulkChangeEnd(this); } } } protected void changeAttributes(final Map<String, Object> attributes) { Collection<String> names = getAttributeNames(); try { bulkChangeStart(); for (Map.Entry<String, Object> entry : attributes.entrySet()) { String attributeName = entry.getKey(); if (names.contains(attributeName)) { Object desired = entry.getValue(); Object expected = getAttribute(attributeName); if (changeAttribute(attributeName, desired)) { attributeSet(attributeName, expected, desired); } } } } finally { bulkChangeEnd(); } } protected void validateChange(final ConfiguredObject<?> proxyForValidation, final Set<String> changedAttributes) { for (ConfiguredObjectAttribute<?, ?> attr : _attributeTypes.values()) { if (!attr.isDerived() && changedAttributes.contains(attr.getName())) { ConfiguredSettableAttribute autoAttr = (ConfiguredSettableAttribute) attr; if (autoAttr.isImmutable() && !Objects.equals(autoAttr.getValue(this), autoAttr.getValue(proxyForValidation))) { throw new IllegalConfigurationException( "Attribute '" + autoAttr.getName() + "' cannot be changed."); } if (autoAttr.hasValidValues()) { Object desiredValue = autoAttr.getValue(proxyForValidation); if ((autoAttr.isMandatory() || desiredValue != null) && !checkValidValues(autoAttr, desiredValue)) { throw new IllegalConfigurationException( "Attribute '" + autoAttr.getName() + "' instance of " + getClass().getName() + " named '" + getName() + "'" + " cannot have value '" + desiredValue + "'" + ". Valid values are: " + autoAttr.validValues()); } } else if (!"".equals(autoAttr.validValuePattern())) { Object desiredValueOrDefault = autoAttr.getValue(proxyForValidation); if (desiredValueOrDefault != null && !checkValidValuePattern(autoAttr, desiredValueOrDefault)) { throw new IllegalConfigurationException("Attribute '" + autoAttr.getName() + "' instance of " + getClass().getName() + " named '" + getName() + "'" + " cannot have value '" + desiredValueOrDefault + "'" + ". Valid values pattern is: " + autoAttr.validValuePattern()); } } if (autoAttr.isMandatory() && autoAttr.getValue(proxyForValidation) == null) { throw new IllegalConfigurationException( "Attribute '" + autoAttr.getName() + "' instance of " + getClass().getName() + " named '" + getName() + "'" + " cannot be null, as it is mandatory"); } } } } private ConfiguredObject<?> createProxyForValidation(final Map<String, Object> attributes) { return (ConfiguredObject<?>) Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { _bestFitInterface }, new AttributeGettingHandler(attributes, _attributeTypes, this)); } private ConfiguredObject<?> createProxyForInitialization(final Map<String, Object> attributes) { return (ConfiguredObject<?>) Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { _bestFitInterface }, new AttributeInitializationInvocationHandler(attributes, _attributeTypes, this)); } private ConfiguredObject<?> createProxyForAuthorisation(final Class<? extends ConfiguredObject> category, final Map<String, Object> attributes, final ConfiguredObject<?> parent) { return (ConfiguredObject<?>) Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { category }, new AuthorisationProxyInvocationHandler(attributes, getTypeRegistry().getAttributeTypes(category), category, parent)); } protected final <C extends ConfiguredObject<?>> void authoriseCreateChild(Class<C> childClass, Map<String, Object> attributes) throws AccessControlException { ConfiguredObject<?> configuredObject = createProxyForAuthorisation(childClass, attributes, this); authorise(configuredObject, null, Operation.CREATE, Collections.<String, Object>emptyMap()); } @Override public final void authorise(Operation operation) throws AccessControlException { authorise(this, null, operation, Collections.<String, Object>emptyMap()); } @Override public final void authorise(Operation operation, Map<String, Object> arguments) throws AccessControlException { authorise(this, null, operation, arguments); } @Override public final void authorise(SecurityToken token, Operation operation, Map<String, Object> arguments) throws AccessControlException { authorise(this, token, operation, arguments); } @Override public final SecurityToken newToken(final Subject subject) { AccessControl accessControl = getAccessControl(); return accessControl == null ? null : accessControl.newToken(subject); } private void authorise(final ConfiguredObject<?> configuredObject, SecurityToken token, final Operation operation, Map<String, Object> arguments) { AccessControl accessControl = getAccessControl(); if (accessControl != null) { Result result = accessControl.authorise(token, operation, configuredObject, arguments); LOGGER.debug("authorise returned {}", result); if (result == Result.DEFER) { result = accessControl.getDefault(); LOGGER.debug("authorise returned DEFER, returing default: {}", result); } if (result == Result.DENIED) { Class<? extends ConfiguredObject> categoryClass = configuredObject.getCategoryClass(); String objectName = (String) configuredObject.getAttribute(ConfiguredObject.NAME); String operationName = operation.getName().equals(operation.getType().name()) ? operation.getName() : (operation.getType().name() + "(" + operation.getName() + ")"); StringBuilder exceptionMessage = new StringBuilder( String.format("Permission %s is denied for : %s '%s'", operationName, categoryClass.getSimpleName(), objectName)); Model model = configuredObject.getModel(); Class<? extends ConfiguredObject> parentClass = model.getParentType(categoryClass); if (parentClass != null) { exceptionMessage.append(" on"); String objectCategory = parentClass.getSimpleName(); ConfiguredObject<?> parent = configuredObject.getParent(); exceptionMessage.append(" ").append(objectCategory); if (parent != null) { exceptionMessage.append(" '").append(parent.getAttribute(ConfiguredObject.NAME)) .append("'"); } } throw new AccessControlException(exceptionMessage.toString()); } } } protected final void authoriseSetAttributes(final ConfiguredObject<?> proxyForValidation, final Set<String> modifiedAttributes) { if (modifiedAttributes.contains(DESIRED_STATE) && State.DELETED.equals(proxyForValidation.getDesiredState())) { authorise(Operation.DELETE); if (modifiedAttributes.size() == 1) { // nothing left to authorize return; } } authorise(Operation.UPDATE); } protected Principal getSystemPrincipal() { return _systemPrincipal; } protected final Subject getSubjectWithAddedSystemRights() { Subject subject = Subject.getSubject(AccessController.getContext()); if (subject == null) { subject = new Subject(); } else { subject = new Subject(false, subject.getPrincipals(), subject.getPublicCredentials(), subject.getPrivateCredentials()); } subject.getPrincipals().add(getSystemPrincipal()); subject.setReadOnly(); return subject; } protected final AccessControlContext getSystemTaskControllerContext(String taskName, Principal principal) { final Subject subject = getSystemTaskSubject(taskName, principal); return AccessController.doPrivileged(new PrivilegedAction<AccessControlContext>() { public AccessControlContext run() { return new AccessControlContext(AccessController.getContext(), new SubjectDomainCombiner(subject)); } }, null); } protected Subject getSystemTaskSubject(String taskName) { return getSystemSubject(new TaskPrincipal(taskName)); } protected final Subject getSystemTaskSubject(String taskName, Principal principal) { return getSystemSubject(new TaskPrincipal(taskName), principal); } protected final boolean isSystemProcess() { Subject subject = Subject.getSubject(AccessController.getContext()); return isSystemSubject(subject); } protected boolean isSystemSubject(final Subject subject) { return subject != null && subject.getPrincipals().contains(getSystemPrincipal()); } private Subject getSystemSubject(Principal... principals) { Set<Principal> principalSet = new HashSet<>(Arrays.asList(principals)); principalSet.add(getSystemPrincipal()); return new Subject(true, principalSet, Collections.emptySet(), Collections.emptySet()); } private int getAwaitAttainmentTimeout() { if (_awaitAttainmentTimeout == 0) { try { _awaitAttainmentTimeout = getContextValue(Integer.class, AWAIT_ATTAINMENT_TIMEOUT); } catch (IllegalArgumentException e) { _awaitAttainmentTimeout = DEFAULT_AWAIT_ATTAINMENT_TIMEOUT; } } return _awaitAttainmentTimeout; } protected final <C extends ConfiguredObject> C awaitChildClassToAttainState(final Class<C> childClass, final String name) { ListenableFuture<C> attainedChildByName = getAttainedChildByName(childClass, name); try { return (C) doSync(attainedChildByName, getAwaitAttainmentTimeout(), TimeUnit.MILLISECONDS); } catch (TimeoutException e) { LOGGER.warn("Gave up waiting for {} '{}' to attain state. Check object's state via Management.", childClass.getSimpleName(), name); return null; } } protected final <C extends ConfiguredObject> C awaitChildClassToAttainState(final Class<C> childClass, final UUID id) { ListenableFuture<C> attainedChildByName = getAttainedChildById(childClass, id); try { return (C) doSync(attainedChildByName, getAwaitAttainmentTimeout(), TimeUnit.MILLISECONDS); } catch (TimeoutException e) { LOGGER.warn("Gave up waiting for {} with ID {} to attain state. Check object's state via Management.", childClass.getSimpleName(), id); return null; } } protected AccessControl getAccessControl() { return _parentAccessControl; } @Override public final String getLastUpdatedBy() { return _lastUpdatedBy; } @Override public final Date getLastUpdatedTime() { return _lastUpdatedTime; } @Override public final String getCreatedBy() { return _createdBy; } @Override public final Date getCreatedTime() { return _createdTime; } @Override public final String getType() { return _type; } @Override public Map<String, Object> getStatistics() { return getStatistics(Collections.<String>emptyList()); } @Override public Map<String, Object> getStatistics(List<String> statistics) { Collection<ConfiguredObjectStatistic> stats = getTypeRegistry().getStatistics(getClass()); Map<String, Object> map = new HashMap<>(); boolean allStats = statistics == null || statistics.isEmpty(); for (ConfiguredObjectStatistic stat : stats) { if (allStats || statistics.contains(stat.getName())) { map.put(stat.getName(), 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; } /** * Retrieve and interpolate a context variable of the given name and convert it to the given type. * * Note that this SHOULD not be called before the model has been resolved (e.g., not in the constructor). * @param <T> the type the interpolated context variable should be converted to * @param clazz the class object of the type the interpolated context variable should be converted to * @param propertyName the name of the context variable to retrieve * @return the interpolated context variable converted to an object of the given type * @throws IllegalArgumentException if the interpolated context variable cannot be converted to the given type */ @Override public final <T> T getContextValue(Class<T> clazz, String propertyName) { return getContextValue(clazz, clazz, propertyName); } @Override public <T> T getContextValue(final Class<T> clazz, final Type type, final String propertyName) { AttributeValueConverter<T> converter = AttributeValueConverter.getConverter(clazz, type); return converter.convert("${" + propertyName + "}", this); } @Override public Set<String> getContextKeys(final boolean excludeSystem) { Map<String, String> inheritedContext = new HashMap<>(getTypeRegistry().getDefaultContext()); if (!excludeSystem) { inheritedContext.putAll(System.getenv()); inheritedContext.putAll((Map) System.getProperties()); } generateInheritedContext(getModel(), this, inheritedContext); return Collections.unmodifiableSet(inheritedContext.keySet()); } private ConfiguredObjectTypeRegistry getTypeRegistry() { return getModel().getTypeRegistry(); } private OwnAttributeResolver getOwnAttributeResolver() { return _ownAttributeResolver; } private AncestorAttributeResolver getAncestorAttributeResolver() { return _ancestorAttributeResolver; } @Override public boolean hasEncrypter() { return _encrypter != null; } @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); } } } } @Override public final Date getLastOpenedTime() { return _lastOpenedTime; } @Override public UserPreferences getUserPreferences() { return _userPreferences; } @Override public void setUserPreferences(final UserPreferences userPreferences) { _userPreferences = userPreferences; } private EventLogger getEventLogger() { if (_parent instanceof EventLoggerProvider) { return ((EventLoggerProvider) _parent).getEventLogger(); } else if (_parent instanceof AbstractConfiguredObject) { final EventLogger eventLogger = ((AbstractConfiguredObject<?>) _parent).getEventLogger(); if (eventLogger != null) { return eventLogger; } } return null; } protected void logOperation(String operation) { EventLogger eventLogger = getEventLogger(); if (eventLogger != null) { eventLogger.message(new OperationLogMessage(this, operation)); } else { LOGGER.info(getCategoryClass().getSimpleName() + "(" + getName() + ") : Operation " + operation + " invoked by user " + AuthenticatedPrincipal.getCurrentUser().getName()); } } //========================================================================================= 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), getAncestorAttributeResolver(object), new Strings.MapResolver(inheritedContext), Strings.JAVA_SYS_PROPS_RESOLVER, Strings.ENV_VARS_RESOLVER, object.getModel().getTypeRegistry().getDefaultContextResolver()); } } static String interpolate(Model model, String value) { return Strings.expand(value, false, JSON_SUBSTITUTION_RESOLVER, Strings.JAVA_SYS_PROPS_RESOLVER, Strings.ENV_VARS_RESOLVER, model.getTypeRegistry().getDefaultContextResolver()); } private static OwnAttributeResolver getOwnAttributeResolver(final ConfiguredObject<?> object) { return object instanceof AbstractConfiguredObject ? ((AbstractConfiguredObject) object).getOwnAttributeResolver() : new OwnAttributeResolver(object); } private static AncestorAttributeResolver getAncestorAttributeResolver(final ConfiguredObject<?> object) { return object instanceof AbstractConfiguredObject ? ((AbstractConfiguredObject) object).getAncestorAttributeResolver() : new AncestorAttributeResolver(object); } static void generateInheritedContext(final Model model, final ConfiguredObject<?> object, final Map<String, String> inheritedContext) { Class<? extends ConfiguredObject> parentClass = model.getParentType(object.getCategoryClass()); if (parentClass != null) { ConfiguredObject parent = object.getParent(); 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 AttributeGettingHandler implements InvocationHandler { private final Map<String, Object> _attributes; private final Map<String, ConfiguredObjectAttribute<?, ?>> _attributeTypes; private final ConfiguredObject<?> _configuredObject; AttributeGettingHandler(final Map<String, Object> modifiedAttributes, Map<String, ConfiguredObjectAttribute<?, ?>> attributeTypes, ConfiguredObject<?> configuredObject) { Map<String, Object> combinedAttributes = new HashMap<>(); if (configuredObject != null) { combinedAttributes.putAll(configuredObject.getActualAttributes()); } combinedAttributes.putAll(modifiedAttributes); _attributes = combinedAttributes; _attributeTypes = attributeTypes; _configuredObject = configuredObject; } @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { ConfiguredObjectAttribute attribute = getAttributeFromMethod(method); if (attribute != null && attribute.isAutomated()) { return getValue(attribute); } else if (method.getName().equals("getAttribute") && args != null && args.length == 1 && args[0] instanceof String) { attribute = _attributeTypes.get((String) args[0]); if (attribute != null) { return getValue(attribute); } else { return null; } } else if (method.getName().equals("getActualAttributes") && (args == null || args.length == 0)) { return Collections.unmodifiableMap(_attributes); } else if (method.getName().equals("toString") && (args == null || args.length == 0)) { return "ValidationProxy{" + getCategoryClass().getSimpleName() + "/" + getType() + "}"; } else if (method.getName().equals("getModel") && (args == null || args.length == 0)) { return _configuredObject.getModel(); } else { 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) { Object value; if (!attribute.isDerived()) { ConfiguredSettableAttribute settableAttr = (ConfiguredSettableAttribute) attribute; value = _attributes.get(attribute.getName()); if (value == null && !"".equals(settableAttr.defaultValue())) { value = settableAttr.defaultValue(); } return convert(settableAttr, value); } else { if (_attributes.containsKey(attribute.getName())) { return _attributes.get(attribute.getName()); } else if (_configuredObject != null) { return _configuredObject.getAttribute(attribute.getName()); } else { return null; } } } protected Object convert(ConfiguredSettableAttribute attribute, Object value) { return attribute.convert(value, _configuredObject); } private ConfiguredObjectAttribute getAttributeFromMethod(final Method method) { if (!Modifier.isStatic(method.getModifiers()) && method.getParameterTypes().length == 0) { for (ConfiguredObjectAttribute attribute : _attributeTypes.values()) { if ((attribute instanceof ConfiguredObjectMethodAttribute) && ((ConfiguredObjectMethodAttribute) attribute).getGetter().getName() .equals(method.getName())) { return attribute; } } } return null; } protected String getType() { return _configuredObject.getType(); } protected Class<? extends ConfiguredObject> getCategoryClass() { return _configuredObject.getCategoryClass(); } ConfiguredObject<?> getConfiguredObject() { return _configuredObject; } } private static class AttributeInitializationInvocationHandler extends AttributeGettingHandler { AttributeInitializationInvocationHandler(final Map<String, Object> modifiedAttributes, final Map<String, ConfiguredObjectAttribute<?, ?>> attributeTypes, final ConfiguredObject<?> configuredObject) { super(modifiedAttributes, attributeTypes, configuredObject); } @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { if (Arrays.asList("getModel", "getCategoryClass", "getParent").contains(method.getName())) { return method.invoke(getConfiguredObject(), args); } else { return super.invoke(proxy, method, args); } } } private static class AuthorisationProxyInvocationHandler extends AttributeGettingHandler { private final Class<? extends ConfiguredObject> _category; private final ConfiguredObject<?> _parent; private Map<String, Object> _attributes; AuthorisationProxyInvocationHandler(Map<String, Object> attributes, Map<String, ConfiguredObjectAttribute<?, ?>> attributeTypes, Class<? extends ConfiguredObject> categoryClass, ConfiguredObject<?> parent) { super(attributes, attributeTypes, null); _parent = parent; _category = categoryClass; _attributes = attributes; } @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { if (method.getName().equals("getParent") && (args == null || args.length == 0)) { return _parent; } else if (method.getName().equals("getCategoryClass")) { return _category; } else if (method.getName().equals("getModel") && (args == null || args.length == 0)) { return _parent.getModel(); } return super.invoke(proxy, method, args); } @Override protected Object convert(ConfiguredSettableAttribute attribute, Object value) { return attribute.convert(value, _parent); } @Override protected Class<? extends ConfiguredObject> getCategoryClass() { return _category; } @Override protected String getType() { return String.valueOf(_attributes.get(ConfiguredObject.TYPE)); } } public final static class DuplicateIdException extends IllegalArgumentException { private DuplicateIdException(final ConfiguredObject<?> existing) { super("Child of type " + existing.getClass().getSimpleName() + " already exists with id of " + existing.getId()); } } public static class DuplicateNameException extends IllegalArgumentException { private final ConfiguredObject<?> _existing; protected DuplicateNameException(final ConfiguredObject<?> existing) { this("Child of type " + existing.getClass().getSimpleName() + " already exists with name of " + existing.getName(), existing); } protected DuplicateNameException(String message, final ConfiguredObject<?> existing) { super(message); _existing = existing; } public String getName() { return _existing.getName(); } public ConfiguredObject<?> getExisting() { return _existing; } } interface AbstractConfiguredObjectExceptionHandler { void handleException(RuntimeException exception, ConfiguredObject<?> source); } private static class OpenExceptionHandler implements AbstractConfiguredObjectExceptionHandler { @Override public void handleException(RuntimeException exception, ConfiguredObject<?> source) { if (source instanceof AbstractConfiguredObject) { ((AbstractConfiguredObject) source).handleExceptionOnOpen(exception); } else if (source instanceof AbstractConfiguredObjectProxy) { ((AbstractConfiguredObjectProxy) 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, ConfiguredObject<?> source) { try { if (source.getState() != State.DELETED) { // TODO - RG - This isn't right :-( source.deleteAsync(); } } finally { if (_unregister) { if (source instanceof AbstractConfiguredObject) { ((AbstractConfiguredObject) source).unregister(false); } else if (source instanceof AbstractConfiguredObjectProxy) { ((AbstractConfiguredObjectProxy) source).unregister(false); } } throw exception; } } } private interface AbstractConfiguredObjectProxy { void registerChild(ConfiguredObject configuredObject); DynamicState getDynamicState(); ListenableFuture<Void> doAttainState(AbstractConfiguredObjectExceptionHandler exceptionHandler); void doOpening(boolean skipCheck, AbstractConfiguredObjectExceptionHandler exceptionHandler); void handleExceptionOnOpen(RuntimeException exception); void unregister(boolean removed); void doValidation(boolean skipCheck, AbstractConfiguredObjectExceptionHandler exceptionHandler); void doResolution(boolean skipCheck, AbstractConfiguredObjectExceptionHandler exceptionHandler); void doCreation(boolean skipCheck, AbstractConfiguredObjectExceptionHandler exceptionHandler); void validateChildDelete(ConfiguredObject child); void unregisterChild(ConfiguredObject child); void childRemoved(ConfiguredObject child); ListenableFuture getAttainStateFuture(); void forceUpdateAllSecureAttributes(); } }