/* * 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.opends.server.admin; import static org.opends.messages.AdminMessages.*; import static 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.forgerock.i18n.LocalizableMessage; 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.forgerock.i18n.slf4j.LocalizedLogger; import org.opends.server.types.ConfigChangeResult; import org.opends.server.types.DN; import org.opends.server.types.ResultCode; import org.opends.server.util.StaticUtils; /** * 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 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 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. logger.traceException(e); LocalizableMessage 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 getManagedObjectPath() { return path; } } /** * A delete listener which prevents the named component from being * deleted. */ private 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 = 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 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 = ServerManagementContext.getInstance(); 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 = 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 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 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.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 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 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 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 List> findReferences( ManagementContext context, AbstractManagedObjectDefinition mod, String name) throws AuthorizationException, CommunicationException { 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 AuthorizationException, CommunicationException { 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; } } private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** * 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); } // 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) throws PropertyException { 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) throws PropertyException { 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(String.valueOf(targetNeedsEnablingCondition)); builder.append(" targetIsEnabledCondition="); builder.append(String.valueOf(targetIsEnabledCondition)); } /** * {@inheritDoc} */ @Override public void validateValue(String value) throws PropertyException { 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); } }