001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2015 ForgeRock AS.
016 */
017package org.forgerock.opendj.config.client.spi;
018
019import static org.forgerock.opendj.config.PropertyException.*;
020
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.SortedSet;
027
028import org.forgerock.i18n.LocalizableMessage;
029import org.forgerock.opendj.config.AbsoluteInheritedDefaultBehaviorProvider;
030import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
031import org.forgerock.opendj.config.AliasDefaultBehaviorProvider;
032import org.forgerock.opendj.config.Configuration;
033import org.forgerock.opendj.config.ConfigurationClient;
034import org.forgerock.opendj.config.Constraint;
035import org.forgerock.opendj.config.DefaultBehaviorProviderVisitor;
036import org.forgerock.opendj.config.DefinedDefaultBehaviorProvider;
037import org.forgerock.opendj.config.DefinitionDecodingException;
038import org.forgerock.opendj.config.DefinitionDecodingException.Reason;
039import org.forgerock.opendj.config.InstantiableRelationDefinition;
040import org.forgerock.opendj.config.ManagedObjectNotFoundException;
041import org.forgerock.opendj.config.ManagedObjectPath;
042import org.forgerock.opendj.config.OptionalRelationDefinition;
043import org.forgerock.opendj.config.PropertyDefinition;
044import org.forgerock.opendj.config.PropertyException;
045import org.forgerock.opendj.config.PropertyNotFoundException;
046import org.forgerock.opendj.config.PropertyOption;
047import org.forgerock.opendj.config.RelationDefinition;
048import org.forgerock.opendj.config.RelativeInheritedDefaultBehaviorProvider;
049import org.forgerock.opendj.config.SetRelationDefinition;
050import org.forgerock.opendj.config.UndefinedDefaultBehaviorProvider;
051import org.forgerock.opendj.config.client.ClientConstraintHandler;
052import org.forgerock.opendj.config.client.ManagedObject;
053import org.forgerock.opendj.config.client.ManagedObjectDecodingException;
054import org.forgerock.opendj.config.client.ManagementContext;
055import org.forgerock.opendj.config.client.OperationRejectedException;
056import org.forgerock.opendj.config.client.OperationRejectedException.OperationType;
057import org.forgerock.opendj.ldap.LdapException;
058import org.forgerock.opendj.server.config.client.RootCfgClient;
059
060/**
061 * An abstract management connection context driver which should form the basis
062 * of driver implementations.
063 */
064public abstract class Driver {
065
066    /**
067     * A default behavior visitor used for retrieving the default values of a
068     * property.
069     *
070     * @param <T>
071     *            The type of the property.
072     */
073    private final class DefaultValueFinder<T> implements DefaultBehaviorProviderVisitor<T, Collection<T>, Void> {
074
075        /**
076         * Any exception that occurred whilst retrieving inherited default
077         * values.
078         */
079        private PropertyException exception;
080
081        /** The path of the managed object containing the first property. */
082        private final ManagedObjectPath<?, ?> firstPath;
083
084        /** Indicates whether the managed object has been created yet. */
085        private final boolean isCreate;
086
087        /** The path of the managed object containing the next property. */
088        private ManagedObjectPath<?, ?> nextPath;
089
090        /** The next property whose default values were required. */
091        private PropertyDefinition<T> nextProperty;
092
093        /** Private constructor. */
094        private DefaultValueFinder(ManagedObjectPath<?, ?> p, boolean isCreate) {
095            this.firstPath = p;
096            this.isCreate = isCreate;
097        }
098
099        /** {@inheritDoc} */
100        @Override
101        public Collection<T> visitAbsoluteInherited(AbsoluteInheritedDefaultBehaviorProvider<T> d, Void p) {
102            try {
103                return getInheritedProperty(d.getManagedObjectPath(), d.getManagedObjectDefinition(),
104                    d.getPropertyName());
105            } catch (PropertyException e) {
106                exception = e;
107                return Collections.emptySet();
108            }
109        }
110
111        /** {@inheritDoc} */
112        @Override
113        public Collection<T> visitAlias(AliasDefaultBehaviorProvider<T> d, Void p) {
114            return Collections.emptySet();
115        }
116
117        /** {@inheritDoc} */
118        @Override
119        public Collection<T> visitDefined(DefinedDefaultBehaviorProvider<T> d, Void p) {
120            Collection<String> stringValues = d.getDefaultValues();
121            List<T> values = new ArrayList<>(stringValues.size());
122
123            for (String stringValue : stringValues) {
124                try {
125                    values.add(nextProperty.decodeValue(stringValue));
126                } catch (PropertyException e) {
127                    exception = PropertyException.defaultBehaviorException(nextProperty, e);
128                    break;
129                }
130            }
131
132            return values;
133        }
134
135        /** {@inheritDoc} */
136        @Override
137        public Collection<T> visitRelativeInherited(RelativeInheritedDefaultBehaviorProvider<T> d, Void p) {
138            try {
139                return getInheritedProperty(d.getManagedObjectPath(nextPath), d.getManagedObjectDefinition(),
140                    d.getPropertyName());
141            } catch (PropertyException e) {
142                exception = e;
143                return Collections.emptySet();
144            }
145        }
146
147        /** {@inheritDoc} */
148        @Override
149        public Collection<T> visitUndefined(UndefinedDefaultBehaviorProvider<T> d, Void p) {
150            return Collections.emptySet();
151        }
152
153        /** Find the default values for the next path/property. */
154        private Collection<T> find(ManagedObjectPath<?, ?> p, PropertyDefinition<T> pd) {
155            this.nextPath = p;
156            this.nextProperty = pd;
157
158            Collection<T> values = nextProperty.getDefaultBehaviorProvider().accept(this, null);
159
160            if (exception != null) {
161                throw exception;
162            }
163
164            if (values.size() > 1 && !pd.hasOption(PropertyOption.MULTI_VALUED)) {
165                throw defaultBehaviorException(pd, propertyIsSingleValuedException(pd));
166            }
167
168            return values;
169        }
170
171        /** Get an inherited property value. */
172        @SuppressWarnings("unchecked")
173        private Collection<T> getInheritedProperty(ManagedObjectPath<?, ?> target,
174                AbstractManagedObjectDefinition<?, ?> definition, String propertyName) {
175            // First check that the requested type of managed object
176            // corresponds to the path.
177            AbstractManagedObjectDefinition<?, ?> actual = target.getManagedObjectDefinition();
178            if (!definition.isParentOf(actual)) {
179                throw PropertyException.defaultBehaviorException(nextProperty,
180                        new DefinitionDecodingException(actual, Reason.WRONG_TYPE_INFORMATION));
181            }
182
183            // Save the current property in case of recursion.
184            PropertyDefinition<T> pd1 = nextProperty;
185
186            try {
187                // Determine the requested property definition.
188                PropertyDefinition<T> pd2;
189                try {
190                    // FIXME: we use the definition taken from the default
191                    // behavior here when we should really use the exact
192                    // definition of the component being created.
193                    PropertyDefinition<?> pdTmp = definition.getPropertyDefinition(propertyName);
194                    pd2 = pd1.getClass().cast(pdTmp);
195                } catch (IllegalArgumentException | ClassCastException e) {
196                    throw new PropertyNotFoundException(propertyName);
197                }
198
199                // If the path relates to the current managed object and the
200                // managed object is in the process of being created it won't
201                // exist, so we should just use the default values of the
202                // referenced property.
203                if (isCreate && firstPath.equals(target)) {
204                    // Recursively retrieve this property's default values.
205                    Collection<T> tmp = find(target, pd2);
206                    Collection<T> values = new ArrayList<>(tmp.size());
207                    for (T value : tmp) {
208                        pd1.validateValue(value);
209                        values.add(value);
210                    }
211                    return values;
212                } else {
213                    // FIXME: issue 2481 - this is broken if the referenced property
214                    // inherits its defaults from the newly created managed object.
215                    return getPropertyValues(target, pd2);
216                }
217            } catch (PropertyException | DefinitionDecodingException | PropertyNotFoundException
218                    | LdapException | ManagedObjectNotFoundException e) {
219                throw PropertyException.defaultBehaviorException(pd1, e);
220            }
221        }
222    }
223
224    /** Creates a new abstract driver. */
225    protected Driver() {
226       // Do nothing.
227    }
228
229    /** Closes any context associated with this management context driver. */
230    public void close() {
231        // do nothing by default
232    }
233
234    /**
235     * Deletes the named instantiable child managed object from the named parent
236     * managed object.
237     *
238     * @param <C>
239     *            The type of client managed object configuration that the
240     *            relation definition refers to.
241     * @param <S>
242     *            The type of server managed object configuration that the
243     *            relation definition refers to.
244     * @param parent
245     *            The path of the parent managed object.
246     * @param rd
247     *            The instantiable relation definition.
248     * @param name
249     *            The name of the child managed object to be removed.
250     * @return Returns <code>true</code> if the named instantiable child managed
251     *         object was found, or <code>false</code> if it was not found.
252     * @throws IllegalArgumentException
253     *             If the relation definition is not associated with the parent
254     *             managed object's definition.
255     * @throws ManagedObjectNotFoundException
256     *             If the parent managed object could not be found.
257     * @throws OperationRejectedException
258     *             If the managed object cannot be removed due to some
259     *             client-side or server-side constraint which cannot be
260     *             satisfied (for example, if it is referenced by another
261     *             managed object).
262     * @throws LdapException
263     *             If any other error occurs.
264     */
265    public final <C extends ConfigurationClient, S extends Configuration> boolean deleteManagedObject(
266        ManagedObjectPath<?, ?> parent, InstantiableRelationDefinition<C, S> rd, String name)
267            throws ManagedObjectNotFoundException, OperationRejectedException, LdapException {
268        validateRelationDefinition(parent, rd);
269        ManagedObjectPath<?, ?> child = parent.child(rd, name);
270        return doDeleteManagedObject(child);
271    }
272
273    /**
274     * Deletes the optional child managed object from the named parent managed
275     * object.
276     *
277     * @param <C>
278     *            The type of client managed object configuration that the
279     *            relation definition refers to.
280     * @param <S>
281     *            The type of server managed object configuration that the
282     *            relation definition refers to.
283     * @param parent
284     *            The path of the parent managed object.
285     * @param rd
286     *            The optional relation definition.
287     * @return Returns <code>true</code> if the optional child managed object
288     *         was found, or <code>false</code> if it was not found.
289     * @throws IllegalArgumentException
290     *             If the relation definition is not associated with the parent
291     *             managed object's definition.
292     * @throws ManagedObjectNotFoundException
293     *             If the parent managed object could not be found.
294     * @throws OperationRejectedException
295     *             If the managed object cannot be removed due to some
296     *             client-side or server-side constraint which cannot be
297     *             satisfied (for example, if it is referenced by another
298     *             managed object).
299     * @throws LdapException
300     *             If any other error occurs.
301     */
302    public final <C extends ConfigurationClient, S extends Configuration> boolean deleteManagedObject(
303        ManagedObjectPath<?, ?> parent, OptionalRelationDefinition<C, S> rd) throws ManagedObjectNotFoundException,
304        OperationRejectedException, LdapException {
305        validateRelationDefinition(parent, rd);
306        ManagedObjectPath<?, ?> child = parent.child(rd);
307        return doDeleteManagedObject(child);
308    }
309
310    /**
311     * Deletes the named instantiable child managed object from the named parent
312     * managed object.
313     *
314     * @param <C>
315     *            The type of client managed object configuration that the
316     *            relation definition refers to.
317     * @param <S>
318     *            The type of server managed object configuration that the
319     *            relation definition refers to.
320     * @param parent
321     *            The path of the parent managed object.
322     * @param rd
323     *            The instantiable relation definition.
324     * @param name
325     *            The name of the child managed object to be removed.
326     * @return Returns <code>true</code> if the named instantiable child managed
327     *         object was found, or <code>false</code> if it was not found.
328     * @throws IllegalArgumentException
329     *             If the relation definition is not associated with the parent
330     *             managed object's definition.
331     * @throws ManagedObjectNotFoundException
332     *             If the parent managed object could not be found.
333     * @throws OperationRejectedException
334     *             If the managed object cannot be removed due to some
335     *             client-side or server-side constraint which cannot be
336     *             satisfied (for example, if it is referenced by another
337     *             managed object).
338     * @throws LdapException
339     *             If any other error occurs.
340     */
341    public final <C extends ConfigurationClient, S extends Configuration> boolean deleteManagedObject(
342        ManagedObjectPath<?, ?> parent, SetRelationDefinition<C, S> rd, String name)
343            throws ManagedObjectNotFoundException, OperationRejectedException, LdapException {
344        validateRelationDefinition(parent, rd);
345        ManagedObjectPath<?, ?> child = parent.child(rd, name);
346        return doDeleteManagedObject(child);
347    }
348
349    /**
350     * Gets the named managed object. The path is guaranteed to be non-empty, so
351     * implementations do not need to worry about handling this special case.
352     *
353     * @param <C>
354     *            The type of client managed object configuration that the path
355     *            definition refers to.
356     * @param <S>
357     *            The type of server managed object configuration that the path
358     *            definition refers to.
359     * @param path
360     *            The non-empty path of the managed object.
361     * @return Returns the named managed object.
362     * @throws DefinitionDecodingException
363     *             If the managed object was found but its type could not be
364     *             determined.
365     * @throws ManagedObjectDecodingException
366     *             If the managed object was found but one or more of its
367     *             properties could not be decoded.
368     * @throws ManagedObjectNotFoundException
369     *             If the requested managed object could not be found on the
370     *             server.
371     * @throws LdapException
372     *             If any other error occurs.
373     */
374    // @Checkstyle:ignore
375    public abstract <C extends ConfigurationClient, S extends Configuration> ManagedObject<? extends C> getManagedObject(
376        ManagedObjectPath<C, S> path) throws DefinitionDecodingException, ManagedObjectDecodingException,
377        ManagedObjectNotFoundException, LdapException;
378
379    /**
380     * Gets the effective values of a property in the named managed object.
381     * <p>
382     * Implementations MUST NOT not use
383     * {@link #getManagedObject(ManagedObjectPath)} to read the referenced
384     * managed object in its entirety. Specifically, implementations MUST only
385     * attempt to resolve the default values for the requested property and its
386     * dependencies (if it uses inherited defaults). This is to avoid infinite
387     * recursion where a managed object contains a property which inherits
388     * default values from another property in the same managed object.
389     *
390     * @param <C>
391     *            The type of client managed object configuration that the path
392     *            definition refers to.
393     * @param <S>
394     *            The type of server managed object configuration that the path
395     *            definition refers to.
396     * @param <P>
397     *            The type of the property to be retrieved.
398     * @param path
399     *            The path of the managed object containing the property.
400     * @param pd
401     *            The property to be retrieved.
402     * @return Returns the property's effective values, or an empty set if there
403     *         are no values defined.
404     * @throws IllegalArgumentException
405     *             If the property definition is not associated with the
406     *             referenced managed object's definition.
407     * @throws DefinitionDecodingException
408     *             If the managed object was found but its type could not be
409     *             determined.
410     * @throws PropertyException
411     *             If the managed object was found but the requested property
412     *             could not be decoded.
413     * @throws ManagedObjectNotFoundException
414     *             If the requested managed object could not be found on the
415     *             server.
416     * @throws LdapException
417     *             If any other error occurs.
418     */
419    public abstract <C extends ConfigurationClient, S extends Configuration, P> SortedSet<P> getPropertyValues(
420        ManagedObjectPath<C, S> path, PropertyDefinition<P> pd) throws DefinitionDecodingException,
421        ManagedObjectNotFoundException, LdapException;
422
423    /**
424     * Gets the root configuration managed object associated with this
425     * management context driver.
426     *
427     * @return Returns the root configuration managed object associated with
428     *         this management context driver.
429     */
430    public abstract ManagedObject<RootCfgClient> getRootConfigurationManagedObject();
431
432    /**
433     * Lists the child managed objects of the named parent managed object which
434     * are a sub-type of the specified managed object definition.
435     *
436     * @param <C>
437     *            The type of client managed object configuration that the
438     *            relation definition refers to.
439     * @param <S>
440     *            The type of server managed object configuration that the
441     *            relation definition refers to.
442     * @param parent
443     *            The path of the parent managed object.
444     * @param rd
445     *            The instantiable relation definition.
446     * @param d
447     *            The managed object definition.
448     * @return Returns the names of the child managed objects which are a
449     *         sub-type of the specified managed object definition.
450     * @throws IllegalArgumentException
451     *             If the relation definition is not associated with the parent
452     *             managed object's definition.
453     * @throws ManagedObjectNotFoundException
454     *             If the parent managed object could not be found.
455     * @throws LdapException
456     *             If any other error occurs.
457     */
458    public abstract <C extends ConfigurationClient, S extends Configuration> String[] listManagedObjects(
459        ManagedObjectPath<?, ?> parent, InstantiableRelationDefinition<C, S> rd,
460        AbstractManagedObjectDefinition<? extends C, ? extends S> d) throws ManagedObjectNotFoundException,
461        LdapException;
462
463    /**
464     * Lists the child managed objects of the named parent managed object which
465     * are a sub-type of the specified managed object definition.
466     *
467     * @param <C>
468     *            The type of client managed object configuration that the
469     *            relation definition refers to.
470     * @param <S>
471     *            The type of server managed object configuration that the
472     *            relation definition refers to.
473     * @param parent
474     *            The path of the parent managed object.
475     * @param rd
476     *            The set relation definition.
477     * @param d
478     *            The managed object definition.
479     * @return Returns the names of the child managed objects which are a
480     *         sub-type of the specified managed object definition.
481     * @throws IllegalArgumentException
482     *             If the relation definition is not associated with the parent
483     *             managed object's definition.
484     * @throws ManagedObjectNotFoundException
485     *             If the parent managed object could not be found.
486     * @throws LdapException
487     *             If any other error occurs.
488     */
489    public abstract <C extends ConfigurationClient, S extends Configuration> String[] listManagedObjects(
490        ManagedObjectPath<?, ?> parent, SetRelationDefinition<C, S> rd,
491        AbstractManagedObjectDefinition<? extends C, ? extends S> d) throws ManagedObjectNotFoundException,
492        LdapException;
493
494    /**
495     * Determines whether or not the named managed object exists.
496     * <p>
497     * Implementations should always return <code>true</code> when the provided
498     * path is empty.
499     *
500     * @param path
501     *            The path of the named managed object.
502     * @return Returns <code>true</code> if the named managed object exists,
503     *         <code>false</code> otherwise.
504     * @throws ManagedObjectNotFoundException
505     *             If the parent managed object could not be found.
506     * @throws LdapException
507     *             If any other error occurs.
508     */
509    public abstract boolean managedObjectExists(ManagedObjectPath<?, ?> path) throws ManagedObjectNotFoundException,
510        LdapException;
511
512    /**
513     * Deletes the named managed object.
514     * <p>
515     * Implementations do not need check whether the named managed object
516     * exists, nor do they need to enforce client constraints.
517     *
518     * @param <C>
519     *            The type of client managed object configuration that the
520     *            relation definition refers to.
521     * @param <S>
522     *            The type of server managed object configuration that the
523     *            relation definition refers to.
524     * @param path
525     *            The path of the managed object to be deleted.
526     * @throws OperationRejectedException
527     *             If the managed object cannot be removed due to some
528     *             server-side constraint which cannot be satisfied (for
529     *             example, if it is referenced by another managed object).
530     * @throws LdapException
531     *             If any other error occurs.
532     */
533    protected abstract <C extends ConfigurationClient, S extends Configuration> void deleteManagedObject(
534        ManagedObjectPath<C, S> path) throws OperationRejectedException, LdapException;
535
536    /**
537     * Gets the default values for the specified property.
538     *
539     * @param <P>
540     *            The type of the property.
541     * @param p
542     *            The managed object path of the current managed object.
543     * @param pd
544     *            The property definition.
545     * @param isCreate
546     *            Indicates whether the managed object has been created yet.
547     * @return Returns the default values for the specified property.
548     * @throws PropertyException
549     *             If the default values could not be retrieved or decoded
550     *             properly.
551     */
552    protected final <P> Collection<P> findDefaultValues(ManagedObjectPath<?, ?> p, PropertyDefinition<P> pd,
553        boolean isCreate) {
554        DefaultValueFinder<P> v = new DefaultValueFinder<>(p, isCreate);
555        return v.find(p, pd);
556    }
557
558    /**
559     * Gets the management context associated with this driver.
560     *
561     * @return Returns the management context associated with this driver.
562     */
563    protected abstract ManagementContext getManagementContext();
564
565    /**
566     * Validate that a relation definition belongs to the managed object
567     * referenced by the provided path.
568     *
569     * @param path
570     *            The parent managed object path.
571     * @param rd
572     *            The relation definition.
573     * @throws IllegalArgumentException
574     *             If the relation definition does not belong to the managed
575     *             object definition.
576     */
577    protected final void validateRelationDefinition(ManagedObjectPath<?, ?> path, RelationDefinition<?, ?> rd) {
578        AbstractManagedObjectDefinition<?, ?> d = path.getManagedObjectDefinition();
579        RelationDefinition<?, ?> tmp = d.getRelationDefinition(rd.getName());
580        if (tmp != rd) {
581            throw new IllegalArgumentException("The relation " + rd.getName() + " is not associated with a "
582                + d.getName());
583        }
584    }
585
586    /**
587     * Remove a managed object, first ensuring that the parent exists,
588     * then ensuring that the child exists, before ensuring that any
589     * constraints are satisfied.
590     */
591    private <C extends ConfigurationClient, S extends Configuration> boolean doDeleteManagedObject(
592        ManagedObjectPath<C, S> path) throws ManagedObjectNotFoundException, OperationRejectedException,
593        LdapException {
594        // First make sure that the parent exists.
595        if (!managedObjectExists(path.parent())) {
596            throw new ManagedObjectNotFoundException();
597        }
598
599        // Make sure that the targeted managed object exists.
600        if (!managedObjectExists(path)) {
601            return false;
602        }
603
604        // The targeted managed object is guaranteed to exist, so enforce
605        // any constraints.
606        AbstractManagedObjectDefinition<?, ?> d = path.getManagedObjectDefinition();
607        List<LocalizableMessage> messages = new LinkedList<>();
608        if (!isAcceptable(path, d, messages)) {
609            throw new OperationRejectedException(OperationType.DELETE, d.getUserFriendlyName(), messages);
610        }
611
612        deleteManagedObject(path);
613        return true;
614    }
615
616    private <C extends ConfigurationClient, S extends Configuration>
617    boolean isAcceptable(ManagedObjectPath<C, S> path, AbstractManagedObjectDefinition<?, ?> d,
618            List<LocalizableMessage> messages) throws LdapException {
619        for (Constraint constraint : d.getAllConstraints()) {
620            for (ClientConstraintHandler handler : constraint.getClientConstraintHandlers()) {
621                ManagementContext context = getManagementContext();
622                if (!handler.isDeleteAcceptable(context, path, messages)) {
623                    return false;
624                }
625            }
626        }
627        return true;
628    }
629}