| | |
| | | */ |
| | | package org.opends.server.admin; |
| | | |
| | | |
| | | |
| | | import static org.opends.messages.AdminMessages.*; |
| | | import static org.opends.server.loggers.debug.DebugLogger.*; |
| | | import static org.opends.server.util.Validator.*; |
| | | |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.EnumSet; |
| | |
| | | import java.util.MissingResourceException; |
| | | import java.util.SortedSet; |
| | | |
| | | import org.opends.messages.Message; |
| | | import org.opends.server.admin.client.AuthorizationException; |
| | | import org.opends.server.admin.client.ClientConstraintHandler; |
| | | import org.opends.server.admin.client.CommunicationException; |
| | |
| | | import org.opends.server.admin.server.ServerManagedObject; |
| | | import org.opends.server.admin.server.ServerManagedObjectChangeListener; |
| | | import org.opends.server.admin.server.ServerManagementContext; |
| | | import org.opends.server.admin.std.meta.RootCfgDefn; |
| | | import org.opends.server.config.ConfigException; |
| | | import org.opends.server.loggers.ErrorLogger; |
| | | import org.opends.server.loggers.debug.DebugTracer; |
| | | import org.opends.server.types.ConfigChangeResult; |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.opendj.ldap.DN; |
| | | import org.opends.server.types.DebugLogLevel; |
| | | import org.opends.server.types.ResultCode; |
| | | import org.opends.server.util.StaticUtils; |
| | | |
| | | |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | |
| | | /** |
| | | * Aggregation property definition. |
| | | * <p> |
| | | * An aggregation property names one or more managed objects which are |
| | | * required by the managed object associated with this property. An |
| | | * aggregation property definition takes care to perform referential |
| | | * integrity checks: referenced managed objects cannot be deleted. Nor |
| | | * can an aggregation reference non-existent managed objects. |
| | | * Referential integrity checks are <b>not</b> performed during value |
| | | * validation. Instead they are performed when changes to the managed |
| | | * object are committed. |
| | | * An aggregation property names one or more managed objects which are required |
| | | * by the managed object associated with this property. An aggregation property |
| | | * definition takes care to perform referential integrity checks: referenced |
| | | * managed objects cannot be deleted. Nor can an aggregation reference |
| | | * non-existent managed objects. Referential integrity checks are <b>not</b> |
| | | * performed during value validation. Instead they are performed when changes to |
| | | * the managed object are committed. |
| | | * <p> |
| | | * An aggregation property definition can optionally identify two |
| | | * properties: |
| | | * An aggregation property definition can optionally identify two properties: |
| | | * <ul> |
| | | * <li>an <code>enabled</code> property in the aggregated managed |
| | | * object - the property must be a {@link BooleanPropertyDefinition} |
| | | * and indicate whether the aggregated managed object is enabled or |
| | | * not. If specified, the administration framework will prevent the |
| | | * aggregated managed object from being disabled while it is |
| | | * referenced |
| | | * <li>an <code>enabled</code> property in this property's managed |
| | | * object - the property must be a {@link BooleanPropertyDefinition} |
| | | * and indicate whether this property's managed object is enabled or |
| | | * not. If specified, and as long as there is an equivalent |
| | | * <code>enabled</code> property defined for the aggregated managed |
| | | * object, the <code>enabled</code> property in the aggregated |
| | | * managed object will only be checked when this property is true. |
| | | * <li>an <code>enabled</code> property in the aggregated managed object - the |
| | | * property must be a {@link BooleanPropertyDefinition} and indicate whether the |
| | | * aggregated managed object is enabled or not. If specified, the administration |
| | | * framework will prevent the aggregated managed object from being disabled |
| | | * while it is referenced |
| | | * <li>an <code>enabled</code> property in this property's managed object - the |
| | | * property must be a {@link BooleanPropertyDefinition} and indicate whether |
| | | * this property's managed object is enabled or not. If specified, and as long |
| | | * as there is an equivalent <code>enabled</code> property defined for the |
| | | * aggregated managed object, the <code>enabled</code> property in the |
| | | * aggregated managed object will only be checked when this property is true. |
| | | * </ul> |
| | | * In other words, these properties can be used to make sure that |
| | | * referenced managed objects are not disabled while they are |
| | | * referenced. |
| | | * In other words, these properties can be used to make sure that referenced |
| | | * managed objects are not disabled while they are referenced. |
| | | * |
| | | * @param <C> |
| | | * The type of client managed object configuration that this |
| | | * aggregation property definition refers to. |
| | | * The type of client managed object configuration that this |
| | | * aggregation property definition refers to. |
| | | * @param <S> |
| | | * The type of server managed object configuration that this |
| | | * aggregation property definition refers to. |
| | | * The type of server managed object configuration that this |
| | | * aggregation property definition refers to. |
| | | */ |
| | | public final class AggregationPropertyDefinition |
| | | <C extends ConfigurationClient, S extends Configuration> |
| | | extends PropertyDefinition<String> { |
| | | public final class AggregationPropertyDefinition<C extends ConfigurationClient, S extends Configuration> extends |
| | | PropertyDefinition<String> { |
| | | |
| | | /** |
| | | * An interface for incrementally constructing aggregation property |
| | | * definitions. |
| | | * |
| | | * @param <C> |
| | | * The type of client managed object configuration that |
| | | * this aggregation property definition refers to. |
| | | * @param <S> |
| | | * The type of server managed object configuration that |
| | | * this aggregation property definition refers to. |
| | | */ |
| | | public static class Builder |
| | | <C extends ConfigurationClient, S extends Configuration> |
| | | extends AbstractBuilder<String, AggregationPropertyDefinition<C, S>> { |
| | | /** |
| | | * An interface for incrementally constructing aggregation property |
| | | * definitions. |
| | | * |
| | | * @param <C> |
| | | * The type of client managed object configuration that this |
| | | * aggregation property definition refers to. |
| | | * @param <S> |
| | | * The type of server managed object configuration that this |
| | | * aggregation property definition refers to. |
| | | */ |
| | | public static class Builder<C extends ConfigurationClient, S extends Configuration> extends |
| | | AbstractBuilder<String, AggregationPropertyDefinition<C, S>> { |
| | | |
| | | // The string representation of the managed object path specifying |
| | | // the parent of the aggregated managed objects. |
| | | private String parentPathString = null; |
| | | |
| | | // The name of a relation in the parent managed object which |
| | | // contains the aggregated managed objects. |
| | | private String rdName = null; |
| | | |
| | | // The condition which is used to determine if a referenced |
| | | // managed object is enabled. |
| | | private Condition targetIsEnabledCondition = Conditions.TRUE; |
| | | |
| | | // The condition which is used to determine whether or not |
| | | // referenced managed objects need to be enabled. |
| | | private Condition targetNeedsEnablingCondition = Conditions.TRUE; |
| | | |
| | | // Private constructor |
| | | private Builder(AbstractManagedObjectDefinition<?, ?> d, String propertyName) { |
| | | super(d, propertyName); |
| | | } |
| | | |
| | | /** |
| | | * Sets the name of the managed object which is the parent of the |
| | | * aggregated managed objects. |
| | | * <p> |
| | | * This must be defined before the property definition can be built. |
| | | * |
| | | * @param pathString |
| | | * The string representation of the managed object path |
| | | * specifying the parent of the aggregated managed objects. |
| | | */ |
| | | public final void setParentPath(String pathString) { |
| | | this.parentPathString = pathString; |
| | | } |
| | | |
| | | /** |
| | | * Sets the relation in the parent managed object which contains the |
| | | * aggregated managed objects. |
| | | * <p> |
| | | * This must be defined before the property definition can be built. |
| | | * |
| | | * @param rdName |
| | | * The name of a relation in the parent managed object which |
| | | * contains the aggregated managed objects. |
| | | */ |
| | | public final void setRelationDefinition(String rdName) { |
| | | this.rdName = rdName; |
| | | } |
| | | |
| | | /** |
| | | * Sets the condition which is used to determine if a referenced managed |
| | | * object is enabled. By default referenced managed objects are assumed |
| | | * to always be enabled. |
| | | * |
| | | * @param condition |
| | | * The condition which is used to determine if a referenced |
| | | * managed object is enabled. |
| | | */ |
| | | public final void setTargetIsEnabledCondition(Condition condition) { |
| | | this.targetIsEnabledCondition = condition; |
| | | } |
| | | |
| | | /** |
| | | * Sets the condition which is used to determine whether or not |
| | | * referenced managed objects need to be enabled. By default referenced |
| | | * managed objects must always be enabled. |
| | | * |
| | | * @param condition |
| | | * The condition which is used to determine whether or not |
| | | * referenced managed objects need to be enabled. |
| | | */ |
| | | public final void setTargetNeedsEnablingCondition(Condition condition) { |
| | | this.targetNeedsEnablingCondition = condition; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | protected AggregationPropertyDefinition<C, S> buildInstance(AbstractManagedObjectDefinition<?, ?> d, |
| | | String propertyName, EnumSet<PropertyOption> options, AdministratorAction adminAction, |
| | | DefaultBehaviorProvider<String> defaultBehavior) { |
| | | // Make sure that the parent path has been defined. |
| | | if (parentPathString == null) { |
| | | throw new IllegalStateException("Parent path undefined"); |
| | | } |
| | | |
| | | // Make sure that the relation definition has been defined. |
| | | if (rdName == null) { |
| | | throw new IllegalStateException("Relation definition undefined"); |
| | | } |
| | | |
| | | return new AggregationPropertyDefinition<C, S>(d, propertyName, options, adminAction, defaultBehavior, |
| | | parentPathString, rdName, targetNeedsEnablingCondition, targetIsEnabledCondition); |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * A change listener which prevents the named component from being disabled. |
| | | */ |
| | | private class ReferentialIntegrityChangeListener implements ServerManagedObjectChangeListener<S> { |
| | | |
| | | // The error message which should be returned if an attempt is |
| | | // made to disable the referenced component. |
| | | private final LocalizableMessage message; |
| | | |
| | | // The path of the referenced component. |
| | | private final ManagedObjectPath<C, S> path; |
| | | |
| | | // Creates a new referential integrity delete listener. |
| | | private ReferentialIntegrityChangeListener(ManagedObjectPath<C, S> path, LocalizableMessage message) { |
| | | this.path = path; |
| | | this.message = message; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public ConfigChangeResult applyConfigurationChange(ServerManagedObject<? extends S> mo) { |
| | | try { |
| | | if (targetIsEnabledCondition.evaluate(mo)) { |
| | | return new ConfigChangeResult(ResultCode.SUCCESS, false); |
| | | } |
| | | } catch (ConfigException e) { |
| | | // This should not happen - ignore it and throw an exception |
| | | // anyway below. |
| | | } |
| | | |
| | | // This should not happen - the previous call-back should have |
| | | // trapped this. |
| | | throw new IllegalStateException("Attempting to disable a referenced " |
| | | + relationDefinition.getChildDefinition().getUserFriendlyName()); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public boolean isConfigurationChangeAcceptable(ServerManagedObject<? extends S> mo, |
| | | List<LocalizableMessage> unacceptableReasons) { |
| | | // Always prevent the referenced component from being |
| | | // disabled. |
| | | try { |
| | | if (!targetIsEnabledCondition.evaluate(mo)) { |
| | | unacceptableReasons.add(message); |
| | | return false; |
| | | } else { |
| | | return true; |
| | | } |
| | | } catch (ConfigException e) { |
| | | // The condition could not be evaluated. |
| | | if (debugEnabled()) { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | LocalizableMessage message = ERR_REFINT_UNABLE_TO_EVALUATE_TARGET_CONDITION.get(mo.getManagedObjectDefinition() |
| | | .getUserFriendlyName(), String.valueOf(mo.getDN()), StaticUtils.getExceptionLocalizableMessage(e)); |
| | | ErrorLogger.logError(message); |
| | | unacceptableReasons.add(message); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | // Gets the path associated with this listener. |
| | | private ManagedObjectPath<C, S> getManagedObjectPath() { |
| | | return path; |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * A delete listener which prevents the named component from being deleted. |
| | | */ |
| | | private class ReferentialIntegrityDeleteListener implements ConfigurationDeleteListener<S> { |
| | | |
| | | // The DN of the referenced configuration entry. |
| | | private final DN dn; |
| | | |
| | | // The error message which should be returned if an attempt is |
| | | // made to delete the referenced component. |
| | | private final LocalizableMessage message; |
| | | |
| | | // Creates a new referential integrity delete listener. |
| | | private ReferentialIntegrityDeleteListener(DN dn, LocalizableMessage message) { |
| | | this.dn = dn; |
| | | this.message = message; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public ConfigChangeResult applyConfigurationDelete(S configuration) { |
| | | // This should not happen - the |
| | | // isConfigurationDeleteAcceptable() call-back should have |
| | | // trapped this. |
| | | if (configuration.dn().equals(dn)) { |
| | | // This should not happen - the |
| | | // isConfigurationDeleteAcceptable() call-back should have |
| | | // trapped this. |
| | | throw new IllegalStateException("Attempting to delete a referenced " |
| | | + relationDefinition.getChildDefinition().getUserFriendlyName()); |
| | | } else { |
| | | return new ConfigChangeResult(ResultCode.SUCCESS, false); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public boolean isConfigurationDeleteAcceptable(S configuration, List<LocalizableMessage> unacceptableReasons) { |
| | | if (configuration.dn().equals(dn)) { |
| | | // Always prevent deletion of the referenced component. |
| | | unacceptableReasons.add(message); |
| | | return false; |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * The server-side constraint handler implementation. |
| | | */ |
| | | private class ServerHandler extends ServerConstraintHandler { |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean isUsable(ServerManagedObject<?> managedObject, Collection<LocalizableMessage> unacceptableReasons) |
| | | throws ConfigException { |
| | | SortedSet<String> names = managedObject.getPropertyValues(AggregationPropertyDefinition.this); |
| | | ServerManagementContext context = ServerManagementContext.getInstance(); |
| | | LocalizableMessage thisUFN = managedObject.getManagedObjectDefinition().getUserFriendlyName(); |
| | | String thisDN = managedObject.getDN().toString(); |
| | | LocalizableMessage thatUFN = getRelationDefinition().getUserFriendlyName(); |
| | | |
| | | boolean isUsable = true; |
| | | boolean needsEnabling = targetNeedsEnablingCondition.evaluate(managedObject); |
| | | for (String name : names) { |
| | | ManagedObjectPath<C, S> path = getChildPath(name); |
| | | String thatDN = path.toDN().toString(); |
| | | |
| | | if (!context.managedObjectExists(path)) { |
| | | LocalizableMessage msg = ERR_SERVER_REFINT_DANGLING_REFERENCE.get(name, getName(), thisUFN, thisDN, thatUFN, |
| | | thatDN); |
| | | unacceptableReasons.add(msg); |
| | | isUsable = false; |
| | | } else if (needsEnabling) { |
| | | // Check that the referenced component is enabled if |
| | | // required. |
| | | ServerManagedObject<? extends S> ref = context.getManagedObject(path); |
| | | if (!targetIsEnabledCondition.evaluate(ref)) { |
| | | LocalizableMessage msg = ERR_SERVER_REFINT_TARGET_DISABLED.get(name, getName(), thisUFN, thisDN, thatUFN, |
| | | thatDN); |
| | | unacceptableReasons.add(msg); |
| | | isUsable = false; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return isUsable; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void performPostAdd(ServerManagedObject<?> managedObject) throws ConfigException { |
| | | // First make sure existing listeners associated with this |
| | | // managed object are removed. This is required in order to |
| | | // prevent multiple change listener registrations from |
| | | // occurring, for example if this call-back is invoked multiple |
| | | // times after the same add event. |
| | | performPostDelete(managedObject); |
| | | |
| | | // Add change and delete listeners against all referenced |
| | | // components. |
| | | LocalizableMessage thisUFN = managedObject.getManagedObjectDefinition().getUserFriendlyName(); |
| | | String thisDN = managedObject.getDN().toString(); |
| | | LocalizableMessage thatUFN = getRelationDefinition().getUserFriendlyName(); |
| | | |
| | | // Referenced managed objects will only need a change listener |
| | | // if they have can be disabled. |
| | | boolean needsChangeListeners = targetNeedsEnablingCondition.evaluate(managedObject); |
| | | |
| | | // Delete listeners need to be registered against the parent |
| | | // entry of the referenced components. |
| | | ServerManagementContext context = ServerManagementContext.getInstance(); |
| | | ManagedObjectPath<?, ?> parentPath = getParentPath(); |
| | | ServerManagedObject<?> parent = context.getManagedObject(parentPath); |
| | | |
| | | // Create entries in the listener tables. |
| | | List<ReferentialIntegrityDeleteListener> dlist = new LinkedList<ReferentialIntegrityDeleteListener>(); |
| | | deleteListeners.put(managedObject.getDN(), dlist); |
| | | |
| | | List<ReferentialIntegrityChangeListener> clist = new LinkedList<ReferentialIntegrityChangeListener>(); |
| | | changeListeners.put(managedObject.getDN(), clist); |
| | | |
| | | for (String name : managedObject.getPropertyValues(AggregationPropertyDefinition.this)) { |
| | | ManagedObjectPath<C, S> path = getChildPath(name); |
| | | DN dn = path.toDN(); |
| | | String thatDN = dn.toString(); |
| | | |
| | | // Register the delete listener. |
| | | LocalizableMessage msg = ERR_SERVER_REFINT_CANNOT_DELETE.get(thatUFN, thatDN, getName(), thisUFN, thisDN); |
| | | ReferentialIntegrityDeleteListener dl = new ReferentialIntegrityDeleteListener(dn, msg); |
| | | parent.registerDeleteListener(getRelationDefinition(), dl); |
| | | dlist.add(dl); |
| | | |
| | | // Register the change listener if required. |
| | | if (needsChangeListeners) { |
| | | ServerManagedObject<? extends S> ref = context.getManagedObject(path); |
| | | msg = ERR_SERVER_REFINT_CANNOT_DISABLE.get(thatUFN, thatDN, getName(), thisUFN, thisDN); |
| | | ReferentialIntegrityChangeListener cl = new ReferentialIntegrityChangeListener(path, msg); |
| | | ref.registerChangeListener(cl); |
| | | clist.add(cl); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void performPostDelete(ServerManagedObject<?> managedObject) throws ConfigException { |
| | | // Remove any registered delete and change listeners. |
| | | ServerManagementContext context = ServerManagementContext.getInstance(); |
| | | DN dn = managedObject.getDN(); |
| | | |
| | | // Delete listeners need to be deregistered against the parent |
| | | // entry of the referenced components. |
| | | ManagedObjectPath<?, ?> parentPath = getParentPath(); |
| | | ServerManagedObject<?> parent = context.getManagedObject(parentPath); |
| | | if (deleteListeners.containsKey(dn)) { |
| | | for (ReferentialIntegrityDeleteListener dl : deleteListeners.get(dn)) { |
| | | parent.deregisterDeleteListener(getRelationDefinition(), dl); |
| | | } |
| | | deleteListeners.remove(dn); |
| | | } |
| | | |
| | | // Change listeners need to be deregistered from their |
| | | // associated referenced component. |
| | | if (changeListeners.containsKey(dn)) { |
| | | for (ReferentialIntegrityChangeListener cl : changeListeners.get(dn)) { |
| | | ManagedObjectPath<C, S> path = cl.getManagedObjectPath(); |
| | | ServerManagedObject<? extends S> ref = context.getManagedObject(path); |
| | | ref.deregisterChangeListener(cl); |
| | | } |
| | | changeListeners.remove(dn); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void performPostModify(ServerManagedObject<?> managedObject) throws ConfigException { |
| | | // Remove all the constraints associated with this managed |
| | | // object and then re-register them. |
| | | performPostDelete(managedObject); |
| | | performPostAdd(managedObject); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * The client-side constraint handler implementation which enforces |
| | | * referential integrity when aggregating managed objects are added or |
| | | * modified. |
| | | */ |
| | | private class SourceClientHandler extends ClientConstraintHandler { |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean isAddAcceptable(ManagementContext context, ManagedObject<?> managedObject, |
| | | Collection<LocalizableMessage> unacceptableReasons) throws AuthorizationException, CommunicationException { |
| | | // If all of this managed object's "enabled" properties are true |
| | | // then any referenced managed objects must also be enabled. |
| | | boolean needsEnabling = targetNeedsEnablingCondition.evaluate(context, managedObject); |
| | | |
| | | // Check the referenced managed objects exist and, if required, |
| | | // are enabled. |
| | | boolean isAcceptable = true; |
| | | LocalizableMessage ufn = getRelationDefinition().getUserFriendlyName(); |
| | | for (String name : managedObject.getPropertyValues(AggregationPropertyDefinition.this)) { |
| | | // Retrieve the referenced managed object and make sure it |
| | | // exists. |
| | | ManagedObjectPath<?, ?> path = getChildPath(name); |
| | | ManagedObject<?> ref; |
| | | try { |
| | | ref = context.getManagedObject(path); |
| | | } catch (DefinitionDecodingException e) { |
| | | LocalizableMessage msg = ERR_CLIENT_REFINT_TARGET_INVALID.get(ufn, name, getName(), e.getLocalizableMessageObject()); |
| | | unacceptableReasons.add(msg); |
| | | isAcceptable = false; |
| | | continue; |
| | | } catch (ManagedObjectDecodingException e) { |
| | | LocalizableMessage msg = ERR_CLIENT_REFINT_TARGET_INVALID.get(ufn, name, getName(), e.getLocalizableMessageObject()); |
| | | unacceptableReasons.add(msg); |
| | | isAcceptable = false; |
| | | continue; |
| | | } catch (ManagedObjectNotFoundException e) { |
| | | LocalizableMessage msg = ERR_CLIENT_REFINT_TARGET_DANGLING_REFERENCE.get(ufn, name, getName()); |
| | | unacceptableReasons.add(msg); |
| | | isAcceptable = false; |
| | | continue; |
| | | } |
| | | |
| | | // Make sure the reference managed object is enabled. |
| | | if (needsEnabling) { |
| | | if (!targetIsEnabledCondition.evaluate(context, ref)) { |
| | | LocalizableMessage msg = ERR_CLIENT_REFINT_TARGET_DISABLED.get(ufn, name, getName()); |
| | | unacceptableReasons.add(msg); |
| | | isAcceptable = false; |
| | | } |
| | | } |
| | | } |
| | | return isAcceptable; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean isModifyAcceptable(ManagementContext context, ManagedObject<?> managedObject, |
| | | Collection<LocalizableMessage> unacceptableReasons) throws AuthorizationException, CommunicationException { |
| | | // The same constraint applies as for adds. |
| | | return isAddAcceptable(context, managedObject, unacceptableReasons); |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * The client-side constraint handler implementation which enforces |
| | | * referential integrity when aggregated managed objects are deleted or |
| | | * modified. |
| | | */ |
| | | private class TargetClientHandler extends ClientConstraintHandler { |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean isDeleteAcceptable(ManagementContext context, ManagedObjectPath<?, ?> path, |
| | | Collection<LocalizableMessage> unacceptableReasons) throws AuthorizationException, CommunicationException { |
| | | // Any references to the deleted managed object should cause a |
| | | // constraint violation. |
| | | boolean isAcceptable = true; |
| | | for (ManagedObject<?> mo : findReferences(context, getManagedObjectDefinition(), path.getName())) { |
| | | String name = mo.getManagedObjectPath().getName(); |
| | | if (name == null) { |
| | | LocalizableMessage msg = ERR_CLIENT_REFINT_CANNOT_DELETE_WITHOUT_NAME.get(getName(), mo |
| | | .getManagedObjectDefinition().getUserFriendlyName(), getManagedObjectDefinition() |
| | | .getUserFriendlyName()); |
| | | unacceptableReasons.add(msg); |
| | | } else { |
| | | LocalizableMessage msg = ERR_CLIENT_REFINT_CANNOT_DELETE_WITH_NAME.get(getName(), mo |
| | | .getManagedObjectDefinition().getUserFriendlyName(), name, getManagedObjectDefinition() |
| | | .getUserFriendlyName()); |
| | | unacceptableReasons.add(msg); |
| | | } |
| | | isAcceptable = false; |
| | | } |
| | | return isAcceptable; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean isModifyAcceptable(ManagementContext context, ManagedObject<?> managedObject, |
| | | Collection<LocalizableMessage> unacceptableReasons) throws AuthorizationException, CommunicationException { |
| | | // If the modified managed object is disabled and there are some |
| | | // active references then refuse the change. |
| | | if (targetIsEnabledCondition.evaluate(context, managedObject)) { |
| | | return true; |
| | | } |
| | | |
| | | // The referenced managed object is disabled. Need to check for |
| | | // active references. |
| | | boolean isAcceptable = true; |
| | | for (ManagedObject<?> mo : findReferences(context, getManagedObjectDefinition(), managedObject |
| | | .getManagedObjectPath().getName())) { |
| | | if (targetNeedsEnablingCondition.evaluate(context, mo)) { |
| | | String name = mo.getManagedObjectPath().getName(); |
| | | if (name == null) { |
| | | LocalizableMessage msg = ERR_CLIENT_REFINT_CANNOT_DISABLE_WITHOUT_NAME.get(managedObject |
| | | .getManagedObjectDefinition().getUserFriendlyName(), getName(), mo |
| | | .getManagedObjectDefinition().getUserFriendlyName()); |
| | | unacceptableReasons.add(msg); |
| | | } else { |
| | | LocalizableMessage msg = ERR_CLIENT_REFINT_CANNOT_DISABLE_WITH_NAME.get(managedObject |
| | | .getManagedObjectDefinition().getUserFriendlyName(), getName(), mo |
| | | .getManagedObjectDefinition().getUserFriendlyName(), name); |
| | | unacceptableReasons.add(msg); |
| | | } |
| | | isAcceptable = false; |
| | | } |
| | | } |
| | | return isAcceptable; |
| | | } |
| | | |
| | | // Find all managed objects which reference the named managed |
| | | // object using this property. |
| | | private <CC extends ConfigurationClient> List<ManagedObject<? extends CC>> findReferences( |
| | | ManagementContext context, AbstractManagedObjectDefinition<CC, ?> mod, String name) |
| | | throws AuthorizationException, CommunicationException { |
| | | List<ManagedObject<? extends CC>> instances = findInstances(context, mod); |
| | | |
| | | Iterator<ManagedObject<? extends CC>> i = instances.iterator(); |
| | | while (i.hasNext()) { |
| | | ManagedObject<? extends CC> mo = i.next(); |
| | | boolean hasReference = false; |
| | | |
| | | for (String value : mo.getPropertyValues(AggregationPropertyDefinition.this)) { |
| | | if (compare(value, name) == 0) { |
| | | hasReference = true; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (!hasReference) { |
| | | i.remove(); |
| | | } |
| | | } |
| | | |
| | | return instances; |
| | | } |
| | | |
| | | // Find all instances of a specific type of managed object. |
| | | @SuppressWarnings("unchecked") |
| | | private <CC extends ConfigurationClient> List<ManagedObject<? extends CC>> findInstances( |
| | | ManagementContext context, AbstractManagedObjectDefinition<CC, ?> mod) throws AuthorizationException, |
| | | CommunicationException { |
| | | List<ManagedObject<? extends CC>> instances = new LinkedList<ManagedObject<? extends CC>>(); |
| | | |
| | | if (mod == RootCfgDefn.getInstance()) { |
| | | instances.add((ManagedObject<? extends CC>) context.getRootConfigurationManagedObject()); |
| | | } else { |
| | | for (RelationDefinition<? super CC, ?> rd : mod.getAllReverseRelationDefinitions()) { |
| | | for (ManagedObject<?> parent : findInstances(context, rd.getParentDefinition())) { |
| | | try { |
| | | if (rd instanceof SingletonRelationDefinition) { |
| | | SingletonRelationDefinition<? super CC, ?> srd = (SingletonRelationDefinition<? super CC, ?>) rd; |
| | | ManagedObject<?> mo = parent.getChild(srd); |
| | | if (mo.getManagedObjectDefinition().isChildOf(mod)) { |
| | | instances.add((ManagedObject<? extends CC>) mo); |
| | | } |
| | | } else if (rd instanceof OptionalRelationDefinition) { |
| | | OptionalRelationDefinition<? super CC, ?> ord = (OptionalRelationDefinition<? super CC, ?>) rd; |
| | | ManagedObject<?> mo = parent.getChild(ord); |
| | | if (mo.getManagedObjectDefinition().isChildOf(mod)) { |
| | | instances.add((ManagedObject<? extends CC>) mo); |
| | | } |
| | | } else if (rd instanceof InstantiableRelationDefinition) { |
| | | InstantiableRelationDefinition<? super CC, ?> ird = (InstantiableRelationDefinition<? super CC, ?>) rd; |
| | | |
| | | for (String name : parent.listChildren(ird)) { |
| | | ManagedObject<?> mo = parent.getChild(ird, name); |
| | | if (mo.getManagedObjectDefinition().isChildOf(mod)) { |
| | | instances.add((ManagedObject<? extends CC>) mo); |
| | | } |
| | | } |
| | | } |
| | | } catch (OperationsException e) { |
| | | // Ignore all operations exceptions. |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | return instances; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * The tracer object for the debug logger. |
| | | */ |
| | | private static final DebugTracer TRACER = getTracer(); |
| | | |
| | | /** |
| | | * Creates an aggregation property definition builder. |
| | | * |
| | | * @param <C> |
| | | * The type of client managed object configuration that this |
| | | * aggregation property definition refers to. |
| | | * @param <S> |
| | | * The type of server managed object configuration that this |
| | | * aggregation property definition refers to. |
| | | * @param d |
| | | * The managed object definition associated with this property |
| | | * definition. |
| | | * @param propertyName |
| | | * The property name. |
| | | * @return Returns the new aggregation property definition builder. |
| | | */ |
| | | public static <C extends ConfigurationClient, S extends Configuration> Builder<C, S> createBuilder( |
| | | AbstractManagedObjectDefinition<?, ?> d, String propertyName) { |
| | | return new Builder<C, S>(d, propertyName); |
| | | } |
| | | |
| | | // The active server-side referential integrity change listeners |
| | | // associated with this property. |
| | | private final Map<DN, List<ReferentialIntegrityChangeListener>> changeListeners = new HashMap<DN, List<ReferentialIntegrityChangeListener>>(); |
| | | |
| | | // The active server-side referential integrity delete listeners |
| | | // associated with this property. |
| | | private final Map<DN, List<ReferentialIntegrityDeleteListener>> deleteListeners = new HashMap<DN, List<ReferentialIntegrityDeleteListener>>(); |
| | | |
| | | // The name of the managed object which is the parent of the |
| | | // aggregated managed objects. |
| | | private ManagedObjectPath<?, ?> parentPath; |
| | | |
| | | // The string representation of the managed object path specifying |
| | | // the parent of the aggregated managed objects. |
| | | private String parentPathString = null; |
| | | private final String parentPathString; |
| | | |
| | | // The name of a relation in the parent managed object which |
| | | // contains the aggregated managed objects. |
| | | private String rdName = null; |
| | | private final String rdName; |
| | | |
| | | // The condition which is used to determine if a referenced |
| | | // managed object is enabled. |
| | | private Condition targetIsEnabledCondition = Conditions.TRUE; |
| | | // The relation in the parent managed object which contains the |
| | | // aggregated managed objects. |
| | | private InstantiableRelationDefinition<C, S> relationDefinition; |
| | | |
| | | // The source constraint. |
| | | private final Constraint sourceConstraint; |
| | | |
| | | // The condition which is used to determine if a referenced managed |
| | | // object is enabled. |
| | | private final Condition targetIsEnabledCondition; |
| | | |
| | | // The condition which is used to determine whether or not |
| | | // referenced managed objects need to be enabled. |
| | | private Condition targetNeedsEnablingCondition = Conditions.TRUE; |
| | | private final Condition targetNeedsEnablingCondition; |
| | | |
| | | // Private constructor. |
| | | private AggregationPropertyDefinition(AbstractManagedObjectDefinition<?, ?> d, String propertyName, |
| | | EnumSet<PropertyOption> options, AdministratorAction adminAction, |
| | | DefaultBehaviorProvider<String> defaultBehavior, String parentPathString, String rdName, |
| | | Condition targetNeedsEnablingCondition, Condition targetIsEnabledCondition) { |
| | | super(d, String.class, propertyName, options, adminAction, defaultBehavior); |
| | | |
| | | this.parentPathString = parentPathString; |
| | | this.rdName = rdName; |
| | | this.targetNeedsEnablingCondition = targetNeedsEnablingCondition; |
| | | this.targetIsEnabledCondition = targetIsEnabledCondition; |
| | | this.sourceConstraint = new Constraint() { |
| | | |
| | | // Private constructor |
| | | private Builder(AbstractManagedObjectDefinition<?, ?> d, |
| | | String propertyName) { |
| | | super(d, propertyName); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Sets the name of the managed object which is the parent of the |
| | | * aggregated managed objects. |
| | | * <p> |
| | | * This must be defined before the property definition can be |
| | | * built. |
| | | * |
| | | * @param pathString |
| | | * The string representation of the managed object path |
| | | * specifying the parent of the aggregated managed |
| | | * objects. |
| | | */ |
| | | public final void setParentPath(String pathString) { |
| | | this.parentPathString = pathString; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Sets the relation in the parent managed object which contains |
| | | * the aggregated managed objects. |
| | | * <p> |
| | | * This must be defined before the property definition can be |
| | | * built. |
| | | * |
| | | * @param rdName |
| | | * The name of a relation in the parent managed object |
| | | * which contains the aggregated managed objects. |
| | | */ |
| | | public final void setRelationDefinition(String rdName) { |
| | | this.rdName = rdName; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Sets the condition which is used to determine if a referenced |
| | | * managed object is enabled. By default referenced managed |
| | | * objects are assumed to always be enabled. |
| | | * |
| | | * @param condition |
| | | * The condition which is used to determine if a |
| | | * referenced managed object is enabled. |
| | | */ |
| | | public final void setTargetIsEnabledCondition(Condition condition) { |
| | | this.targetIsEnabledCondition = condition; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Sets the condition which is used to determine whether or not |
| | | * referenced managed objects need to be enabled. By default |
| | | * referenced managed objects must always be enabled. |
| | | * |
| | | * @param condition |
| | | * The condition which is used to determine whether or |
| | | * not referenced managed objects need to be enabled. |
| | | */ |
| | | public final void setTargetNeedsEnablingCondition(Condition condition) { |
| | | this.targetNeedsEnablingCondition = condition; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | protected AggregationPropertyDefinition<C, S> buildInstance( |
| | | AbstractManagedObjectDefinition<?, ?> d, String propertyName, |
| | | EnumSet<PropertyOption> options, AdministratorAction adminAction, |
| | | DefaultBehaviorProvider<String> defaultBehavior) { |
| | | // Make sure that the parent path has been defined. |
| | | if (parentPathString == null) { |
| | | throw new IllegalStateException("Parent path undefined"); |
| | | } |
| | | |
| | | // Make sure that the relation definition has been defined. |
| | | if (rdName == null) { |
| | | throw new IllegalStateException("Relation definition undefined"); |
| | | } |
| | | |
| | | return new AggregationPropertyDefinition<C, S>(d, propertyName, options, |
| | | adminAction, defaultBehavior, parentPathString, rdName, |
| | | targetNeedsEnablingCondition, targetIsEnabledCondition); |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * A change listener which prevents the named component from being |
| | | * disabled. |
| | | */ |
| | | private class ReferentialIntegrityChangeListener implements |
| | | ServerManagedObjectChangeListener<S> { |
| | | |
| | | // The error message which should be returned if an attempt is |
| | | // made to disable the referenced component. |
| | | private final Message message; |
| | | |
| | | // The path of the referenced component. |
| | | private final ManagedObjectPath<C, S> path; |
| | | |
| | | |
| | | |
| | | // Creates a new referential integrity delete listener. |
| | | private ReferentialIntegrityChangeListener(ManagedObjectPath<C, S> path, |
| | | Message message) { |
| | | this.path = path; |
| | | this.message = message; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public ConfigChangeResult applyConfigurationChange( |
| | | ServerManagedObject<? extends S> mo) { |
| | | try { |
| | | if (targetIsEnabledCondition.evaluate(mo)) { |
| | | return new ConfigChangeResult(ResultCode.SUCCESS, false); |
| | | } |
| | | } catch (ConfigException e) { |
| | | // This should not happen - ignore it and throw an exception |
| | | // anyway below. |
| | | } |
| | | |
| | | // This should not happen - the previous call-back should have |
| | | // trapped this. |
| | | throw new IllegalStateException("Attempting to disable a referenced " |
| | | + relationDefinition.getChildDefinition().getUserFriendlyName()); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public boolean isConfigurationChangeAcceptable( |
| | | ServerManagedObject<? extends S> mo, |
| | | List<Message> unacceptableReasons) { |
| | | // Always prevent the referenced component from being |
| | | // disabled. |
| | | try { |
| | | if (!targetIsEnabledCondition.evaluate(mo)) { |
| | | unacceptableReasons.add(message); |
| | | return false; |
| | | } else { |
| | | return true; |
| | | } |
| | | } catch (ConfigException e) { |
| | | // The condition could not be evaluated. |
| | | if (debugEnabled()) { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | Message message = ERR_REFINT_UNABLE_TO_EVALUATE_TARGET_CONDITION.get(mo |
| | | .getManagedObjectDefinition().getUserFriendlyName(), String |
| | | .valueOf(mo.getDN()), StaticUtils.getExceptionMessage(e)); |
| | | ErrorLogger.logError(message); |
| | | unacceptableReasons.add(message); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | // Gets the path associated with this listener. |
| | | private ManagedObjectPath<C, S> getManagedObjectPath() { |
| | | return path; |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * A delete listener which prevents the named component from being |
| | | * deleted. |
| | | */ |
| | | private class ReferentialIntegrityDeleteListener implements |
| | | ConfigurationDeleteListener<S> { |
| | | |
| | | // The DN of the referenced configuration entry. |
| | | private final DN dn; |
| | | |
| | | // The error message which should be returned if an attempt is |
| | | // made to delete the referenced component. |
| | | private final Message message; |
| | | |
| | | |
| | | |
| | | // Creates a new referential integrity delete listener. |
| | | private ReferentialIntegrityDeleteListener(DN dn, Message message) { |
| | | this.dn = dn; |
| | | this.message = message; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public ConfigChangeResult applyConfigurationDelete(S configuration) { |
| | | // This should not happen - the |
| | | // isConfigurationDeleteAcceptable() call-back should have |
| | | // trapped this. |
| | | if (configuration.dn().equals(dn)) { |
| | | // This should not happen - the |
| | | // isConfigurationDeleteAcceptable() call-back should have |
| | | // trapped this. |
| | | throw new IllegalStateException("Attempting to delete a referenced " |
| | | + relationDefinition.getChildDefinition().getUserFriendlyName()); |
| | | } else { |
| | | return new ConfigChangeResult(ResultCode.SUCCESS, false); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public boolean isConfigurationDeleteAcceptable(S configuration, |
| | | List<Message> unacceptableReasons) { |
| | | if (configuration.dn().equals(dn)) { |
| | | // Always prevent deletion of the referenced component. |
| | | unacceptableReasons.add(message); |
| | | return false; |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * The server-side constraint handler implementation. |
| | | */ |
| | | private class ServerHandler extends ServerConstraintHandler { |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean isUsable(ServerManagedObject<?> managedObject, |
| | | Collection<Message> unacceptableReasons) throws ConfigException { |
| | | SortedSet<String> names = managedObject |
| | | .getPropertyValues(AggregationPropertyDefinition.this); |
| | | ServerManagementContext context = ServerManagementContext.getInstance(); |
| | | Message thisUFN = managedObject.getManagedObjectDefinition() |
| | | .getUserFriendlyName(); |
| | | String thisDN = managedObject.getDN().toString(); |
| | | Message thatUFN = getRelationDefinition().getUserFriendlyName(); |
| | | |
| | | boolean isUsable = true; |
| | | boolean needsEnabling = targetNeedsEnablingCondition |
| | | .evaluate(managedObject); |
| | | for (String name : names) { |
| | | ManagedObjectPath<C, S> path = getChildPath(name); |
| | | String thatDN = path.toDN().toString(); |
| | | |
| | | if (!context.managedObjectExists(path)) { |
| | | Message msg = ERR_SERVER_REFINT_DANGLING_REFERENCE.get(name, |
| | | getName(), thisUFN, thisDN, thatUFN, thatDN); |
| | | unacceptableReasons.add(msg); |
| | | isUsable = false; |
| | | } else if (needsEnabling) { |
| | | // Check that the referenced component is enabled if |
| | | // required. |
| | | ServerManagedObject<? extends S> ref = context.getManagedObject(path); |
| | | if (!targetIsEnabledCondition.evaluate(ref)) { |
| | | Message msg = ERR_SERVER_REFINT_TARGET_DISABLED.get(name, |
| | | getName(), thisUFN, thisDN, thatUFN, thatDN); |
| | | unacceptableReasons.add(msg); |
| | | isUsable = false; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return isUsable; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void performPostAdd(ServerManagedObject<?> managedObject) |
| | | throws ConfigException { |
| | | // First make sure existing listeners associated with this |
| | | // managed object are removed. This is required in order to |
| | | // prevent multiple change listener registrations from |
| | | // occurring, for example if this call-back is invoked multiple |
| | | // times after the same add event. |
| | | performPostDelete(managedObject); |
| | | |
| | | // Add change and delete listeners against all referenced |
| | | // components. |
| | | Message thisUFN = managedObject.getManagedObjectDefinition() |
| | | .getUserFriendlyName(); |
| | | String thisDN = managedObject.getDN().toString(); |
| | | Message thatUFN = getRelationDefinition().getUserFriendlyName(); |
| | | |
| | | // Referenced managed objects will only need a change listener |
| | | // if they have can be disabled. |
| | | boolean needsChangeListeners = targetNeedsEnablingCondition |
| | | .evaluate(managedObject); |
| | | |
| | | // Delete listeners need to be registered against the parent |
| | | // entry of the referenced components. |
| | | ServerManagementContext context = ServerManagementContext.getInstance(); |
| | | ManagedObjectPath<?, ?> parentPath = getParentPath(); |
| | | ServerManagedObject<?> parent = context.getManagedObject(parentPath); |
| | | |
| | | // Create entries in the listener tables. |
| | | List<ReferentialIntegrityDeleteListener> dlist = |
| | | new LinkedList<ReferentialIntegrityDeleteListener>(); |
| | | deleteListeners.put(managedObject.getDN(), dlist); |
| | | |
| | | List<ReferentialIntegrityChangeListener> clist = |
| | | new LinkedList<ReferentialIntegrityChangeListener>(); |
| | | changeListeners.put(managedObject.getDN(), clist); |
| | | |
| | | for (String name : managedObject |
| | | .getPropertyValues(AggregationPropertyDefinition.this)) { |
| | | ManagedObjectPath<C, S> path = getChildPath(name); |
| | | DN dn = path.toDN(); |
| | | String thatDN = dn.toString(); |
| | | |
| | | // Register the delete listener. |
| | | Message msg = ERR_SERVER_REFINT_CANNOT_DELETE.get(thatUFN, thatDN, |
| | | getName(), thisUFN, thisDN); |
| | | ReferentialIntegrityDeleteListener dl = |
| | | new ReferentialIntegrityDeleteListener(dn, msg); |
| | | parent.registerDeleteListener(getRelationDefinition(), dl); |
| | | dlist.add(dl); |
| | | |
| | | // Register the change listener if required. |
| | | if (needsChangeListeners) { |
| | | ServerManagedObject<? extends S> ref = context.getManagedObject(path); |
| | | msg = ERR_SERVER_REFINT_CANNOT_DISABLE.get(thatUFN, thatDN, |
| | | getName(), thisUFN, thisDN); |
| | | ReferentialIntegrityChangeListener cl = |
| | | new ReferentialIntegrityChangeListener(path, msg); |
| | | ref.registerChangeListener(cl); |
| | | clist.add(cl); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void performPostDelete(ServerManagedObject<?> managedObject) |
| | | throws ConfigException { |
| | | // Remove any registered delete and change listeners. |
| | | ServerManagementContext context = ServerManagementContext.getInstance(); |
| | | DN dn = managedObject.getDN(); |
| | | |
| | | // Delete listeners need to be deregistered against the parent |
| | | // entry of the referenced components. |
| | | ManagedObjectPath<?, ?> parentPath = getParentPath(); |
| | | ServerManagedObject<?> parent = context.getManagedObject(parentPath); |
| | | if (deleteListeners.containsKey(dn)) { |
| | | for (ReferentialIntegrityDeleteListener dl : deleteListeners.get(dn)) { |
| | | parent.deregisterDeleteListener(getRelationDefinition(), dl); |
| | | } |
| | | deleteListeners.remove(dn); |
| | | } |
| | | |
| | | // Change listeners need to be deregistered from their |
| | | // associated referenced component. |
| | | if (changeListeners.containsKey(dn)) { |
| | | for (ReferentialIntegrityChangeListener cl : changeListeners.get(dn)) { |
| | | ManagedObjectPath<C, S> path = cl.getManagedObjectPath(); |
| | | ServerManagedObject<? extends S> ref = context.getManagedObject(path); |
| | | ref.deregisterChangeListener(cl); |
| | | } |
| | | changeListeners.remove(dn); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void performPostModify(ServerManagedObject<?> managedObject) |
| | | throws ConfigException { |
| | | // Remove all the constraints associated with this managed |
| | | // object and then re-register them. |
| | | performPostDelete(managedObject); |
| | | performPostAdd(managedObject); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * The client-side constraint handler implementation which enforces |
| | | * referential integrity when aggregating managed objects are added |
| | | * or modified. |
| | | */ |
| | | private class SourceClientHandler extends ClientConstraintHandler { |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean isAddAcceptable(ManagementContext context, |
| | | ManagedObject<?> managedObject, Collection<Message> unacceptableReasons) |
| | | throws AuthorizationException, CommunicationException { |
| | | // If all of this managed object's "enabled" properties are true |
| | | // then any referenced managed objects must also be enabled. |
| | | boolean needsEnabling = targetNeedsEnablingCondition.evaluate(context, |
| | | managedObject); |
| | | |
| | | // Check the referenced managed objects exist and, if required, |
| | | // are enabled. |
| | | boolean isAcceptable = true; |
| | | Message ufn = getRelationDefinition().getUserFriendlyName(); |
| | | for (String name : managedObject |
| | | .getPropertyValues(AggregationPropertyDefinition.this)) { |
| | | // Retrieve the referenced managed object and make sure it |
| | | // exists. |
| | | ManagedObjectPath<?, ?> path = getChildPath(name); |
| | | ManagedObject<?> ref; |
| | | try { |
| | | ref = context.getManagedObject(path); |
| | | } catch (DefinitionDecodingException e) { |
| | | Message msg = ERR_CLIENT_REFINT_TARGET_INVALID.get(ufn, name, |
| | | getName(), e.getMessageObject()); |
| | | unacceptableReasons.add(msg); |
| | | isAcceptable = false; |
| | | continue; |
| | | } catch (ManagedObjectDecodingException e) { |
| | | Message msg = ERR_CLIENT_REFINT_TARGET_INVALID.get(ufn, name, |
| | | getName(), e.getMessageObject()); |
| | | unacceptableReasons.add(msg); |
| | | isAcceptable = false; |
| | | continue; |
| | | } catch (ManagedObjectNotFoundException e) { |
| | | Message msg = ERR_CLIENT_REFINT_TARGET_DANGLING_REFERENCE.get(ufn, |
| | | name, getName()); |
| | | unacceptableReasons.add(msg); |
| | | isAcceptable = false; |
| | | continue; |
| | | } |
| | | |
| | | // Make sure the reference managed object is enabled. |
| | | if (needsEnabling) { |
| | | if (!targetIsEnabledCondition.evaluate(context, ref)) { |
| | | Message msg = ERR_CLIENT_REFINT_TARGET_DISABLED.get(ufn, name, |
| | | getName()); |
| | | unacceptableReasons.add(msg); |
| | | isAcceptable = false; |
| | | } |
| | | } |
| | | } |
| | | return isAcceptable; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean isModifyAcceptable(ManagementContext context, |
| | | ManagedObject<?> managedObject, Collection<Message> unacceptableReasons) |
| | | throws AuthorizationException, CommunicationException { |
| | | // The same constraint applies as for adds. |
| | | return isAddAcceptable(context, managedObject, unacceptableReasons); |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * The client-side constraint handler implementation which enforces |
| | | * referential integrity when aggregated managed objects are deleted |
| | | * or modified. |
| | | */ |
| | | private class TargetClientHandler extends ClientConstraintHandler { |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean isDeleteAcceptable(ManagementContext context, |
| | | ManagedObjectPath<?, ?> path, Collection<Message> unacceptableReasons) |
| | | throws AuthorizationException, CommunicationException { |
| | | // Any references to the deleted managed object should cause a |
| | | // constraint violation. |
| | | boolean isAcceptable = true; |
| | | for (ManagedObject<?> mo : findReferences(context, |
| | | getManagedObjectDefinition(), path.getName())) { |
| | | String name = mo.getManagedObjectPath().getName(); |
| | | if (name == null) { |
| | | Message msg = ERR_CLIENT_REFINT_CANNOT_DELETE_WITHOUT_NAME.get( |
| | | getName(), mo.getManagedObjectDefinition().getUserFriendlyName(), |
| | | getManagedObjectDefinition().getUserFriendlyName()); |
| | | unacceptableReasons.add(msg); |
| | | } else { |
| | | Message msg = ERR_CLIENT_REFINT_CANNOT_DELETE_WITH_NAME.get( |
| | | getName(), mo.getManagedObjectDefinition().getUserFriendlyName(), |
| | | name, getManagedObjectDefinition().getUserFriendlyName()); |
| | | unacceptableReasons.add(msg); |
| | | } |
| | | isAcceptable = false; |
| | | } |
| | | return isAcceptable; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean isModifyAcceptable(ManagementContext context, |
| | | ManagedObject<?> managedObject, Collection<Message> unacceptableReasons) |
| | | throws AuthorizationException, CommunicationException { |
| | | // If the modified managed object is disabled and there are some |
| | | // active references then refuse the change. |
| | | if (targetIsEnabledCondition.evaluate(context, managedObject)) { |
| | | return true; |
| | | } |
| | | |
| | | // The referenced managed object is disabled. Need to check for |
| | | // active references. |
| | | boolean isAcceptable = true; |
| | | for (ManagedObject<?> mo : findReferences(context, |
| | | getManagedObjectDefinition(), managedObject.getManagedObjectPath() |
| | | .getName())) { |
| | | if (targetNeedsEnablingCondition.evaluate(context, mo)) { |
| | | String name = mo.getManagedObjectPath().getName(); |
| | | if (name == null) { |
| | | Message msg = ERR_CLIENT_REFINT_CANNOT_DISABLE_WITHOUT_NAME.get( |
| | | managedObject.getManagedObjectDefinition() |
| | | .getUserFriendlyName(), getName(), mo |
| | | .getManagedObjectDefinition().getUserFriendlyName()); |
| | | unacceptableReasons.add(msg); |
| | | } else { |
| | | Message msg = ERR_CLIENT_REFINT_CANNOT_DISABLE_WITH_NAME.get( |
| | | managedObject.getManagedObjectDefinition() |
| | | .getUserFriendlyName(), getName(), mo |
| | | .getManagedObjectDefinition().getUserFriendlyName(), name); |
| | | unacceptableReasons.add(msg); |
| | | } |
| | | isAcceptable = false; |
| | | } |
| | | } |
| | | return isAcceptable; |
| | | } |
| | | |
| | | |
| | | |
| | | // Find all managed objects which reference the named managed |
| | | // object using this property. |
| | | private <CC extends ConfigurationClient> |
| | | List<ManagedObject<? extends CC>> findReferences( |
| | | ManagementContext context, AbstractManagedObjectDefinition<CC, ?> mod, |
| | | String name) throws AuthorizationException, CommunicationException { |
| | | List<ManagedObject<? extends CC>> instances = findInstances(context, mod); |
| | | |
| | | Iterator<ManagedObject<? extends CC>> i = instances.iterator(); |
| | | while (i.hasNext()) { |
| | | ManagedObject<? extends CC> mo = i.next(); |
| | | boolean hasReference = false; |
| | | |
| | | for (String value : mo |
| | | .getPropertyValues(AggregationPropertyDefinition.this)) { |
| | | if (compare(value, name) == 0) { |
| | | hasReference = true; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (!hasReference) { |
| | | i.remove(); |
| | | } |
| | | } |
| | | |
| | | return instances; |
| | | } |
| | | |
| | | |
| | | |
| | | // Find all instances of a specific type of managed object. |
| | | @SuppressWarnings("unchecked") |
| | | private <CC extends ConfigurationClient> |
| | | List<ManagedObject<? extends CC>> findInstances( |
| | | ManagementContext context, AbstractManagedObjectDefinition<CC, ?> mod) |
| | | throws AuthorizationException, CommunicationException { |
| | | List<ManagedObject<? extends CC>> instances = |
| | | new LinkedList<ManagedObject<? extends CC>>(); |
| | | |
| | | if (mod == RootCfgDefn.getInstance()) { |
| | | instances.add((ManagedObject<? extends CC>) context |
| | | .getRootConfigurationManagedObject()); |
| | | } else { |
| | | for (RelationDefinition<? super CC, ?> rd : mod |
| | | .getAllReverseRelationDefinitions()) { |
| | | for (ManagedObject<?> parent : findInstances(context, rd |
| | | .getParentDefinition())) { |
| | | try { |
| | | if (rd instanceof SingletonRelationDefinition) { |
| | | SingletonRelationDefinition<? super CC, ?> srd = |
| | | (SingletonRelationDefinition<? super CC, ?>) rd; |
| | | ManagedObject<?> mo = parent.getChild(srd); |
| | | if (mo.getManagedObjectDefinition().isChildOf(mod)) { |
| | | instances.add((ManagedObject<? extends CC>) mo); |
| | | } |
| | | } else if (rd instanceof OptionalRelationDefinition) { |
| | | OptionalRelationDefinition<? super CC, ?> ord = |
| | | (OptionalRelationDefinition<? super CC, ?>) rd; |
| | | ManagedObject<?> mo = parent.getChild(ord); |
| | | if (mo.getManagedObjectDefinition().isChildOf(mod)) { |
| | | instances.add((ManagedObject<? extends CC>) mo); |
| | | } |
| | | } else if (rd instanceof InstantiableRelationDefinition) { |
| | | InstantiableRelationDefinition<? super CC, ?> ird = |
| | | (InstantiableRelationDefinition<? super CC, ?>) rd; |
| | | |
| | | for (String name : parent.listChildren(ird)) { |
| | | ManagedObject<?> mo = parent.getChild(ird, name); |
| | | if (mo.getManagedObjectDefinition().isChildOf(mod)) { |
| | | instances.add((ManagedObject<? extends CC>) mo); |
| | | } |
| | | } |
| | | } |
| | | } catch (OperationsException e) { |
| | | // Ignore all operations exceptions. |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public Collection<ClientConstraintHandler> getClientConstraintHandlers() { |
| | | ClientConstraintHandler handler = new SourceClientHandler(); |
| | | return Collections.singleton(handler); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public Collection<ServerConstraintHandler> getServerConstraintHandlers() { |
| | | ServerConstraintHandler handler = new ServerHandler(); |
| | | return Collections.singleton(handler); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public <R, P> R accept(PropertyDefinitionVisitor<R, P> v, P p) { |
| | | return v.visitAggregation(this, p); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public <R, P> R accept(PropertyValueVisitor<R, P> v, String value, P p) { |
| | | return v.visitAggregation(this, value, p); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public String decodeValue(String value) throws IllegalPropertyValueStringException { |
| | | ensureNotNull(value); |
| | | |
| | | try { |
| | | validateValue(value); |
| | | return value; |
| | | } catch (IllegalPropertyValueException e) { |
| | | throw new IllegalPropertyValueStringException(this, value); |
| | | } |
| | | } |
| | | |
| | | return instances; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * The tracer object for the debug logger. |
| | | */ |
| | | private static final DebugTracer TRACER = getTracer(); |
| | | |
| | | |
| | | |
| | | /** |
| | | * Creates an aggregation property definition builder. |
| | | * |
| | | * @param <C> |
| | | * The type of client managed object configuration that |
| | | * this aggregation property definition refers to. |
| | | * @param <S> |
| | | * The type of server managed object configuration that |
| | | * this aggregation property definition refers to. |
| | | * @param d |
| | | * The managed object definition associated with this |
| | | * property definition. |
| | | * @param propertyName |
| | | * The property name. |
| | | * @return Returns the new aggregation property definition builder. |
| | | */ |
| | | public static <C extends ConfigurationClient, S extends Configuration> |
| | | Builder<C, S> createBuilder( |
| | | AbstractManagedObjectDefinition<?, ?> d, String propertyName) { |
| | | return new Builder<C, S>(d, propertyName); |
| | | } |
| | | |
| | | // The active server-side referential integrity change listeners |
| | | // associated with this property. |
| | | private final Map<DN, List<ReferentialIntegrityChangeListener>> |
| | | changeListeners = new HashMap<DN, |
| | | List<ReferentialIntegrityChangeListener>>(); |
| | | |
| | | // The active server-side referential integrity delete listeners |
| | | // associated with this property. |
| | | private final Map<DN, List<ReferentialIntegrityDeleteListener>> |
| | | deleteListeners = new HashMap<DN, |
| | | List<ReferentialIntegrityDeleteListener>>(); |
| | | |
| | | // The name of the managed object which is the parent of the |
| | | // aggregated managed objects. |
| | | private ManagedObjectPath<?, ?> parentPath; |
| | | |
| | | // The string representation of the managed object path specifying |
| | | // the parent of the aggregated managed objects. |
| | | private final String parentPathString; |
| | | |
| | | // The name of a relation in the parent managed object which |
| | | // contains the aggregated managed objects. |
| | | private final String rdName; |
| | | |
| | | // The relation in the parent managed object which contains the |
| | | // aggregated managed objects. |
| | | private InstantiableRelationDefinition<C, S> relationDefinition; |
| | | |
| | | // The source constraint. |
| | | private final Constraint sourceConstraint; |
| | | |
| | | // The condition which is used to determine if a referenced managed |
| | | // object is enabled. |
| | | private final Condition targetIsEnabledCondition; |
| | | |
| | | // The condition which is used to determine whether or not |
| | | // referenced managed objects need to be enabled. |
| | | private final Condition targetNeedsEnablingCondition; |
| | | |
| | | |
| | | |
| | | // Private constructor. |
| | | private AggregationPropertyDefinition( |
| | | AbstractManagedObjectDefinition<?, ?> d, String propertyName, |
| | | EnumSet<PropertyOption> options, AdministratorAction adminAction, |
| | | DefaultBehaviorProvider<String> defaultBehavior, String parentPathString, |
| | | String rdName, Condition targetNeedsEnablingCondition, |
| | | Condition targetIsEnabledCondition) { |
| | | super(d, String.class, propertyName, options, adminAction, defaultBehavior); |
| | | |
| | | this.parentPathString = parentPathString; |
| | | this.rdName = rdName; |
| | | this.targetNeedsEnablingCondition = targetNeedsEnablingCondition; |
| | | this.targetIsEnabledCondition = targetIsEnabledCondition; |
| | | this.sourceConstraint = new Constraint() { |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public Collection<ClientConstraintHandler> getClientConstraintHandlers() { |
| | | ClientConstraintHandler handler = new SourceClientHandler(); |
| | | return Collections.singleton(handler); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public Collection<ServerConstraintHandler> getServerConstraintHandlers() { |
| | | ServerConstraintHandler handler = new ServerHandler(); |
| | | return Collections.singleton(handler); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public <R, P> R accept(PropertyDefinitionVisitor<R, P> v, P p) { |
| | | return v.visitAggregation(this, p); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public <R, P> R accept(PropertyValueVisitor<R, P> v, String value, P p) { |
| | | return v.visitAggregation(this, value, p); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public String decodeValue(String value) |
| | | throws IllegalPropertyValueStringException { |
| | | ensureNotNull(value); |
| | | |
| | | try { |
| | | validateValue(value); |
| | | return value; |
| | | } catch (IllegalPropertyValueException e) { |
| | | throw new IllegalPropertyValueStringException(this, value); |
| | | /** |
| | | * Constructs a DN for a referenced managed object having the provided name. |
| | | * This method is implemented by first calling {@link #getChildPath(String)} |
| | | * and then invoking {@code ManagedObjectPath.toDN()} on the returned path. |
| | | * |
| | | * @param name |
| | | * The name of the child managed object. |
| | | * @return Returns a DN for a referenced managed object having the provided |
| | | * name. |
| | | */ |
| | | public final DN getChildDN(String name) { |
| | | return getChildPath(name).toDN(); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Constructs a DN for a referenced managed object having the |
| | | * provided name. This method is implemented by first calling |
| | | * {@link #getChildPath(String)} and then invoking |
| | | * {@code ManagedObjectPath.toDN()} on the returned path. |
| | | * |
| | | * @param name |
| | | * The name of the child managed object. |
| | | * @return Returns a DN for a referenced managed object having the |
| | | * provided name. |
| | | */ |
| | | public final DN getChildDN(String name) { |
| | | return getChildPath(name).toDN(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Constructs a managed object path for a referenced managed object |
| | | * having the provided name. |
| | | * |
| | | * @param name |
| | | * The name of the child managed object. |
| | | * @return Returns a managed object path for a referenced managed |
| | | * object having the provided name. |
| | | */ |
| | | public final ManagedObjectPath<C, S> getChildPath(String name) { |
| | | return parentPath.child(relationDefinition, name); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Gets the name of the managed object which is the parent of the |
| | | * aggregated managed objects. |
| | | * |
| | | * @return Returns the name of the managed object which is the |
| | | * parent of the aggregated managed objects. |
| | | */ |
| | | public final ManagedObjectPath<?, ?> getParentPath() { |
| | | return parentPath; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Gets the relation in the parent managed object which contains the |
| | | * aggregated managed objects. |
| | | * |
| | | * @return Returns the relation in the parent managed object which |
| | | * contains the aggregated managed objects. |
| | | */ |
| | | public final InstantiableRelationDefinition<C, S> getRelationDefinition() { |
| | | return relationDefinition; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Gets the constraint which should be enforced on the aggregating |
| | | * managed object. |
| | | * |
| | | * @return Returns the constraint which should be enforced on the |
| | | * aggregating managed object. |
| | | */ |
| | | public final Constraint getSourceConstraint() { |
| | | return sourceConstraint; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Gets the optional constraint synopsis of this aggregation |
| | | * property definition in the default locale. The constraint |
| | | * synopsis describes when and how referenced managed objects must |
| | | * be enabled. When there are no constraints between the source |
| | | * managed object and the objects it references through this |
| | | * aggregation, <code>null</code> is returned. |
| | | * |
| | | * @return Returns the optional constraint synopsis of this |
| | | * aggregation property definition in the default locale, or |
| | | * <code>null</code> if there is no constraint synopsis. |
| | | */ |
| | | public final Message getSourceConstraintSynopsis() { |
| | | return getSourceConstraintSynopsis(Locale.getDefault()); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Gets the optional constraint synopsis of this aggregation |
| | | * property definition in the specified locale.The constraint |
| | | * synopsis describes when and how referenced managed objects must |
| | | * be enabled. When there are no constraints between the source |
| | | * managed object and the objects it references through this |
| | | * aggregation, <code>null</code> is returned. |
| | | * |
| | | * @param locale |
| | | * The locale. |
| | | * @return Returns the optional constraint synopsis of this |
| | | * aggregation property definition in the specified locale, |
| | | * or <code>null</code> if there is no constraint |
| | | * synopsis. |
| | | */ |
| | | public final Message getSourceConstraintSynopsis(Locale locale) { |
| | | ManagedObjectDefinitionI18NResource resource = |
| | | ManagedObjectDefinitionI18NResource.getInstance(); |
| | | String property = "property." + getName() |
| | | + ".syntax.aggregation.constraint-synopsis"; |
| | | try { |
| | | return resource |
| | | .getMessage(getManagedObjectDefinition(), property, locale); |
| | | } catch (MissingResourceException e) { |
| | | return null; |
| | | /** |
| | | * Constructs a managed object path for a referenced managed object having |
| | | * the provided name. |
| | | * |
| | | * @param name |
| | | * The name of the child managed object. |
| | | * @return Returns a managed object path for a referenced managed object |
| | | * having the provided name. |
| | | */ |
| | | public final ManagedObjectPath<C, S> getChildPath(String name) { |
| | | return parentPath.child(relationDefinition, name); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Gets the condition which is used to determine if a referenced |
| | | * managed object is enabled. |
| | | * |
| | | * @return Returns the condition which is used to determine if a |
| | | * referenced managed object is enabled. |
| | | */ |
| | | public final Condition getTargetIsEnabledCondition() { |
| | | return targetIsEnabledCondition; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Gets the condition which is used to determine whether or not |
| | | * referenced managed objects need to be enabled. |
| | | * |
| | | * @return Returns the condition which is used to determine whether |
| | | * or not referenced managed objects need to be enabled. |
| | | */ |
| | | public final Condition getTargetNeedsEnablingCondition() { |
| | | return targetNeedsEnablingCondition; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public String normalizeValue(String value) |
| | | throws IllegalPropertyValueException { |
| | | try { |
| | | Reference<C, S> reference = Reference.parseName(parentPath, |
| | | relationDefinition, value); |
| | | return reference.getNormalizedName(); |
| | | } catch (IllegalArgumentException e) { |
| | | throw new IllegalPropertyValueException(this, value); |
| | | /** |
| | | * Gets the name of the managed object which is the parent of the aggregated |
| | | * managed objects. |
| | | * |
| | | * @return Returns the name of the managed object which is the parent of the |
| | | * aggregated managed objects. |
| | | */ |
| | | public final ManagedObjectPath<?, ?> getParentPath() { |
| | | return parentPath; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void toString(StringBuilder builder) { |
| | | super.toString(builder); |
| | | |
| | | builder.append(" parentPath="); |
| | | builder.append(parentPath); |
| | | |
| | | builder.append(" relationDefinition="); |
| | | builder.append(relationDefinition.getName()); |
| | | |
| | | builder.append(" targetNeedsEnablingCondition="); |
| | | builder.append(String.valueOf(targetNeedsEnablingCondition)); |
| | | |
| | | builder.append(" targetIsEnabledCondition="); |
| | | builder.append(String.valueOf(targetIsEnabledCondition)); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void validateValue(String value) throws IllegalPropertyValueException { |
| | | try { |
| | | Reference.parseName(parentPath, relationDefinition, value); |
| | | } catch (IllegalArgumentException e) { |
| | | throw new IllegalPropertyValueException(this, value); |
| | | /** |
| | | * Gets the relation in the parent managed object which contains the |
| | | * aggregated managed objects. |
| | | * |
| | | * @return Returns the relation in the parent managed object which contains |
| | | * the aggregated managed objects. |
| | | */ |
| | | public final InstantiableRelationDefinition<C, S> getRelationDefinition() { |
| | | return relationDefinition; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Gets the constraint which should be enforced on the aggregating managed |
| | | * object. |
| | | * |
| | | * @return Returns the constraint which should be enforced on the |
| | | * aggregating managed object. |
| | | */ |
| | | public final Constraint getSourceConstraint() { |
| | | return sourceConstraint; |
| | | } |
| | | |
| | | /** |
| | | * Gets the optional constraint synopsis of this aggregation property |
| | | * definition in the default locale. The constraint synopsis describes when |
| | | * and how referenced managed objects must be enabled. When there are no |
| | | * constraints between the source managed object and the objects it |
| | | * references through this aggregation, <code>null</code> is returned. |
| | | * |
| | | * @return Returns the optional constraint synopsis of this aggregation |
| | | * property definition in the default locale, or <code>null</code> |
| | | * if there is no constraint synopsis. |
| | | */ |
| | | public final LocalizableMessage getSourceConstraintSynopsis() { |
| | | return getSourceConstraintSynopsis(Locale.getDefault()); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @SuppressWarnings("unchecked") |
| | | @Override |
| | | public void initialize() throws Exception { |
| | | // Decode the path. |
| | | parentPath = ManagedObjectPath.valueOf(parentPathString); |
| | | /** |
| | | * Gets the optional constraint synopsis of this aggregation property |
| | | * definition in the specified locale.The constraint synopsis describes when |
| | | * and how referenced managed objects must be enabled. When there are no |
| | | * constraints between the source managed object and the objects it |
| | | * references through this aggregation, <code>null</code> is returned. |
| | | * |
| | | * @param locale |
| | | * The locale. |
| | | * @return Returns the optional constraint synopsis of this aggregation |
| | | * property definition in the specified locale, or <code>null</code> |
| | | * if there is no constraint synopsis. |
| | | */ |
| | | public final LocalizableMessage getSourceConstraintSynopsis(Locale locale) { |
| | | ManagedObjectDefinitionI18NResource resource = ManagedObjectDefinitionI18NResource.getInstance(); |
| | | String property = "property." + getName() + ".syntax.aggregation.constraint-synopsis"; |
| | | try { |
| | | return resource.getLocalizableMessage(getManagedObjectDefinition(), property, locale); |
| | | } catch (MissingResourceException e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | // Decode the relation definition. |
| | | AbstractManagedObjectDefinition<?, ?> parent = parentPath |
| | | .getManagedObjectDefinition(); |
| | | RelationDefinition<?, ?> rd = parent.getRelationDefinition(rdName); |
| | | relationDefinition = (InstantiableRelationDefinition<C, S>) rd; |
| | | /** |
| | | * Gets the condition which is used to determine if a referenced managed |
| | | * object is enabled. |
| | | * |
| | | * @return Returns the condition which is used to determine if a referenced |
| | | * managed object is enabled. |
| | | */ |
| | | public final Condition getTargetIsEnabledCondition() { |
| | | return targetIsEnabledCondition; |
| | | } |
| | | |
| | | // Now decode the conditions. |
| | | targetNeedsEnablingCondition.initialize(getManagedObjectDefinition()); |
| | | targetIsEnabledCondition.initialize(rd.getChildDefinition()); |
| | | /** |
| | | * Gets the condition which is used to determine whether or not referenced |
| | | * managed objects need to be enabled. |
| | | * |
| | | * @return Returns the condition which is used to determine whether or not |
| | | * referenced managed objects need to be enabled. |
| | | */ |
| | | public final Condition getTargetNeedsEnablingCondition() { |
| | | return targetNeedsEnablingCondition; |
| | | } |
| | | |
| | | // Register a client-side constraint with the referenced |
| | | // definition. This will be used to enforce referential integrity |
| | | // for actions performed against referenced managed objects. |
| | | Constraint constraint = new Constraint() { |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public String normalizeValue(String value) throws IllegalPropertyValueException { |
| | | try { |
| | | Reference<C, S> reference = Reference.parseName(parentPath, relationDefinition, value); |
| | | return reference.getNormalizedName(); |
| | | } catch (IllegalArgumentException e) { |
| | | throw new IllegalPropertyValueException(this, value); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public Collection<ClientConstraintHandler> getClientConstraintHandlers() { |
| | | ClientConstraintHandler handler = new TargetClientHandler(); |
| | | return Collections.singleton(handler); |
| | | } |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void toString(StringBuilder builder) { |
| | | super.toString(builder); |
| | | |
| | | builder.append(" parentPath="); |
| | | builder.append(parentPath); |
| | | |
| | | builder.append(" relationDefinition="); |
| | | builder.append(relationDefinition.getName()); |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public Collection<ServerConstraintHandler> getServerConstraintHandlers() { |
| | | return Collections.emptyList(); |
| | | } |
| | | }; |
| | | builder.append(" targetNeedsEnablingCondition="); |
| | | builder.append(String.valueOf(targetNeedsEnablingCondition)); |
| | | |
| | | rd.getChildDefinition().registerConstraint(constraint); |
| | | } |
| | | builder.append(" targetIsEnabledCondition="); |
| | | builder.append(String.valueOf(targetIsEnabledCondition)); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void validateValue(String value) throws IllegalPropertyValueException { |
| | | try { |
| | | Reference.parseName(parentPath, relationDefinition, value); |
| | | } catch (IllegalArgumentException e) { |
| | | throw new IllegalPropertyValueException(this, value); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @SuppressWarnings("unchecked") |
| | | @Override |
| | | public void initialize() throws Exception { |
| | | // Decode the path. |
| | | parentPath = ManagedObjectPath.valueOf(parentPathString); |
| | | |
| | | // Decode the relation definition. |
| | | AbstractManagedObjectDefinition<?, ?> parent = parentPath.getManagedObjectDefinition(); |
| | | RelationDefinition<?, ?> rd = parent.getRelationDefinition(rdName); |
| | | relationDefinition = (InstantiableRelationDefinition<C, S>) rd; |
| | | |
| | | // Now decode the conditions. |
| | | targetNeedsEnablingCondition.initialize(getManagedObjectDefinition()); |
| | | targetIsEnabledCondition.initialize(rd.getChildDefinition()); |
| | | |
| | | // Register a client-side constraint with the referenced |
| | | // definition. This will be used to enforce referential integrity |
| | | // for actions performed against referenced managed objects. |
| | | Constraint constraint = new Constraint() { |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public Collection<ClientConstraintHandler> getClientConstraintHandlers() { |
| | | ClientConstraintHandler handler = new TargetClientHandler(); |
| | | return Collections.singleton(handler); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public Collection<ServerConstraintHandler> getServerConstraintHandlers() { |
| | | return Collections.emptyList(); |
| | | } |
| | | }; |
| | | |
| | | rd.getChildDefinition().registerConstraint(constraint); |
| | | } |
| | | |
| | | } |