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