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