/* * 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 2008-2009 Sun Microsystems, Inc. * Portions Copyright 2014-2015 ForgeRock AS */ package org.forgerock.opendj.config.client.spi; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.opendj.config.AbstractManagedObjectDefinition; import org.forgerock.opendj.config.Configuration; import org.forgerock.opendj.config.ConfigurationClient; import org.forgerock.opendj.config.Constraint; import org.forgerock.opendj.config.PropertyException; import org.forgerock.opendj.config.DefaultManagedObject; import org.forgerock.opendj.config.DefinitionDecodingException; import org.forgerock.opendj.config.InstantiableRelationDefinition; import org.forgerock.opendj.config.ManagedObjectAlreadyExistsException; import org.forgerock.opendj.config.ManagedObjectDefinition; import org.forgerock.opendj.config.ManagedObjectNotFoundException; import org.forgerock.opendj.config.ManagedObjectPath; import org.forgerock.opendj.config.OptionalRelationDefinition; import org.forgerock.opendj.config.PropertyDefinition; import org.forgerock.opendj.config.PropertyOption; import org.forgerock.opendj.config.RelationDefinition; import org.forgerock.opendj.config.RelationDefinitionVisitor; import org.forgerock.opendj.config.SetRelationDefinition; import org.forgerock.opendj.config.SingletonRelationDefinition; import org.forgerock.opendj.config.DefinitionDecodingException.Reason; import org.forgerock.opendj.config.client.ClientConstraintHandler; import org.forgerock.opendj.config.client.ConcurrentModificationException; import org.forgerock.opendj.config.client.IllegalManagedObjectNameException; 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.client.MissingMandatoryPropertiesException; import org.forgerock.opendj.config.client.OperationRejectedException; import org.forgerock.opendj.config.client.OperationRejectedException.OperationType; import org.forgerock.opendj.ldap.LdapException; /** * An abstract managed object implementation. * * @param * The type of client configuration represented by the client managed * object. */ public abstract class AbstractManagedObject implements ManagedObject { /** * Creates any default managed objects associated with a relation * definition. */ private final class DefaultManagedObjectFactory implements RelationDefinitionVisitor { /** Possible exceptions. */ private ManagedObjectAlreadyExistsException moaee; private MissingMandatoryPropertiesException mmpe; private ConcurrentModificationException cme; private OperationRejectedException ore; private LdapException ere; /** {@inheritDoc} */ @Override public Void visitInstantiable( InstantiableRelationDefinition rd, Void p) { for (String name : rd.getDefaultManagedObjectNames()) { DefaultManagedObject dmo = rd.getDefaultManagedObject(name); ManagedObjectDefinition d = dmo.getManagedObjectDefinition(); ManagedObject child; try { child = createChild(rd, d, name, null); } catch (IllegalManagedObjectNameException e) { // This should not happen. throw new RuntimeException(e); } createDefaultManagedObject(d, child, dmo); } return null; } /** {@inheritDoc} */ @Override public Void visitOptional( OptionalRelationDefinition rd, Void p) { if (rd.getDefaultManagedObject() != null) { DefaultManagedObject dmo = rd.getDefaultManagedObject(); ManagedObjectDefinition d = dmo.getManagedObjectDefinition(); ManagedObject child = createChild(rd, d, null); createDefaultManagedObject(d, child, dmo); } return null; } /** {@inheritDoc} */ @Override public Void visitSingleton( SingletonRelationDefinition rd, Void p) { // Do nothing - not possible to create singletons // dynamically. return null; } /** {@inheritDoc} */ @Override public Void visitSet(SetRelationDefinition rd, Void p) { for (String name : rd.getDefaultManagedObjectNames()) { DefaultManagedObject dmo = rd.getDefaultManagedObject(name); ManagedObjectDefinition d = dmo.getManagedObjectDefinition(); ManagedObject child = createChild(rd, d, null); createDefaultManagedObject(d, child, dmo); } return null; } /** Create the child managed object. */ private void createDefaultManagedObject(ManagedObjectDefinition d, ManagedObject child, DefaultManagedObject dmo) { for (PropertyDefinition pd : d.getAllPropertyDefinitions()) { setPropertyValues(child, pd, dmo); } try { child.commit(); } catch (ManagedObjectAlreadyExistsException e) { moaee = e; } catch (MissingMandatoryPropertiesException e) { mmpe = e; } catch (ConcurrentModificationException e) { cme = e; } catch (OperationRejectedException e) { ore = e; } catch (LdapException e) { ere = e; } } /** * Creates the default managed objects associated with the provided * relation definition. * * @param rd * The relation definition. */ private void createDefaultManagedObjects(RelationDefinition rd) throws LdapException, ConcurrentModificationException, MissingMandatoryPropertiesException, ManagedObjectAlreadyExistsException, OperationRejectedException { rd.accept(this, null); if (ere != null) { throw ere; } else if (cme != null) { throw cme; } else if (mmpe != null) { throw mmpe; } else if (moaee != null) { throw moaee; } else if (ore != null) { throw ore; } } /** Set property values. */ private

void setPropertyValues(ManagedObject mo, PropertyDefinition

pd, DefaultManagedObject dmo) { mo.setPropertyValues(pd, dmo.getPropertyValues(pd)); } } /** * The managed object definition associated with this managed * object. */ private final ManagedObjectDefinition definition; /** * Indicates whether or not this managed object exists on the server * (false means the managed object is new and has not been * committed). */ private boolean existsOnServer; /** Optional naming property definition. */ private final PropertyDefinition namingPropertyDefinition; /** The path associated with this managed object. */ private ManagedObjectPath path; /** The managed object's properties. */ private final PropertySet properties; /** * Creates a new abstract managed object. * * @param d * The managed object's definition. * @param path * The managed object's path. * @param properties * The managed object's properties. * @param existsOnServer * Indicates whether or not the managed object exists on the * server (false means the managed object is new and has not been * committed). * @param namingPropertyDefinition * Optional naming property definition. */ protected AbstractManagedObject(ManagedObjectDefinition d, ManagedObjectPath path, PropertySet properties, boolean existsOnServer, PropertyDefinition namingPropertyDefinition) { this.definition = d; this.path = path; this.properties = properties; this.existsOnServer = existsOnServer; this.namingPropertyDefinition = namingPropertyDefinition; } /** {@inheritDoc} */ @Override public final void commit() throws ManagedObjectAlreadyExistsException, MissingMandatoryPropertiesException, ConcurrentModificationException, OperationRejectedException, LdapException { // First make sure all mandatory properties are defined. List exceptions = new LinkedList<>(); for (PropertyDefinition pd : definition.getAllPropertyDefinitions()) { Property p = getProperty(pd); if (pd.hasOption(PropertyOption.MANDATORY) && p.getEffectiveValues().isEmpty()) { exceptions.add(PropertyException.propertyIsMandatoryException(pd)); } } if (!exceptions.isEmpty()) { throw new MissingMandatoryPropertiesException(definition.getUserFriendlyName(), exceptions, !existsOnServer); } // Now enforce any constraints. List messages = new LinkedList<>(); boolean isAcceptable = true; ManagementContext context = getDriver().getManagementContext(); for (Constraint constraint : definition.getAllConstraints()) { for (ClientConstraintHandler handler : constraint.getClientConstraintHandlers()) { if (existsOnServer) { if (!handler.isModifyAcceptable(context, this, messages)) { isAcceptable = false; } } else { if (!handler.isAddAcceptable(context, this, messages)) { isAcceptable = false; } } } if (!isAcceptable) { break; } } if (!isAcceptable) { if (existsOnServer) { throw new OperationRejectedException(OperationType.MODIFY, definition.getUserFriendlyName(), messages); } else { throw new OperationRejectedException(OperationType.CREATE, definition.getUserFriendlyName(), messages); } } // Commit the managed object. if (existsOnServer) { modifyExistingManagedObject(); } else { addNewManagedObject(); } // Make all pending property values active. properties.commit(); // If the managed object was created make sure that any default // subordinate managed objects are also created. if (!existsOnServer) { DefaultManagedObjectFactory factory = new DefaultManagedObjectFactory(); for (RelationDefinition rd : definition.getAllRelationDefinitions()) { factory.createDefaultManagedObjects(rd); } existsOnServer = true; } } /** {@inheritDoc} */ @Override public final ManagedObject createChild( InstantiableRelationDefinition r, ManagedObjectDefinition d, String name, Collection exceptions) throws IllegalManagedObjectNameException { validateRelationDefinition(r); // Empty names are not allowed. if (name.trim().length() == 0) { throw new IllegalManagedObjectNameException(name); } // If the relation uses a naming property definition then it must // be a valid value. PropertyDefinition pd = r.getNamingPropertyDefinition(); if (pd != null) { try { pd.decodeValue(name); } catch (PropertyException e) { throw new IllegalManagedObjectNameException(name, pd); } } ManagedObjectPath childPath = path.child(r, d, name); return createNewManagedObject(d, childPath, pd, name, exceptions); } /** {@inheritDoc} */ @Override public final ManagedObject createChild( OptionalRelationDefinition r, ManagedObjectDefinition d, Collection exceptions) { validateRelationDefinition(r); ManagedObjectPath childPath = path.child(r, d); return createNewManagedObject(d, childPath, null, null, exceptions); } /** {@inheritDoc} */ @Override public final ManagedObject createChild( SetRelationDefinition r, ManagedObjectDefinition d, Collection exceptions) { validateRelationDefinition(r); ManagedObjectPath childPath = path.child(r, d); return createNewManagedObject(d, childPath, null, null, exceptions); } /** {@inheritDoc} */ @Override public final ManagedObject getChild( InstantiableRelationDefinition r, String name) throws DefinitionDecodingException, ManagedObjectDecodingException, ManagedObjectNotFoundException, ConcurrentModificationException, LdapException { validateRelationDefinition(r); ensureThisManagedObjectExists(); Driver ctx = getDriver(); return ctx.getManagedObject(path.child(r, name)); } /** {@inheritDoc} */ @Override public final ManagedObject getChild( OptionalRelationDefinition r) throws DefinitionDecodingException, ManagedObjectDecodingException, ManagedObjectNotFoundException, ConcurrentModificationException, LdapException { validateRelationDefinition(r); ensureThisManagedObjectExists(); Driver ctx = getDriver(); return ctx.getManagedObject(path.child(r)); } /** {@inheritDoc} */ @Override public final ManagedObject getChild( SingletonRelationDefinition r) throws DefinitionDecodingException, ManagedObjectDecodingException, ManagedObjectNotFoundException, ConcurrentModificationException, LdapException { validateRelationDefinition(r); ensureThisManagedObjectExists(); Driver ctx = getDriver(); return ctx.getManagedObject(path.child(r)); } /** {@inheritDoc} */ @Override public final ManagedObject getChild( SetRelationDefinition r, String name) throws DefinitionDecodingException, ManagedObjectDecodingException, ManagedObjectNotFoundException, ConcurrentModificationException, LdapException { validateRelationDefinition(r); ensureThisManagedObjectExists(); Driver ctx = getDriver(); AbstractManagedObjectDefinition d = r.getChildDefinition(); AbstractManagedObjectDefinition cd; try { cd = d.getChild(name); } catch (IllegalArgumentException e) { // Unrecognized definition name - report this as a decoding // exception. throw new DefinitionDecodingException(d, Reason.WRONG_TYPE_INFORMATION); } return ctx.getManagedObject(path.child(r, cd)); } /** {@inheritDoc} */ @Override public final T getConfiguration() { return definition.createClientConfiguration(this); } /** {@inheritDoc} */ @Override public final ManagedObjectDefinition getManagedObjectDefinition() { return definition; } /** {@inheritDoc} */ @Override public final ManagedObjectPath getManagedObjectPath() { return path; } /** {@inheritDoc} */ @Override public final

SortedSet

getPropertyDefaultValues(PropertyDefinition

pd) { return new TreeSet<>(getProperty(pd).getDefaultValues()); } /** {@inheritDoc} */ @Override public final

P getPropertyValue(PropertyDefinition

pd) { Set

values = getProperty(pd).getEffectiveValues(); if (!values.isEmpty()) { return values.iterator().next(); } return null; } /** {@inheritDoc} */ @Override public final

SortedSet

getPropertyValues(PropertyDefinition

pd) { return new TreeSet<>(getProperty(pd).getEffectiveValues()); } /** {@inheritDoc} */ @Override public final boolean hasChild( OptionalRelationDefinition r) throws ConcurrentModificationException, LdapException { validateRelationDefinition(r); Driver ctx = getDriver(); try { return ctx.managedObjectExists(path.child(r)); } catch (ManagedObjectNotFoundException e) { throw new ConcurrentModificationException(); } } /** {@inheritDoc} */ @Override public final boolean isPropertyPresent(PropertyDefinition pd) { return !getProperty(pd).isEmpty(); } /** {@inheritDoc} */ @Override public final String[] listChildren( InstantiableRelationDefinition r) throws ConcurrentModificationException, LdapException { return listChildren(r, r.getChildDefinition()); } /** {@inheritDoc} */ @Override public final String[] listChildren( InstantiableRelationDefinition r, AbstractManagedObjectDefinition d) throws ConcurrentModificationException, LdapException { validateRelationDefinition(r); Driver ctx = getDriver(); try { return ctx.listManagedObjects(path, r, d); } catch (ManagedObjectNotFoundException e) { throw new ConcurrentModificationException(); } } /** {@inheritDoc} */ @Override public final String[] listChildren( SetRelationDefinition r) throws ConcurrentModificationException, LdapException { return listChildren(r, r.getChildDefinition()); } /** {@inheritDoc} */ @Override public final String[] listChildren( SetRelationDefinition r, AbstractManagedObjectDefinition d) throws ConcurrentModificationException, LdapException { validateRelationDefinition(r); Driver ctx = getDriver(); try { return ctx.listManagedObjects(path, r, d); } catch (ManagedObjectNotFoundException e) { throw new ConcurrentModificationException(); } } /** {@inheritDoc} */ @Override public final void removeChild( InstantiableRelationDefinition r, String name) throws ManagedObjectNotFoundException, OperationRejectedException, ConcurrentModificationException, LdapException { validateRelationDefinition(r); Driver ctx = getDriver(); boolean found; try { found = ctx.deleteManagedObject(path, r, name); } catch (ManagedObjectNotFoundException e) { throw new ConcurrentModificationException(); } if (!found) { throw new ManagedObjectNotFoundException(); } } /** {@inheritDoc} */ @Override public final void removeChild( OptionalRelationDefinition r) throws ManagedObjectNotFoundException, OperationRejectedException, ConcurrentModificationException, LdapException { validateRelationDefinition(r); Driver ctx = getDriver(); boolean found; try { found = ctx.deleteManagedObject(path, r); } catch (ManagedObjectNotFoundException e) { throw new ConcurrentModificationException(); } if (!found) { throw new ManagedObjectNotFoundException(); } } /** {@inheritDoc} */ @Override public final void removeChild( SetRelationDefinition r, String name) throws ManagedObjectNotFoundException, OperationRejectedException, ConcurrentModificationException, LdapException { validateRelationDefinition(r); Driver ctx = getDriver(); boolean found; try { found = ctx.deleteManagedObject(path, r, name); } catch (ManagedObjectNotFoundException e) { throw new ConcurrentModificationException(); } if (!found) { throw new ManagedObjectNotFoundException(); } } /** {@inheritDoc} */ @Override public final

void setPropertyValue(PropertyDefinition

pd, P value) { if (value != null) { setPropertyValues(pd, Collections.singleton(value)); } else { setPropertyValues(pd, Collections.

emptySet()); } } /** {@inheritDoc} */ @Override public final

void setPropertyValues(PropertyDefinition

pd, Collection

values) { if (pd.hasOption(PropertyOption.MONITORING)) { throw PropertyException.propertyIsReadOnlyException(pd); } if (existsOnServer && pd.hasOption(PropertyOption.READ_ONLY)) { throw PropertyException.propertyIsReadOnlyException(pd); } properties.setPropertyValues(pd, values); // If this is a naming property then update the name. if (pd.equals(namingPropertyDefinition)) { // The property must be single-valued and mandatory. String newName = pd.encodeValue(values.iterator().next()); path = path.rename(newName); } } /** {@inheritDoc} */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("{ TYPE="); builder.append(definition.getName()); builder.append(", PATH=\""); builder.append(path); builder.append('\"'); for (PropertyDefinition pd : definition.getAllPropertyDefinitions()) { builder.append(", "); builder.append(pd.getName()); builder.append('='); builder.append(getPropertyValues(pd)); } builder.append(" }"); return builder.toString(); } /** * Adds this new managed object. * * @throws ManagedObjectAlreadyExistsException * If the managed object cannot be added to the server because * it already exists. * @throws ConcurrentModificationException * If the managed object's parent has been removed by another * client. * @throws OperationRejectedException * If the managed object cannot be added due to some client-side * or server-side constraint which cannot be satisfied. * @throws LdapException * If any other error occurs. */ protected abstract void addNewManagedObject() throws LdapException, OperationRejectedException, ConcurrentModificationException, ManagedObjectAlreadyExistsException; /** * Gets the management context driver associated with this managed object. * * @return Returns the management context driver associated with this * managed object. */ protected abstract Driver getDriver(); /** * Gets the naming property definition associated with this managed object. * * @return Returns the naming property definition associated with this * managed object, or null if this managed object does * not have a naming property. */ protected final PropertyDefinition getNamingPropertyDefinition() { return namingPropertyDefinition; } /** * Gets the property associated with the specified property definition. * * @param

* The underlying type of the property. * @param pd * The Property definition. * @return Returns the property associated with the specified property * definition. * @throws IllegalArgumentException * If this property provider does not recognize the requested * property definition. */ protected final

Property

getProperty(PropertyDefinition

pd) { return properties.getProperty(pd); } /** * Applies changes made to this managed object. * * @throws ConcurrentModificationException * If this managed object has been removed from the server by * another client. * @throws OperationRejectedException * If the managed object cannot be added due to some client-side * or server-side constraint which cannot be satisfied. * @throws LdapException * If any other error occurs. */ protected abstract void modifyExistingManagedObject() throws ConcurrentModificationException, OperationRejectedException, LdapException; /** * Creates a new managed object. * * @param * The type of client configuration represented by the client * managed object. * @param d * The managed object's definition. * @param path * The managed object's path. * @param properties * The managed object's properties. * @param existsOnServer * Indicates whether or not the managed object exists on the * server (false means the managed object is new and has not been * committed). * @param namingPropertyDefinition * Optional naming property definition. * @return Returns the new managed object. */ protected abstract ManagedObject newInstance(ManagedObjectDefinition d, ManagedObjectPath path, PropertySet properties, boolean existsOnServer, PropertyDefinition namingPropertyDefinition); /** * Creates a new managed object with no active values, just default * values. */ private ManagedObject createNewManagedObject( ManagedObjectDefinition d, ManagedObjectPath p, PropertyDefinition

namingPropertyDefinition, String name, Collection exceptions) { PropertySet childProperties = new PropertySet(); for (PropertyDefinition pd : d.getAllPropertyDefinitions()) { try { createProperty(childProperties, p, pd); } catch (PropertyException e) { // Add the exception if requested. if (exceptions != null) { exceptions.add(e); } } } // Set the naming property if there is one. if (namingPropertyDefinition != null) { P value = namingPropertyDefinition.decodeValue(name); childProperties.setPropertyValues(namingPropertyDefinition, Collections.singleton(value)); } return newInstance(d, p, childProperties, false, namingPropertyDefinition); } /** Create an empty property. */ private

void createProperty(PropertySet properties, ManagedObjectPath p, PropertyDefinition

pd) { try { Driver context = getDriver(); Collection

defaultValues = context.findDefaultValues(p, pd, true); properties.addProperty(pd, defaultValues, Collections.

emptySet()); } catch (PropertyException e) { // Make sure that we have still created the property. properties.addProperty(pd, Collections.

emptySet(), Collections.

emptySet()); throw e; } } /** Makes sure that this managed object exists. */ private void ensureThisManagedObjectExists() throws ConcurrentModificationException, LdapException { if (!path.isEmpty()) { Driver ctx = getDriver(); try { if (!ctx.managedObjectExists(path)) { throw new ConcurrentModificationException(); } } catch (ManagedObjectNotFoundException e) { throw new ConcurrentModificationException(); } } } /** * Validate that a relation definition belongs to this managed * object. */ private void validateRelationDefinition(RelationDefinition rd) { ManagedObjectDefinition d = getManagedObjectDefinition(); RelationDefinition tmp = d.getRelationDefinition(rd.getName()); if (tmp != rd) { throw new IllegalArgumentException("The relation " + rd.getName() + " is not associated with a " + d.getName()); } } }