/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2007-2008 Sun Microsystems, Inc. */ 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.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; 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.client.ManagedObject; import org.opends.server.admin.client.ManagedObjectDecodingException; import org.opends.server.admin.client.ManagementContext; import org.opends.server.admin.condition.Condition; import org.opends.server.admin.condition.Conditions; import org.opends.server.admin.server.ConfigurationDeleteListener; import org.opends.server.admin.server.ServerConstraintHandler; 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.opends.server.types.DN; import org.opends.server.types.DebugLogLevel; import org.opends.server.types.ResultCode; import org.opends.server.util.StaticUtils; /** * 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. * <p> * 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. * </ul> * 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. * @param <S> * 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> { /** * 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 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. } } } } 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 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; } } /** * 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); } } /** * {@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); } } /** * {@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); } }