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