/* * 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 legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * 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 legal-notices/CDDLv1_0.txt. * 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. * Portions Copyright 2014 ForgeRock AS */ package org.forgerock.opendj.config; import static com.forgerock.opendj.ldap.AdminMessages.*; import static com.forgerock.opendj.util.StaticUtils.*; import org.forgerock.util.Reject; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.server.config.meta.RootCfgDefn; import org.forgerock.opendj.config.client.ClientConstraintHandler; import org.forgerock.opendj.config.client.ManagedObject; import org.forgerock.opendj.config.client.ManagedObjectDecodingException; import org.forgerock.opendj.config.client.ManagementContext; import org.forgerock.opendj.config.conditions.Condition; import org.forgerock.opendj.config.conditions.Conditions; import org.forgerock.opendj.config.server.ConfigChangeResult; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.config.server.ConfigurationDeleteListener; import org.forgerock.opendj.config.server.ServerConstraintHandler; import org.forgerock.opendj.config.server.ServerManagedObject; import org.forgerock.opendj.config.server.ServerManagedObjectChangeListener; import org.forgerock.opendj.config.server.ServerManagementContext; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.ldap.LdapException; import org.forgerock.opendj.ldap.ResultCode; /** * Aggregation property definition. *

* 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 not * performed during value validation. Instead they are performed when changes to * the managed object are committed. *

* An aggregation property definition can optionally identify two properties: *

* In other words, these properties can be used to make sure that referenced * managed objects are not disabled while they are referenced. * * @param * The type of client managed object configuration that this * aggregation property definition refers to. * @param * The type of server managed object configuration that this * aggregation property definition refers to. */ public final class AggregationPropertyDefinition extends PropertyDefinition { /** * An interface for incrementally constructing aggregation property * definitions. * * @param * The type of client managed object configuration that this * aggregation property definition refers to. * @param * The type of server managed object configuration that this * aggregation property definition refers to. */ public static final class Builder extends AbstractBuilder> { /** * 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. *

* 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. *

* 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 buildInstance(AbstractManagedObjectDefinition d, String propertyName, EnumSet options, AdministratorAction adminAction, DefaultBehaviorProvider 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(d, propertyName, options, adminAction, defaultBehavior, parentPathString, rdName, targetNeedsEnablingCondition, targetIsEnabledCondition); } } /** * A change listener which prevents the named component from being disabled. */ private final class ReferentialIntegrityChangeListener implements ServerManagedObjectChangeListener { /** * 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 path; /** Creates a new referential integrity delete listener. */ private ReferentialIntegrityChangeListener(ManagedObjectPath path, LocalizableMessage message) { this.path = path; this.message = message; } /** {@inheritDoc} */ public ConfigChangeResult applyConfigurationChange(ServerManagedObject 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 mo, List 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. debugLogger.trace("Unable to perform post add", e); LocalizableMessage message = ERR_REFINT_UNABLE_TO_EVALUATE_TARGET_CONDITION.get(mo.getManagedObjectDefinition() .getUserFriendlyName(), String.valueOf(mo.getDN()), getExceptionMessage(e)); LocalizedLogger logger = LocalizedLogger.getLocalizedLogger(ERR_REFINT_UNABLE_TO_EVALUATE_TARGET_CONDITION.resourceName()); logger.error(message); unacceptableReasons.add(message); return false; } } /** Gets the path associated with this listener. */ private ManagedObjectPath getManagedObjectPath() { return path; } } /** * A delete listener which prevents the named component from being deleted. */ private final class ReferentialIntegrityDeleteListener implements ConfigurationDeleteListener { /** 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 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 unacceptableReasons) throws ConfigException { SortedSet names = managedObject.getPropertyValues(AggregationPropertyDefinition.this); ServerManagementContext context = managedObject.getServerContext(); 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 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 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 = managedObject.getServerContext(); ManagedObjectPath parentPath = getParentPath(); ServerManagedObject parent = context.getManagedObject(parentPath); // Create entries in the listener tables. List dlist = new LinkedList(); deleteListeners.put(managedObject.getDN(), dlist); List clist = new LinkedList(); changeListeners.put(managedObject.getDN(), clist); for (String name : managedObject.getPropertyValues(AggregationPropertyDefinition.this)) { ManagedObjectPath 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 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 = managedObject.getServerContext(); 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 path = cl.getManagedObjectPath(); ServerManagedObject 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 unacceptableReasons) throws LdapException { // 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.getMessageObject()); unacceptableReasons.add(msg); isAcceptable = false; continue; } catch (ManagedObjectDecodingException e) { LocalizableMessage msg = ERR_CLIENT_REFINT_TARGET_INVALID.get(ufn, name, getName(), e.getMessageObject()); 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 unacceptableReasons) throws LdapException { // 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 unacceptableReasons) throws LdapException { // 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 unacceptableReasons) throws LdapException { // 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 List> findReferences( ManagementContext context, AbstractManagedObjectDefinition mod, String name) throws LdapException { List> instances = findInstances(context, mod); Iterator> i = instances.iterator(); while (i.hasNext()) { ManagedObject 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 List> findInstances( ManagementContext context, AbstractManagedObjectDefinition mod) throws LdapException { List> instances = new LinkedList>(); if (mod == RootCfgDefn.getInstance()) { instances.add((ManagedObject) context.getRootConfigurationManagedObject()); } else { for (RelationDefinition rd : mod.getAllReverseRelationDefinitions()) { for (ManagedObject parent : findInstances(context, rd.getParentDefinition())) { try { if (rd instanceof SingletonRelationDefinition) { SingletonRelationDefinition srd = (SingletonRelationDefinition) rd; ManagedObject mo = parent.getChild(srd); if (mo.getManagedObjectDefinition().isChildOf(mod)) { instances.add((ManagedObject) mo); } } else if (rd instanceof OptionalRelationDefinition) { OptionalRelationDefinition ord = (OptionalRelationDefinition) rd; ManagedObject mo = parent.getChild(ord); if (mo.getManagedObjectDefinition().isChildOf(mod)) { instances.add((ManagedObject) mo); } } else if (rd instanceof InstantiableRelationDefinition) { InstantiableRelationDefinition ird = (InstantiableRelationDefinition) rd; for (String name : parent.listChildren(ird)) { ManagedObject mo = parent.getChild(ird, name); if (mo.getManagedObjectDefinition().isChildOf(mod)) { instances.add((ManagedObject) mo); } } } } catch (OperationsException e) { // Ignore all operations exceptions. } } } } return instances; } } /** * Creates an aggregation property definition builder. * * @param * The type of client managed object configuration that this * aggregation property definition refers to. * @param * 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 Builder createBuilder( AbstractManagedObjectDefinition d, String propertyName) { return new Builder(d, propertyName); } private static final Logger debugLogger = LoggerFactory.getLogger(AggregationPropertyDefinition.class); /** * The active server-side referential integrity change listeners * associated with this property. */ private final Map> changeListeners = new HashMap>(); /** * The active server-side referential integrity delete listeners * associated with this property. */ private final Map> deleteListeners = new HashMap>(); /** * 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 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 options, AdministratorAction adminAction, DefaultBehaviorProvider 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 getClientConstraintHandlers() { ClientConstraintHandler handler = new SourceClientHandler(); return Collections.singleton(handler); } /** {@inheritDoc} */ public Collection getServerConstraintHandlers() { ServerConstraintHandler handler = new ServerHandler(); return Collections.singleton(handler); } }; } /** {@inheritDoc} */ @Override public R accept(PropertyDefinitionVisitor v, P p) { return v.visitAggregation(this, p); } /** {@inheritDoc} */ @Override public R accept(PropertyValueVisitor v, String value, P p) { return v.visitAggregation(this, value, p); } /** {@inheritDoc} */ @Override public String decodeValue(String value) { Reject.ifNull(value); try { validateValue(value); return value; } catch (PropertyException e) { throw PropertyException.illegalPropertyValueException(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 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 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, null is returned. * * @return Returns the optional constraint synopsis of this aggregation * property definition in the default locale, or null * if there is no constraint synopsis. */ public final LocalizableMessage 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, null is returned. * * @param locale * The locale. * @return Returns the optional constraint synopsis of this aggregation * property definition in the specified locale, or null * 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.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) { try { Reference reference = Reference.parseName(parentPath, relationDefinition, value); return reference.getNormalizedName(); } catch (IllegalArgumentException e) { throw PropertyException.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(targetNeedsEnablingCondition); builder.append(" targetIsEnabledCondition="); builder.append(targetIsEnabledCondition); } /** {@inheritDoc} */ @Override public void validateValue(String value) { try { Reference.parseName(parentPath, relationDefinition, value); } catch (IllegalArgumentException e) { throw PropertyException.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) 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 getClientConstraintHandlers() { ClientConstraintHandler handler = new TargetClientHandler(); return Collections.singleton(handler); } /** {@inheritDoc} */ public Collection getServerConstraintHandlers() { return Collections.emptyList(); } }; rd.getChildDefinition().registerConstraint(constraint); } }