/* * 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 <T> * The type of client configuration represented by the client managed * object. */ public abstract class AbstractManagedObject<T extends ConfigurationClient> implements ManagedObject<T> { /** * Creates any default managed objects associated with a relation * definition. */ private final class DefaultManagedObjectFactory implements RelationDefinitionVisitor<Void, Void> { /** Possible exceptions. */ private ManagedObjectAlreadyExistsException moaee; private MissingMandatoryPropertiesException mmpe; private ConcurrentModificationException cme; private OperationRejectedException ore; private LdapException ere; /** {@inheritDoc} */ @Override public <C extends ConfigurationClient, S extends Configuration> Void visitInstantiable( InstantiableRelationDefinition<C, S> rd, Void p) { for (String name : rd.getDefaultManagedObjectNames()) { DefaultManagedObject<? extends C, ? extends S> dmo = rd.getDefaultManagedObject(name); ManagedObjectDefinition<? extends C, ? extends S> d = dmo.getManagedObjectDefinition(); ManagedObject<? extends C> 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 <C extends ConfigurationClient, S extends Configuration> Void visitOptional( OptionalRelationDefinition<C, S> rd, Void p) { if (rd.getDefaultManagedObject() != null) { DefaultManagedObject<? extends C, ? extends S> dmo = rd.getDefaultManagedObject(); ManagedObjectDefinition<? extends C, ? extends S> d = dmo.getManagedObjectDefinition(); ManagedObject<? extends C> child = createChild(rd, d, null); createDefaultManagedObject(d, child, dmo); } return null; } /** {@inheritDoc} */ @Override public <C extends ConfigurationClient, S extends Configuration> Void visitSingleton( SingletonRelationDefinition<C, S> rd, Void p) { // Do nothing - not possible to create singletons // dynamically. return null; } /** {@inheritDoc} */ @Override public <C extends ConfigurationClient, S extends Configuration> Void visitSet(SetRelationDefinition<C, S> rd, Void p) { for (String name : rd.getDefaultManagedObjectNames()) { DefaultManagedObject<? extends C, ? extends S> dmo = rd.getDefaultManagedObject(name); ManagedObjectDefinition<? extends C, ? extends S> d = dmo.getManagedObjectDefinition(); ManagedObject<? extends C> 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 <P> void setPropertyValues(ManagedObject<?> mo, PropertyDefinition<P> pd, DefaultManagedObject<?, ?> dmo) { mo.setPropertyValues(pd, dmo.getPropertyValues(pd)); } } /** * The managed object definition associated with this managed * object. */ private final ManagedObjectDefinition<T, ? extends Configuration> 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<T, ? extends Configuration> 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<T, ? extends Configuration> d, ManagedObjectPath<T, ? extends Configuration> 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<PropertyException> 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<LocalizableMessage> 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 <C extends ConfigurationClient, S extends Configuration, C1 extends C> ManagedObject<C1> createChild( InstantiableRelationDefinition<C, S> r, ManagedObjectDefinition<C1, ? extends S> d, String name, Collection<PropertyException> 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<C1, ? extends S> childPath = path.child(r, d, name); return createNewManagedObject(d, childPath, pd, name, exceptions); } /** {@inheritDoc} */ @Override public final <C extends ConfigurationClient, S extends Configuration, C1 extends C> ManagedObject<C1> createChild( OptionalRelationDefinition<C, S> r, ManagedObjectDefinition<C1, ? extends S> d, Collection<PropertyException> exceptions) { validateRelationDefinition(r); ManagedObjectPath<C1, ? extends S> childPath = path.child(r, d); return createNewManagedObject(d, childPath, null, null, exceptions); } /** {@inheritDoc} */ @Override public final <C extends ConfigurationClient, S extends Configuration, C1 extends C> ManagedObject<C1> createChild( SetRelationDefinition<C, S> r, ManagedObjectDefinition<C1, ? extends S> d, Collection<PropertyException> exceptions) { validateRelationDefinition(r); ManagedObjectPath<C1, ? extends S> childPath = path.child(r, d); return createNewManagedObject(d, childPath, null, null, exceptions); } /** {@inheritDoc} */ @Override public final <C extends ConfigurationClient, S extends Configuration> ManagedObject<? extends C> getChild( InstantiableRelationDefinition<C, S> 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 <C extends ConfigurationClient, S extends Configuration> ManagedObject<? extends C> getChild( OptionalRelationDefinition<C, S> r) throws DefinitionDecodingException, ManagedObjectDecodingException, ManagedObjectNotFoundException, ConcurrentModificationException, LdapException { validateRelationDefinition(r); ensureThisManagedObjectExists(); Driver ctx = getDriver(); return ctx.getManagedObject(path.child(r)); } /** {@inheritDoc} */ @Override public final <C extends ConfigurationClient, S extends Configuration> ManagedObject<? extends C> getChild( SingletonRelationDefinition<C, S> r) throws DefinitionDecodingException, ManagedObjectDecodingException, ManagedObjectNotFoundException, ConcurrentModificationException, LdapException { validateRelationDefinition(r); ensureThisManagedObjectExists(); Driver ctx = getDriver(); return ctx.getManagedObject(path.child(r)); } /** {@inheritDoc} */ @Override public final <C extends ConfigurationClient, S extends Configuration> ManagedObject<? extends C> getChild( SetRelationDefinition<C, S> r, String name) throws DefinitionDecodingException, ManagedObjectDecodingException, ManagedObjectNotFoundException, ConcurrentModificationException, LdapException { validateRelationDefinition(r); ensureThisManagedObjectExists(); Driver ctx = getDriver(); AbstractManagedObjectDefinition<C, S> d = r.getChildDefinition(); AbstractManagedObjectDefinition<? extends C, ? extends S> 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<T, ? extends Configuration> getManagedObjectDefinition() { return definition; } /** {@inheritDoc} */ @Override public final ManagedObjectPath<T, ? extends Configuration> getManagedObjectPath() { return path; } /** {@inheritDoc} */ @Override public final <P> SortedSet<P> getPropertyDefaultValues(PropertyDefinition<P> pd) { return new TreeSet<>(getProperty(pd).getDefaultValues()); } /** {@inheritDoc} */ @Override public final <P> P getPropertyValue(PropertyDefinition<P> pd) { Set<P> values = getProperty(pd).getEffectiveValues(); if (!values.isEmpty()) { return values.iterator().next(); } return null; } /** {@inheritDoc} */ @Override public final <P> SortedSet<P> getPropertyValues(PropertyDefinition<P> pd) { return new TreeSet<>(getProperty(pd).getEffectiveValues()); } /** {@inheritDoc} */ @Override public final <C extends ConfigurationClient, S extends Configuration> boolean hasChild( OptionalRelationDefinition<C, S> 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 <C extends ConfigurationClient, S extends Configuration> String[] listChildren( InstantiableRelationDefinition<C, S> r) throws ConcurrentModificationException, LdapException { return listChildren(r, r.getChildDefinition()); } /** {@inheritDoc} */ @Override public final <C extends ConfigurationClient, S extends Configuration> String[] listChildren( InstantiableRelationDefinition<C, S> r, AbstractManagedObjectDefinition<? extends C, ? extends S> 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 <C extends ConfigurationClient, S extends Configuration> String[] listChildren( SetRelationDefinition<C, S> r) throws ConcurrentModificationException, LdapException { return listChildren(r, r.getChildDefinition()); } /** {@inheritDoc} */ @Override public final <C extends ConfigurationClient, S extends Configuration> String[] listChildren( SetRelationDefinition<C, S> r, AbstractManagedObjectDefinition<? extends C, ? extends S> 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 <C extends ConfigurationClient, S extends Configuration> void removeChild( InstantiableRelationDefinition<C, S> 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 <C extends ConfigurationClient, S extends Configuration> void removeChild( OptionalRelationDefinition<C, S> 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 <C extends ConfigurationClient, S extends Configuration> void removeChild( SetRelationDefinition<C, S> 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 <P> void setPropertyValue(PropertyDefinition<P> pd, P value) { if (value != null) { setPropertyValues(pd, Collections.singleton(value)); } else { setPropertyValues(pd, Collections.<P> emptySet()); } } /** {@inheritDoc} */ @Override public final <P> void setPropertyValues(PropertyDefinition<P> pd, Collection<P> 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 <code>null</code> 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 <P> * 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 <P> Property<P> getProperty(PropertyDefinition<P> 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 <M> * 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 <M extends ConfigurationClient> ManagedObject<M> newInstance(ManagedObjectDefinition<M, ?> d, ManagedObjectPath<M, ?> path, PropertySet properties, boolean existsOnServer, PropertyDefinition<?> namingPropertyDefinition); /** * Creates a new managed object with no active values, just default * values. */ private <M extends ConfigurationClient, P> ManagedObject<M> createNewManagedObject( ManagedObjectDefinition<M, ?> d, ManagedObjectPath<M, ?> p, PropertyDefinition<P> namingPropertyDefinition, String name, Collection<PropertyException> 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 <P> void createProperty(PropertySet properties, ManagedObjectPath<?, ?> p, PropertyDefinition<P> pd) { try { Driver context = getDriver(); Collection<P> defaultValues = context.findDefaultValues(p, pd, true); properties.addProperty(pd, defaultValues, Collections.<P> emptySet()); } catch (PropertyException e) { // Make sure that we have still created the property. properties.addProperty(pd, Collections.<P> emptySet(), Collections.<P> 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<T, ?> 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()); } } }