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 2009 Sun Microsystems, Inc.
015 * Portions copyright 2014-2016 ForgeRock AS.
016 */
017
018package org.forgerock.opendj.config.server;
019
020import static com.forgerock.opendj.ldap.config.ConfigMessages.*;
021import static com.forgerock.opendj.util.StaticUtils.*;
022import static org.forgerock.opendj.config.PropertyException.defaultBehaviorException;
023import static org.forgerock.opendj.config.PropertyException.propertyIsSingleValuedException;
024
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.LinkedList;
030import java.util.List;
031import java.util.Map;
032import java.util.Set;
033import java.util.SortedSet;
034import java.util.TreeSet;
035
036import org.forgerock.i18n.LocalizableMessage;
037import org.forgerock.opendj.server.config.meta.RootCfgDefn;
038import org.forgerock.opendj.server.config.server.RootCfg;
039import org.forgerock.opendj.config.AbsoluteInheritedDefaultBehaviorProvider;
040import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
041import org.forgerock.opendj.config.AggregationPropertyDefinition;
042import org.forgerock.opendj.config.AliasDefaultBehaviorProvider;
043import org.forgerock.opendj.config.Configuration;
044import org.forgerock.opendj.config.ConfigurationClient;
045import org.forgerock.opendj.config.PropertyException;
046import org.forgerock.opendj.config.DefaultBehaviorProviderVisitor;
047import org.forgerock.opendj.config.DefinedDefaultBehaviorProvider;
048import org.forgerock.opendj.config.DefinitionDecodingException;
049import org.forgerock.opendj.config.DefinitionDecodingException.Reason;
050import org.forgerock.opendj.config.DefinitionResolver;
051import org.forgerock.opendj.config.LDAPProfile;
052import org.forgerock.opendj.config.ManagedObjectDefinition;
053import org.forgerock.opendj.config.ManagedObjectPath;
054import org.forgerock.opendj.config.PropertyDefinition;
055import org.forgerock.opendj.config.PropertyDefinitionVisitor;
056import org.forgerock.opendj.config.PropertyNotFoundException;
057import org.forgerock.opendj.config.PropertyOption;
058import org.forgerock.opendj.config.Reference;
059import org.forgerock.opendj.config.RelationDefinition;
060import org.forgerock.opendj.config.RelativeInheritedDefaultBehaviorProvider;
061import org.forgerock.opendj.config.UndefinedDefaultBehaviorProvider;
062import org.forgerock.opendj.config.server.spi.ConfigurationRepository;
063import org.forgerock.opendj.ldap.Attribute;
064import org.forgerock.opendj.ldap.AttributeDescription;
065import org.forgerock.opendj.ldap.ByteString;
066import org.forgerock.opendj.ldap.DN;
067import org.forgerock.opendj.ldap.Entry;
068import org.forgerock.opendj.ldap.schema.AttributeType;
069import org.forgerock.opendj.ldap.schema.Schema;
070import org.slf4j.Logger;
071import org.slf4j.LoggerFactory;
072
073/** Server management connection context. */
074public final class ServerManagementContext {
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        /** Any exception that occurred whilst retrieving inherited default values. */
086        private PropertyException exception;
087
088        /** Optional new configuration entry which does not yet exist in the configuration back-end. */
089        private final Entry newConfigEntry;
090
091        /** The path of the managed object containing the next property. */
092        private ManagedObjectPath<?, ?> nextPath;
093
094        /** The next property whose default values were required. */
095        private PropertyDefinition<T> nextProperty;
096
097        /** Private constructor. */
098        private DefaultValueFinder(Entry newConfigEntry) {
099            this.newConfigEntry = newConfigEntry;
100        }
101
102        @Override
103        public Collection<T> visitAbsoluteInherited(AbsoluteInheritedDefaultBehaviorProvider<T> d, Void p) {
104            try {
105                return getInheritedProperty(d.getManagedObjectPath(), d.getManagedObjectDefinition(),
106                        d.getPropertyName());
107            } catch (PropertyException e) {
108                exception = e;
109                return Collections.emptySet();
110            }
111        }
112
113        @Override
114        public Collection<T> visitAlias(AliasDefaultBehaviorProvider<T> d, Void p) {
115            return Collections.emptySet();
116        }
117
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        @Override
136        public Collection<T> visitRelativeInherited(RelativeInheritedDefaultBehaviorProvider<T> d, Void p) {
137            try {
138                return getInheritedProperty(d.getManagedObjectPath(nextPath), d.getManagedObjectDefinition(),
139                        d.getPropertyName());
140            } catch (PropertyException e) {
141                exception = e;
142                return Collections.emptySet();
143            }
144        }
145
146        @Override
147        public Collection<T> visitUndefined(UndefinedDefaultBehaviorProvider<T> d, Void p) {
148            return Collections.emptySet();
149        }
150
151        /** Find the default values for the next path/property. */
152        private Collection<T> find(ManagedObjectPath<?, ?> path, PropertyDefinition<T> propertyDef) {
153            nextPath = path;
154            nextProperty = propertyDef;
155
156            Collection<T> values = nextProperty.getDefaultBehaviorProvider().accept(this, null);
157
158            if (exception != null) {
159                throw exception;
160            }
161
162            if (values.size() > 1 && !propertyDef.hasOption(PropertyOption.MULTI_VALUED)) {
163                throw defaultBehaviorException(propertyDef, propertyIsSingleValuedException(propertyDef));
164            }
165
166            return values;
167        }
168
169        /** Get an inherited property value. */
170        @SuppressWarnings("unchecked")
171        private Collection<T> getInheritedProperty(ManagedObjectPath<?, ?> target,
172            AbstractManagedObjectDefinition<?, ?> definition, String propertyName) {
173            // First check that the requested type of managed object corresponds to the path.
174            AbstractManagedObjectDefinition<?, ?> actual = target.getManagedObjectDefinition();
175            if (!definition.isParentOf(actual)) {
176                throw PropertyException.defaultBehaviorException(nextProperty, new DefinitionDecodingException(actual,
177                        Reason.WRONG_TYPE_INFORMATION));
178            }
179
180            // Save the current property in case of recursion.
181            PropertyDefinition<T> propDef1 = nextProperty;
182
183            try {
184                // Get the actual managed object definition.
185                DN dn = DNBuilder.create(target);
186                Entry configEntry;
187                if (newConfigEntry != null && newConfigEntry.getName().equals(dn)) {
188                    configEntry = newConfigEntry;
189                } else {
190                    configEntry = getManagedObjectConfigEntry(dn);
191                }
192
193                DefinitionResolver resolver = new MyDefinitionResolver(configEntry);
194                ManagedObjectDefinition<?, ?> mod = definition.resolveManagedObjectDefinition(resolver);
195
196                PropertyDefinition<T> propDef2;
197                try {
198                    PropertyDefinition<?> propDefTmp = mod.getPropertyDefinition(propertyName);
199                    propDef2 = propDef1.getClass().cast(propDefTmp);
200                } catch (IllegalArgumentException | ClassCastException e) {
201                    throw new PropertyNotFoundException(propertyName);
202                }
203
204                List<String> attributeValues = getAttributeValues(mod, propDef2, configEntry);
205                if (attributeValues.size() > 0) {
206                    Collection<T> pvalues = new ArrayList<>();
207                    for (String value : attributeValues) {
208                        pvalues.add(ValueDecoder.decode(propDef1, value));
209                    }
210                    return pvalues;
211                } else {
212                    // Recursively retrieve this property's default values.
213                    Collection<T> tmp = find(target, propDef2);
214                    Collection<T> pvalues = new ArrayList<>(tmp.size());
215                    for (T value : tmp) {
216                        propDef1.validateValue(value);
217                        pvalues.add(value);
218                    }
219                    return pvalues;
220                }
221            } catch (Exception e) {
222                throw PropertyException.defaultBehaviorException(propDef1, e);
223            }
224        }
225    }
226
227    /**
228     * A definition resolver that determines the managed object definition from
229     * the object classes of a ConfigEntry.
230     */
231    private static final class MyDefinitionResolver implements DefinitionResolver {
232
233        /** The config entry. */
234        private final Entry entry;
235
236        /** Private constructor. */
237        private MyDefinitionResolver(Entry entry) {
238            this.entry = entry;
239        }
240
241        @Override
242        public boolean matches(AbstractManagedObjectDefinition<?, ?> d) {
243            String oc = LDAPProfile.getInstance().getObjectClass(d);
244         // TODO : use the schema to get object class and check it in the entry
245         // Commented because reject any config entry without proper schema loading
246         // Previous code was
247//            ObjectClass oc = DirectoryServer.getObjectClass(name.toLowerCase());
248//            if (oc == null) {
249//              oc = DirectoryServer.getDefaultObjectClass(name);
250//            }
251//            return Entries.containsObjectClass(entry, oc);
252            return entry.containsAttribute("objectClass", oc);
253        }
254    }
255
256    /** A visitor which is used to decode property LDAP values. */
257    private static final class ValueDecoder extends PropertyDefinitionVisitor<Object, String> {
258
259        /**
260         * Decodes the provided property LDAP value.
261         *
262         * @param <P>
263         *            The type of the property.
264         * @param propertyDef
265         *            The property definition.
266         * @param value
267         *            The LDAP string representation.
268         * @return Returns the decoded LDAP value.
269         * @throws PropertyException
270         *             If the property value could not be decoded because it was
271         *             invalid.
272         */
273        public static <P> P decode(PropertyDefinition<P> propertyDef, String value) {
274            return propertyDef.castValue(propertyDef.accept(new ValueDecoder(), value));
275        }
276
277        /** Prevent instantiation. */
278        private ValueDecoder() {
279            // Do nothing.
280        }
281
282        @Override
283        public <C extends ConfigurationClient, S extends Configuration> Object visitAggregation(
284                AggregationPropertyDefinition<C, S> d, String p) {
285            // Aggregations values are stored as full DNs in LDAP, but
286            // just their common name is exposed in the admin framework.
287            try {
288                Reference<C, S> reference = Reference.parseDN(d.getParentPath(), d.getRelationDefinition(), p);
289                return reference.getName();
290            } catch (IllegalArgumentException e) {
291                throw PropertyException.illegalPropertyValueException(d, p);
292            }
293        }
294
295        @Override
296        public <T> Object visitUnknown(PropertyDefinition<T> d, String p) {
297            // By default the property definition's decoder will do.
298            return d.decodeValue(p);
299        }
300    }
301
302    private static final Logger debugLogger = LoggerFactory.getLogger(ServerManagementContext.class);
303
304    /** The root server managed object, lazily initialized. */
305    private volatile ServerManagedObject<RootCfg> root;
306
307    /** Repository of configuration entries. */
308    private final ConfigurationRepository configRepository;
309
310    /**
311     * Creates a context from the provided configuration repository.
312     *
313     * @param repository
314     *          The repository of configuration entries.
315     */
316    public ServerManagementContext(ConfigurationRepository repository) {
317        configRepository = repository;
318    }
319
320    /**
321     * Gets the named managed object.
322     *
323     * @param <C>
324     *            The type of client managed object configuration that the path
325     *            definition refers to.
326     * @param <S>
327     *            The type of server managed object configuration that the path
328     *            definition refers to.
329     * @param path
330     *            The path of the managed object.
331     * @return Returns the named managed object.
332     * @throws ConfigException
333     *             If the named managed object could not be found or if it could
334     *             not be decoded.
335     */
336    @SuppressWarnings("unchecked")
337    public <C extends ConfigurationClient, S extends Configuration> ServerManagedObject<? extends S> getManagedObject(
338            ManagedObjectPath<C, S> path) throws ConfigException {
339        // Be careful to handle the root configuration.
340        if (path.isEmpty()) {
341            return (ServerManagedObject<S>) getRootConfigurationManagedObject();
342        }
343
344        // Get the configuration entry.
345        DN targetDN = DNBuilder.create(path);
346        Entry configEntry = getManagedObjectConfigEntry(targetDN);
347        try {
348            ServerManagedObject<? extends S> managedObject;
349            managedObject = decode(path, configEntry);
350
351            // Enforce any constraints.
352            managedObject.ensureIsUsable();
353
354            return managedObject;
355        } catch (DefinitionDecodingException e) {
356            throw ConfigExceptionFactory.getInstance().createDecodingExceptionAdaptor(targetDN, e);
357        } catch (ServerManagedObjectDecodingException e) {
358            throw ConfigExceptionFactory.getInstance().createDecodingExceptionAdaptor(e);
359        } catch (ConstraintViolationException e) {
360            throw ConfigExceptionFactory.getInstance().createDecodingExceptionAdaptor(e);
361        }
362    }
363
364    /**
365     * Gets the effective value of a property in the named managed object.
366     *
367     * @param <C>
368     *            The type of client managed object configuration that the path
369     *            definition refers to.
370     * @param <S>
371     *            The type of server managed object configuration that the path
372     *            definition refers to.
373     * @param <P>
374     *            The type of the property to be retrieved.
375     * @param path
376     *            The path of the managed object containing the property.
377     * @param pd
378     *            The property to be retrieved.
379     * @return Returns the property's effective value, or <code>null</code> if
380     *         there are no values defined.
381     * @throws IllegalArgumentException
382     *             If the property definition is not associated with the
383     *             referenced managed object's definition.
384     * @throws PropertyException
385     *             If the managed object was found but the requested property
386     *             could not be decoded.
387     * @throws ConfigException
388     *             If the named managed object could not be found or if it could
389     *             not be decoded.
390     */
391    public <C extends ConfigurationClient, S extends Configuration, P> P getPropertyValue(
392            ManagedObjectPath<C, S> path, PropertyDefinition<P> pd) throws ConfigException {
393        SortedSet<P> values = getPropertyValues(path, pd);
394        if (!values.isEmpty()) {
395            return values.first();
396        }
397        return null;
398    }
399
400    /**
401     * Gets the effective values of a property in the named managed object.
402     *
403     * @param <C>
404     *            The type of client managed object configuration that the path
405     *            definition refers to.
406     * @param <S>
407     *            The type of server managed object configuration that the path
408     *            definition refers to.
409     * @param <P>
410     *            The type of the property to be retrieved.
411     * @param path
412     *            The path of the managed object containing the property.
413     * @param propertyDef
414     *            The property to be retrieved.
415     * @return Returns the property's effective values, or an empty set if there
416     *         are no values defined.
417     * @throws IllegalArgumentException
418     *             If the property definition is not associated with the
419     *             referenced managed object's definition.
420     * @throws PropertyException
421     *             If the managed object was found but the requested property
422     *             could not be decoded.
423     * @throws ConfigException
424     *             If the named managed object could not be found or if it could
425     *             not be decoded.
426     */
427    @SuppressWarnings("unchecked")
428    public <C extends ConfigurationClient, S extends Configuration, P> SortedSet<P> getPropertyValues(
429            ManagedObjectPath<C, S> path, PropertyDefinition<P> propertyDef) throws ConfigException {
430        // Check that the requested property is from the definition
431        // associated with the path.
432        AbstractManagedObjectDefinition<C, S> definition = path.getManagedObjectDefinition();
433        PropertyDefinition<?> tmpPropertyDef = definition.getPropertyDefinition(propertyDef.getName());
434        if (tmpPropertyDef != propertyDef) {
435            throw new IllegalArgumentException("The property " + propertyDef.getName() + " is not associated with a "
436                    + definition.getName());
437        }
438
439        // Determine the exact type of managed object referenced by the path.
440        DN dn = DNBuilder.create(path);
441        Entry configEntry = getManagedObjectConfigEntry(dn);
442
443        DefinitionResolver resolver = new MyDefinitionResolver(configEntry);
444        ManagedObjectDefinition<? extends C, ? extends S> managedObjDef;
445
446        try {
447            managedObjDef = definition.resolveManagedObjectDefinition(resolver);
448        } catch (DefinitionDecodingException e) {
449            throw ConfigExceptionFactory.getInstance().createDecodingExceptionAdaptor(dn, e);
450        }
451
452        // Make sure we use the correct property definition, the
453        // provided one might have been overridden in the resolved definition.
454        propertyDef = (PropertyDefinition<P>) managedObjDef.getPropertyDefinition(propertyDef.getName());
455
456        List<String> attributeValues = getAttributeValues(managedObjDef, propertyDef, configEntry);
457        return decodeProperty(path.asSubType(managedObjDef), propertyDef, attributeValues, null);
458    }
459
460    /**
461     * Get the root configuration manager associated with this management
462     * context.
463     *
464     * @return the root configuration manager associated with this
465     *         management context.
466     */
467    public RootCfg getRootConfiguration() {
468        return getRootConfigurationManagedObject().getConfiguration();
469    }
470
471    /**
472     * Get the root configuration server managed object associated with this
473     * management context.
474     *
475     * @return the root configuration server managed object
476     */
477    public ServerManagedObject<RootCfg> getRootConfigurationManagedObject() {
478        // Use lazy initialisation
479        // because it needs a reference to this server context.
480        ServerManagedObject<RootCfg> rootObject = root;
481        if (rootObject == null) {
482            synchronized (this) {
483                rootObject = root;
484                if (rootObject == null) {
485                    root = rootObject =
486                        new ServerManagedObject<>(ManagedObjectPath.emptyPath(),
487                                RootCfgDefn.getInstance(), Collections.<PropertyDefinition<?>,
488                                SortedSet<?>> emptyMap(), null, this);
489                }
490            }
491        }
492        return rootObject;
493    }
494
495    /**
496     * Lists the child managed objects of the named parent managed object.
497     *
498     * @param <C>
499     *            The type of client managed object configuration that the
500     *            relation definition refers to.
501     * @param <S>
502     *            The type of server managed object configuration that the
503     *            relation definition refers to.
504     * @param parent
505     *            The path of the parent managed object.
506     * @param relationDef
507     *            The relation definition.
508     * @return Returns the names of the child managed objects.
509     * @throws IllegalArgumentException
510     *             If the relation definition is not associated with the parent
511     *             managed object's definition.
512     */
513    public <C extends ConfigurationClient, S extends Configuration> String[] listManagedObjects(
514            ManagedObjectPath<?, ?> parent, RelationDefinition<C, S> relationDef) {
515        validateRelationDefinition(parent, relationDef);
516
517        // Get the target entry.
518        DN targetDN = DNBuilder.create(parent, relationDef);
519        Set<DN> children;
520        try {
521            children = configRepository.getChildren(targetDN);
522        } catch (ConfigException e) {
523            return new String[0];
524        }
525        List<String> names = new ArrayList<>(children.size());
526        for (DN child : children) {
527            // Assume that RDNs are single-valued and can be trimmed.
528            String name = child.rdn().getFirstAVA().getAttributeValue().toString().trim();
529            names.add(name);
530        }
531
532        return names.toArray(new String[names.size()]);
533    }
534
535    /**
536     * Determines whether the named managed object exists.
537     *
538     * @param path
539     *            The path of the named managed object.
540     * @return Returns <code>true</code> if the named managed object exists,
541     *         <code>false</code> otherwise.
542     */
543    public boolean managedObjectExists(ManagedObjectPath<?, ?> path) {
544        // Get the configuration entry.
545        DN targetDN = DNBuilder.create(path);
546        try {
547            return configRepository.getEntry(targetDN) != null;
548        } catch (ConfigException e) {
549            // Assume it doesn't exist.
550            return false;
551        }
552    }
553
554    /**
555     * Decodes a configuration entry into the required type of server managed
556     * object.
557     *
558     * @param <C>
559     *            The type of client managed object configuration that the path
560     *            definition refers to.
561     * @param <S>
562     *            The type of server managed object configuration that the path
563     *            definition refers to.
564     * @param path
565     *            The location of the server managed object.
566     * @param configEntry
567     *            The configuration entry that should be decoded.
568     * @return Returns the new server-side managed object from the provided
569     *         definition and configuration entry.
570     * @throws DefinitionDecodingException
571     *             If the managed object's type could not be determined.
572     * @throws ServerManagedObjectDecodingException
573     *             If one or more of the managed object's properties could not
574     *             be decoded.
575     */
576    <C extends ConfigurationClient, S extends Configuration> ServerManagedObject<? extends S> decode(
577            ManagedObjectPath<C, S> path, Entry configEntry) throws DefinitionDecodingException,
578            ServerManagedObjectDecodingException {
579        return decode(path, configEntry, null);
580    }
581
582    /**
583     * Decodes a configuration entry into the required type of server managed
584     * object.
585     *
586     * @param <C>
587     *            The type of client managed object configuration that the path
588     *            definition refers to.
589     * @param <S>
590     *            The type of server managed object configuration that the path
591     *            definition refers to.
592     * @param path
593     *            The location of the server managed object.
594     * @param configEntry
595     *            The configuration entry that should be decoded.
596     * @param newConfigEntry
597     *            Optional new configuration that does not exist yet in the
598     *            configuration back-end. This will be used for resolving
599     *            inherited default values.
600     * @return Returns the new server-side managed object from the provided
601     *         definition and configuration entry.
602     * @throws DefinitionDecodingException
603     *             If the managed object's type could not be determined.
604     * @throws ServerManagedObjectDecodingException
605     *             If one or more of the managed object's properties could not
606     *             be decoded.
607     */
608    <C extends ConfigurationClient, S extends Configuration> ServerManagedObject<? extends S> decode(
609            ManagedObjectPath<C, S> path, Entry configEntry, Entry newConfigEntry)
610            throws DefinitionDecodingException, ServerManagedObjectDecodingException {
611        // First determine the correct definition to use for the entry.
612        // This could either be the provided definition, or one of its
613        // sub-definitions.
614        DefinitionResolver resolver = new MyDefinitionResolver(configEntry);
615        AbstractManagedObjectDefinition<C, S> d = path.getManagedObjectDefinition();
616        ManagedObjectDefinition<? extends C, ? extends S> mod = d.resolveManagedObjectDefinition(resolver);
617
618        // Build the managed object's properties.
619        List<PropertyException> exceptions = new LinkedList<>();
620        Map<PropertyDefinition<?>, SortedSet<?>> properties = new HashMap<>();
621        for (PropertyDefinition<?> propertyDef : mod.getAllPropertyDefinitions()) {
622            List<String> attributeValues = getAttributeValues(mod, propertyDef, configEntry);
623            try {
624                SortedSet<?> pvalues = decodeProperty(path, propertyDef, attributeValues, newConfigEntry);
625                properties.put(propertyDef, pvalues);
626            } catch (PropertyException e) {
627                exceptions.add(e);
628            }
629        }
630
631        // If there were no decoding problems then return the managed
632        // object, otherwise throw an operations exception.
633        ServerManagedObject<? extends S> managedObject = decodeAux(path, mod, properties, configEntry.getName());
634        if (exceptions.isEmpty()) {
635            return managedObject;
636        } else {
637            throw new ServerManagedObjectDecodingException(managedObject, exceptions);
638        }
639    }
640
641    /** Decode helper method required to avoid generics warning. */
642    private <C extends ConfigurationClient, S extends Configuration> ServerManagedObject<S> decodeAux(
643            ManagedObjectPath<? super C, ? super S> path, ManagedObjectDefinition<C, S> d,
644            Map<PropertyDefinition<?>, SortedSet<?>> properties, DN configDN) {
645        ManagedObjectPath<C, S> newPath = path.asSubType(d);
646        return new ServerManagedObject<>(newPath, d, properties, configDN, this);
647    }
648
649    /** Decode a property using the provided attribute values. */
650    private <T> SortedSet<T> decodeProperty(ManagedObjectPath<?, ?> path, PropertyDefinition<T> propertyDef,
651            List<String> attributeValues, Entry newConfigEntry) {
652        PropertyException exception = null;
653        SortedSet<T> pvalues = new TreeSet<>(propertyDef);
654
655        if (attributeValues.size() > 0) {
656            // The property has values defined for it.
657            for (String value : attributeValues) {
658                try {
659                    pvalues.add(ValueDecoder.decode(propertyDef, value));
660                } catch (PropertyException e) {
661                    exception = e;
662                }
663            }
664        } else {
665            // No values defined so get the defaults.
666            try {
667                pvalues.addAll(getDefaultValues(path, propertyDef, newConfigEntry));
668            } catch (PropertyException e) {
669                exception = e;
670            }
671        }
672
673        if (pvalues.size() > 1 && !propertyDef.hasOption(PropertyOption.MULTI_VALUED)) {
674            // This exception takes precedence over previous exceptions.
675            exception = PropertyException.propertyIsSingleValuedException(propertyDef);
676            T value = pvalues.first();
677            pvalues.clear();
678            pvalues.add(value);
679        }
680
681        if (pvalues.isEmpty() && propertyDef.hasOption(PropertyOption.MANDATORY) && exception == null) {
682            exception = PropertyException.propertyIsMandatoryException(propertyDef);
683        }
684
685        if (exception != null) {
686            throw exception;
687        }
688        return pvalues;
689    }
690
691    /** Gets the attribute values associated with a property from a ConfigEntry. */
692    private List<String> getAttributeValues(ManagedObjectDefinition<?, ?> d, PropertyDefinition<?> pd,
693            Entry configEntry) {
694        // TODO: we create a default attribute type if it is undefined.
695        // We should log a warning here if this is the case
696        // since the attribute should have been defined.
697        String attrID = LDAPProfile.getInstance().getAttributeName(d, pd);
698        AttributeType type = Schema.getDefaultSchema().getAttributeType(attrID);
699        Iterable<Attribute> attributes = configEntry.getAllAttributes(AttributeDescription.create(type));
700        List<String> values = new ArrayList<>();
701        for (Attribute attribute : attributes) {
702            for (ByteString byteValue : attribute) {
703                values.add(byteValue.toString());
704            }
705        }
706        return values;
707    }
708
709    /** Get the default values for the specified property. */
710    private <T> Collection<T> getDefaultValues(ManagedObjectPath<?, ?> p, PropertyDefinition<T> pd,
711            Entry newConfigEntry) {
712        DefaultValueFinder<T> v = new DefaultValueFinder<>(newConfigEntry);
713        return v.find(p, pd);
714    }
715
716    /**
717     * Retrieves a configuration entry corresponding to the provided DN.
718     *
719     * @param dn
720     *            DN of the configuration entry.
721     * @return the configuration entry
722     * @throws ConfigException
723     *             If a problem occurs.
724     */
725    public Entry getConfigEntry(DN dn) throws ConfigException {
726        return configRepository.getEntry(dn);
727    }
728
729    /**
730     * Returns the repository containing all configuration entries.
731     *
732     * @return the repository
733     */
734    public ConfigurationRepository getConfigRepository() {
735        return configRepository;
736    }
737
738    /** Gets a config entry required for a managed object and throws a config exception on failure. */
739    private Entry getManagedObjectConfigEntry(DN dn) throws ConfigException {
740        Entry configEntry;
741        try {
742            configEntry = configRepository.getEntry(dn);
743        } catch (ConfigException e) {
744            debugLogger.trace("Unable to perform post add", e);
745
746            LocalizableMessage message = ERR_ADMIN_CANNOT_GET_MANAGED_OBJECT.get(
747                    dn, stackTraceToSingleLineString(e, true));
748            throw new ConfigException(message, e);
749        }
750
751        // The configuration handler is free to return null indicating
752        // that the entry does not exist.
753        if (configEntry == null) {
754            throw new ConfigException(ERR_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST.get(dn));
755        }
756
757        return configEntry;
758    }
759
760    /** Validate that a relation definition belongs to the path. */
761    private void validateRelationDefinition(ManagedObjectPath<?, ?> path, RelationDefinition<?, ?> rd) {
762        AbstractManagedObjectDefinition<?, ?> d = path.getManagedObjectDefinition();
763        RelationDefinition<?, ?> tmp = d.getRelationDefinition(rd.getName());
764        if (tmp != rd) {
765            throw new IllegalArgumentException("The relation " + rd.getName() + " is not associated with a "
766                    + d.getName());
767        }
768    }
769}