blob: 7e219f36dcd8cd9d6841c3d31288eb1a681e27a4 [file] [log] [blame]
/*
*
* 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.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.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 final AtomicReference<DynamicState> _dynamicState = new AtomicReference<>(DynamicState.UNINIT);
private final Map<String,Object> _attributes = new HashMap<>();
private final Map<Class<? extends ConfiguredObject>, ConfiguredObject> _parents =
new HashMap<>();
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 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().getChildExecutor());
}
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());
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());
for(ConfiguredObject<?> parent : parents.values())
{
if(parent instanceof AbstractConfiguredObject && ((AbstractConfiguredObject)parent)._encrypter != null)
{
_encrypter = ((AbstractConfiguredObject)parent)._encrypter;
break;
}
else if(parent instanceof ConfigurationSecretEncrypterSource && ((ConfigurationSecretEncrypterSource)parent).getEncrypter() != null)
{
_encrypter = ((ConfigurationSecretEncrypterSource)parent).getEncrypter();
break;
}
}
for(ConfiguredObject<?> parent : parents.values())
{
if(parent instanceof AbstractConfiguredObject && ((AbstractConfiguredObject)parent).getAccessControl() != null)
{
_parentAccessControl = ((AbstractConfiguredObject)parent).getAccessControl();
break;
}
else if(parent instanceof AccessControlSource && ((AccessControlSource)parent).getAccessControl()!=null)
{
_parentAccessControl = ((AccessControlSource)parent).getAccessControl();
break;
}
}
for(ConfiguredObject<?> parent : parents.values())
{
if(parent instanceof AbstractConfiguredObject && ((AbstractConfiguredObject)parent).getSystemPrincipal() != null)
{
_systemPrincipal = ((AbstractConfiguredObject)parent).getSystemPrincipal();
break;
}
else if(parent instanceof SystemPrincipalSource && ((SystemPrincipalSource)parent).getSystemPrincipal()!=null)
{
_systemPrincipal = ((SystemPrincipalSource)parent).getSystemPrincipal();
break;
}
}
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();
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
? ((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(DynamicState.UNINIT, DynamicState.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
{
Futures.addCallback(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);
}
});
}
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()
{
for(ConfiguredObject<?> parent : _parents.values())
{
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();
Futures.addCallback(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);
}
});
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());
if(_dynamicState.compareAndSet(DynamicState.OPENED, DynamicState.CLOSED))
{
return doAfter(beforeClose(), new Callable<ListenableFuture<Void>>()
{
@Override
public ListenableFuture<Void> call() throws Exception
{
return closeChildren();
}
}).then(new Runnable()
{
@Override
public void run()
{
onClose();
unregister(false);
LOGGER.debug("Closed " + AbstractConfiguredObject.this.getClass().getSimpleName() + " : " + getName());
}
});
}
else
{
LOGGER.debug("Closed " + AbstractConfiguredObject.this.getClass().getSimpleName() + " : " + getName());
return Futures.immediateFuture(null);
}
}
@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 void onClose()
{
}
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(DynamicState.UNINIT, DynamicState.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(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 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)child)._dynamicState.get() == DynamicState.OPENED)
{
final AbstractConfiguredObject configuredObject = (AbstractConfiguredObject) child;
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();
Futures.addCallback(combinedChildStateFuture, new FutureCallback<List<Void>>()
{
@Override
public void onSuccess(final List<Void> result)
{
try
{
Futures.addCallback(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)
{
exceptionHandler.handleException((RuntimeException) t,
AbstractConfiguredObject.this);
returnVal.set(null);
}
}
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);
}
});
return returnVal;
}
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)
{
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() != 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() != 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() != 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);
Futures.addCallback(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);
}
});
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)
{
for(ConfiguredObject<?> parent : _parents.values())
{
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;
}
}
}
public <T extends ConfiguredObject> T getParent(final Class<T> clazz)
{
return (T) _parents.get(clazz);
}
public final <T> T getAncestor(final Class<T> clazz)
{
return getModel().getAncestor(clazz, this);
}
private <T extends ConfiguredObject> void addParent(Class<T> clazz, T parent)
{
synchronized (_parents)
{
_parents.put(clazz, parent);
}
}
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();
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<>();
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 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,
final ConfiguredObject... otherParents)
{
return doSync(createChildAsync(childClass, attributes, otherParents));
}
@SuppressWarnings("unchecked")
@Override
public <C extends ConfiguredObject> ListenableFuture<C> createChildAsync(final Class<C> childClass, final Map<String, Object> attributes,
final ConfiguredObject... otherParents)
{
return doOnConfigThread(new Task<ListenableFuture<C>, RuntimeException>()
{
@Override
public ListenableFuture<C> execute()
{
authoriseCreateChild(childClass, attributes, otherParents);
return doAfter(addChildAsync(childClass, attributes, otherParents),
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, ConfiguredObject... otherParents)
{
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);
}
if(getModel().getParentTypes(categoryClass).size() == 1)
{
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)
{
for (ConfiguredObject<?> parent : _parents.values())
{
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);
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)
{
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);
Futures.addCallback(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);
Futures.addCallback(first, new FutureCallback<V>()
{
@Override
public void onSuccess(final V result)
{
try
{
final ListenableFuture<V> future = second.call();
Futures.addCallback(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);
Futures.addCallback(first, new FutureCallback<A>()
{
@Override
public void onSuccess(final A result)
{
try
{
final ListenableFuture<V> future = second.call(result);
Futures.addCallback(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);
Futures.addCallback(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;
}
@Override
public ListenableFuture<Void> setAttributesAsync(final Map<String, Object> attributes) throws IllegalStateException, AccessControlException, IllegalArgumentException
{
final Map<String,Object> updateAttributes = new HashMap<>(attributes);
final Object desiredState = updateAttributes.remove(ConfiguredObject.DESIRED_STATE);
return doOnConfigThread(new Task<ListenableFuture<Void>, RuntimeException>()
{
@Override
public ListenableFuture<Void> execute()
{
authoriseSetAttributes(createProxyForValidation(attributes), attributes.keySet());
if (!isSystemProcess())
{
validateChange(createProxyForValidation(attributes), attributes.keySet());
}
changeAttributes(updateAttributes);
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);
}
}
@Override
public String getObject()
{
return AbstractConfiguredObject.this.toString();
}
@Override
public String getAction()
{
return "set attributes";
}
@Override
public String getArguments()
{
return "attributes number=" + attributes.size();
}
});
}
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,
final ConfiguredObject<?>... otherParents)
{
return (ConfiguredObject<?>) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class<?>[]{category},
new AuthorisationProxyInvocationHandler(attributes,
getTypeRegistry().getAttributeTypes(category),
category, parent, otherParents));
}
protected final <C extends ConfiguredObject<?>> void authoriseCreateChild(Class<C> childClass, Map<String, Object> attributes, ConfiguredObject... otherParents) throws AccessControlException
{
ConfiguredObject<?> configuredObject = createProxyForAuthorisation(childClass, attributes, this, otherParents);
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();
Collection<Class<? extends ConfiguredObject>> parentClasses = model.getParentTypes(categoryClass);
if (parentClasses != null)
{
exceptionMessage.append(" on");
for (Class<? extends ConfiguredObject> parentClass : parentClasses)
{
String objectCategory = parentClass.getSimpleName();
ConfiguredObject<?> parent = configuredObject.getParent(parentClass);
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()
{
Collection<ConfiguredObjectStatistic> stats = getTypeRegistry().getStatistics(getClass());
Map<String,Object> map = new HashMap<>();
for(ConfiguredObjectStatistic stat : stats)
{
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;
}
//=========================================================================================
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());
}
}
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)
{
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 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 Map<Class<? extends ConfiguredObject>, ConfiguredObject<?>> _parents;
private final ConfiguredObject<?> _parent ;
private Map<String, Object> _attributes;
AuthorisationProxyInvocationHandler(Map<String, Object> attributes,
Map<String, ConfiguredObjectAttribute<?, ?>> attributeTypes,
Class<? extends ConfiguredObject> categoryClass,
ConfiguredObject<?> parent,
ConfiguredObject<?>... parents)
{
super(attributes, attributeTypes, null);
_parent = parent;
_category = categoryClass;
_parents = new HashMap<>();
_attributes = attributes;
if (parents != null)
{
for (ConfiguredObject<?> parentObject : parents)
{
_parents.put(parentObject.getCategoryClass(), parentObject);
}
}
_parents.put(parent.getCategoryClass(), parent);
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable
{
if(method.getName().equals("getParent") && args != null && args.length == 1 && args[0] instanceof Class)
{
Class<ConfiguredObject> parentClass = (Class<ConfiguredObject> )args[0];
return _parents.get(parentClass);
}
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 final static class DuplicateNameException extends IllegalArgumentException
{
private final ConfiguredObject<?> _existing;
private DuplicateNameException(final ConfiguredObject<?> existing)
{
super("Child of type " + existing.getClass().getSimpleName() + " already exists with name of " + existing.getName());
_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();
}
}