/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2009 Sun Microsystems, Inc. * Portions copyright 2014-2015 ForgeRock AS. */ package org.forgerock.opendj.config.server; import static com.forgerock.opendj.ldap.AdminMessages.*; import static com.forgerock.opendj.util.StaticUtils.*; import static org.forgerock.opendj.config.PropertyException.defaultBehaviorException; import static org.forgerock.opendj.config.PropertyException.propertyIsSingleValuedException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.opendj.server.config.meta.RootCfgDefn; import org.forgerock.opendj.server.config.server.RootCfg; import org.forgerock.opendj.config.AbsoluteInheritedDefaultBehaviorProvider; import org.forgerock.opendj.config.AbstractManagedObjectDefinition; import org.forgerock.opendj.config.AggregationPropertyDefinition; import org.forgerock.opendj.config.AliasDefaultBehaviorProvider; import org.forgerock.opendj.config.Configuration; import org.forgerock.opendj.config.ConfigurationClient; import org.forgerock.opendj.config.PropertyException; import org.forgerock.opendj.config.DefaultBehaviorProviderVisitor; import org.forgerock.opendj.config.DefinedDefaultBehaviorProvider; import org.forgerock.opendj.config.DefinitionDecodingException; import org.forgerock.opendj.config.DefinitionDecodingException.Reason; import org.forgerock.opendj.config.DefinitionResolver; import org.forgerock.opendj.config.LDAPProfile; import org.forgerock.opendj.config.ManagedObjectDefinition; import org.forgerock.opendj.config.ManagedObjectPath; import org.forgerock.opendj.config.PropertyDefinition; import org.forgerock.opendj.config.PropertyDefinitionVisitor; import org.forgerock.opendj.config.PropertyNotFoundException; import org.forgerock.opendj.config.PropertyOption; import org.forgerock.opendj.config.Reference; import org.forgerock.opendj.config.RelationDefinition; import org.forgerock.opendj.config.RelativeInheritedDefaultBehaviorProvider; import org.forgerock.opendj.config.UndefinedDefaultBehaviorProvider; import org.forgerock.opendj.config.server.spi.ConfigurationRepository; import org.forgerock.opendj.ldap.Attribute; import org.forgerock.opendj.ldap.AttributeDescription; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.ldap.Entry; import org.forgerock.opendj.ldap.schema.AttributeType; import org.forgerock.opendj.ldap.schema.Schema; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Server management connection context. */ public final class ServerManagementContext { /** * A default behavior visitor used for retrieving the default values of a * property. * * @param * The type of the property. */ private final class DefaultValueFinder implements DefaultBehaviorProviderVisitor, Void> { /** Any exception that occurred whilst retrieving inherited default values. */ private PropertyException exception; /** * Optional new configuration entry which does not yet exist in * the configuration back-end. */ private final Entry newConfigEntry; /** The path of the managed object containing the next property. */ private ManagedObjectPath nextPath; /** The next property whose default values were required. */ private PropertyDefinition nextProperty; /** Private constructor. */ private DefaultValueFinder(Entry newConfigEntry) { this.newConfigEntry = newConfigEntry; } /** {@inheritDoc} */ @Override public Collection visitAbsoluteInherited(AbsoluteInheritedDefaultBehaviorProvider d, Void p) { try { return getInheritedProperty(d.getManagedObjectPath(), d.getManagedObjectDefinition(), d.getPropertyName()); } catch (PropertyException e) { exception = e; return Collections.emptySet(); } } /** {@inheritDoc} */ @Override public Collection visitAlias(AliasDefaultBehaviorProvider d, Void p) { return Collections.emptySet(); } /** {@inheritDoc} */ @Override public Collection visitDefined(DefinedDefaultBehaviorProvider d, Void p) { Collection stringValues = d.getDefaultValues(); List values = new ArrayList<>(stringValues.size()); for (String stringValue : stringValues) { try { values.add(nextProperty.decodeValue(stringValue)); } catch (PropertyException e) { exception = PropertyException.defaultBehaviorException(nextProperty, e); break; } } return values; } /** {@inheritDoc} */ @Override public Collection visitRelativeInherited(RelativeInheritedDefaultBehaviorProvider d, Void p) { try { return getInheritedProperty(d.getManagedObjectPath(nextPath), d.getManagedObjectDefinition(), d.getPropertyName()); } catch (PropertyException e) { exception = e; return Collections.emptySet(); } } /** {@inheritDoc} */ @Override public Collection visitUndefined(UndefinedDefaultBehaviorProvider d, Void p) { return Collections.emptySet(); } /** Find the default values for the next path/property. */ private Collection find(ManagedObjectPath path, PropertyDefinition propertyDef) { nextPath = path; nextProperty = propertyDef; Collection values = nextProperty.getDefaultBehaviorProvider().accept(this, null); if (exception != null) { throw exception; } if (values.size() > 1 && !propertyDef.hasOption(PropertyOption.MULTI_VALUED)) { throw defaultBehaviorException(propertyDef, propertyIsSingleValuedException(propertyDef)); } return values; } /** Get an inherited property value. */ @SuppressWarnings("unchecked") private Collection getInheritedProperty(ManagedObjectPath target, AbstractManagedObjectDefinition definition, String propertyName) { // First check that the requested type of managed object corresponds to the path. AbstractManagedObjectDefinition actual = target.getManagedObjectDefinition(); if (!definition.isParentOf(actual)) { throw PropertyException.defaultBehaviorException(nextProperty, new DefinitionDecodingException(actual, Reason.WRONG_TYPE_INFORMATION)); } // Save the current property in case of recursion. PropertyDefinition propDef1 = nextProperty; try { // Get the actual managed object definition. DN dn = DNBuilder.create(target); Entry configEntry; if (newConfigEntry != null && newConfigEntry.getName().equals(dn)) { configEntry = newConfigEntry; } else { configEntry = getManagedObjectConfigEntry(dn); } DefinitionResolver resolver = new MyDefinitionResolver(configEntry); ManagedObjectDefinition mod = definition.resolveManagedObjectDefinition(resolver); PropertyDefinition propDef2; try { PropertyDefinition propDefTmp = mod.getPropertyDefinition(propertyName); propDef2 = propDef1.getClass().cast(propDefTmp); } catch (IllegalArgumentException e) { throw new PropertyNotFoundException(propertyName); } catch (ClassCastException e) { // FIXME: would be nice to throw a better exception here. throw new PropertyNotFoundException(propertyName); } List attributeValues = getAttributeValues(mod, propDef2, configEntry); if (attributeValues.size() > 0) { Collection pvalues = new ArrayList<>(); for (String value : attributeValues) { pvalues.add(ValueDecoder.decode(propDef1, value)); } return pvalues; } else { // Recursively retrieve this property's default values. Collection tmp = find(target, propDef2); Collection pvalues = new ArrayList<>(tmp.size()); for (T value : tmp) { propDef1.validateValue(value); pvalues.add(value); } return pvalues; } } catch (Exception e) { throw PropertyException.defaultBehaviorException(propDef1, e); } } } /** * A definition resolver that determines the managed object definition from * the object classes of a ConfigEntry. */ private static final class MyDefinitionResolver implements DefinitionResolver { /** The config entry. */ private final Entry entry; /** Private constructor. */ private MyDefinitionResolver(Entry entry) { this.entry = entry; } /** {@inheritDoc} */ @Override public boolean matches(AbstractManagedObjectDefinition d) { String oc = LDAPProfile.getInstance().getObjectClass(d); // TODO : use the schema to get object class and check it in the entry // Commented because reject any config entry without proper schema loading // Previous code was // ObjectClass oc = DirectoryServer.getObjectClass(name.toLowerCase()); // if (oc == null) { // oc = DirectoryServer.getDefaultObjectClass(name); // } // return Entries.containsObjectClass(entry, oc); return entry.containsAttribute("objectClass", oc); } } /** * A visitor which is used to decode property LDAP values. */ private static final class ValueDecoder extends PropertyDefinitionVisitor { /** * Decodes the provided property LDAP value. * * @param

* The type of the property. * @param propertyDef * The property definition. * @param value * The LDAP string representation. * @return Returns the decoded LDAP value. * @throws PropertyException * If the property value could not be decoded because it was * invalid. */ public static

P decode(PropertyDefinition

propertyDef, String value) { return propertyDef.castValue(propertyDef.accept(new ValueDecoder(), value)); } /** Prevent instantiation. */ private ValueDecoder() { // Do nothing. } /** {@inheritDoc} */ @Override public Object visitAggregation( AggregationPropertyDefinition d, String p) { // Aggregations values are stored as full DNs in LDAP, but // just their common name is exposed in the admin framework. try { Reference reference = Reference.parseDN(d.getParentPath(), d.getRelationDefinition(), p); return reference.getName(); } catch (IllegalArgumentException e) { throw PropertyException.illegalPropertyValueException(d, p); } } /** {@inheritDoc} */ @Override public Object visitUnknown(PropertyDefinition d, String p) { // By default the property definition's decoder will do. return d.decodeValue(p); } } private static final Logger debugLogger = LoggerFactory.getLogger(ServerManagementContext.class); /** * The root server managed object, lazily initialized. */ private volatile ServerManagedObject root; /** Repository of configuration entries. */ private final ConfigurationRepository configRepository; /** * Creates a context from the provided configuration repository. * * @param repository * The repository of configuration entries. */ public ServerManagementContext(ConfigurationRepository repository) { configRepository = repository; } /** * Gets the named managed object. * * @param * The type of client managed object configuration that the path * definition refers to. * @param * The type of server managed object configuration that the path * definition refers to. * @param path * The path of the managed object. * @return Returns the named managed object. * @throws ConfigException * If the named managed object could not be found or if it could * not be decoded. */ @SuppressWarnings("unchecked") public ServerManagedObject getManagedObject( ManagedObjectPath path) throws ConfigException { // Be careful to handle the root configuration. if (path.isEmpty()) { return (ServerManagedObject) getRootConfigurationManagedObject(); } // Get the configuration entry. DN targetDN = DNBuilder.create(path); Entry configEntry = getManagedObjectConfigEntry(targetDN); try { ServerManagedObject managedObject; managedObject = decode(path, configEntry); // Enforce any constraints. managedObject.ensureIsUsable(); return managedObject; } catch (DefinitionDecodingException e) { throw ConfigExceptionFactory.getInstance().createDecodingExceptionAdaptor(targetDN, e); } catch (ServerManagedObjectDecodingException e) { throw ConfigExceptionFactory.getInstance().createDecodingExceptionAdaptor(e); } catch (ConstraintViolationException e) { throw ConfigExceptionFactory.getInstance().createDecodingExceptionAdaptor(e); } } /** * Gets the effective value of a property in the named managed object. * * @param * The type of client managed object configuration that the path * definition refers to. * @param * The type of server managed object configuration that the path * definition refers to. * @param

* The type of the property to be retrieved. * @param path * The path of the managed object containing the property. * @param pd * The property to be retrieved. * @return Returns the property's effective value, or null if * there are no values defined. * @throws IllegalArgumentException * If the property definition is not associated with the * referenced managed object's definition. * @throws PropertyException * If the managed object was found but the requested property * could not be decoded. * @throws ConfigException * If the named managed object could not be found or if it could * not be decoded. */ public P getPropertyValue( ManagedObjectPath path, PropertyDefinition

pd) throws ConfigException { SortedSet

values = getPropertyValues(path, pd); if (!values.isEmpty()) { return values.first(); } return null; } /** * Gets the effective values of a property in the named managed object. * * @param * The type of client managed object configuration that the path * definition refers to. * @param * The type of server managed object configuration that the path * definition refers to. * @param

* The type of the property to be retrieved. * @param path * The path of the managed object containing the property. * @param propertyDef * The property to be retrieved. * @return Returns the property's effective values, or an empty set if there * are no values defined. * @throws IllegalArgumentException * If the property definition is not associated with the * referenced managed object's definition. * @throws PropertyException * If the managed object was found but the requested property * could not be decoded. * @throws ConfigException * If the named managed object could not be found or if it could * not be decoded. */ @SuppressWarnings("unchecked") public SortedSet

getPropertyValues( ManagedObjectPath path, PropertyDefinition

propertyDef) throws ConfigException { // Check that the requested property is from the definition // associated with the path. AbstractManagedObjectDefinition definition = path.getManagedObjectDefinition(); PropertyDefinition tmpPropertyDef = definition.getPropertyDefinition(propertyDef.getName()); if (tmpPropertyDef != propertyDef) { throw new IllegalArgumentException("The property " + propertyDef.getName() + " is not associated with a " + definition.getName()); } // Determine the exact type of managed object referenced by the path. DN dn = DNBuilder.create(path); Entry configEntry = getManagedObjectConfigEntry(dn); DefinitionResolver resolver = new MyDefinitionResolver(configEntry); ManagedObjectDefinition managedObjDef; try { managedObjDef = definition.resolveManagedObjectDefinition(resolver); } catch (DefinitionDecodingException e) { throw ConfigExceptionFactory.getInstance().createDecodingExceptionAdaptor(dn, e); } // Make sure we use the correct property definition, the // provided one might have been overridden in the resolved definition. propertyDef = (PropertyDefinition

) managedObjDef.getPropertyDefinition(propertyDef.getName()); List attributeValues = getAttributeValues(managedObjDef, propertyDef, configEntry); return decodeProperty(path.asSubType(managedObjDef), propertyDef, attributeValues, null); } /** * Get the root configuration manager associated with this management * context. * * @return the root configuration manager associated with this * management context. */ public RootCfg getRootConfiguration() { return getRootConfigurationManagedObject().getConfiguration(); } /** * Get the root configuration server managed object associated with this * management context. * * @return the root configuration server managed object */ public ServerManagedObject getRootConfigurationManagedObject() { // Use lazy initialisation // because it needs a reference to this server context. ServerManagedObject rootObject = root; if (rootObject == null) { synchronized (this) { rootObject = root; if (rootObject == null) { root = rootObject = new ServerManagedObject<>(ManagedObjectPath.emptyPath(), RootCfgDefn.getInstance(), Collections., SortedSet> emptyMap(), null, this); } } } return rootObject; } /** * Lists the child managed objects of the named parent managed object. * * @param * The type of client managed object configuration that the * relation definition refers to. * @param * The type of server managed object configuration that the * relation definition refers to. * @param parent * The path of the parent managed object. * @param relationDef * The relation definition. * @return Returns the names of the child managed objects. * @throws IllegalArgumentException * If the relation definition is not associated with the parent * managed object's definition. */ public String[] listManagedObjects( ManagedObjectPath parent, RelationDefinition relationDef) { validateRelationDefinition(parent, relationDef); // Get the target entry. DN targetDN = DNBuilder.create(parent, relationDef); Set children; try { children = configRepository.getChildren(targetDN); } catch (ConfigException e) { return new String[0]; } List names = new ArrayList<>(children.size()); for (DN child : children) { // Assume that RDNs are single-valued and can be trimmed. String name = child.rdn().getFirstAVA().getAttributeValue().toString().trim(); names.add(name); } return names.toArray(new String[names.size()]); } /** * Determines whether or not the named managed object exists. * * @param path * The path of the named managed object. * @return Returns true if the named managed object exists, * false otherwise. */ public boolean managedObjectExists(ManagedObjectPath path) { // Get the configuration entry. DN targetDN = DNBuilder.create(path); try { return configRepository.getEntry(targetDN) != null; } catch (ConfigException e) { // Assume it doesn't exist. return false; } } /** * Decodes a configuration entry into the required type of server managed * object. * * @param * The type of client managed object configuration that the path * definition refers to. * @param * The type of server managed object configuration that the path * definition refers to. * @param path * The location of the server managed object. * @param configEntry * The configuration entry that should be decoded. * @return Returns the new server-side managed object from the provided * definition and configuration entry. * @throws DefinitionDecodingException * If the managed object's type could not be determined. * @throws ServerManagedObjectDecodingException * If one or more of the managed object's properties could not * be decoded. */ ServerManagedObject decode( ManagedObjectPath path, Entry configEntry) throws DefinitionDecodingException, ServerManagedObjectDecodingException { return decode(path, configEntry, null); } /** * Decodes a configuration entry into the required type of server managed * object. * * @param * The type of client managed object configuration that the path * definition refers to. * @param * The type of server managed object configuration that the path * definition refers to. * @param path * The location of the server managed object. * @param configEntry * The configuration entry that should be decoded. * @param newConfigEntry * Optional new configuration that does not exist yet in the * configuration back-end. This will be used for resolving * inherited default values. * @return Returns the new server-side managed object from the provided * definition and configuration entry. * @throws DefinitionDecodingException * If the managed object's type could not be determined. * @throws ServerManagedObjectDecodingException * If one or more of the managed object's properties could not * be decoded. */ ServerManagedObject decode( ManagedObjectPath path, Entry configEntry, Entry newConfigEntry) throws DefinitionDecodingException, ServerManagedObjectDecodingException { // First determine the correct definition to use for the entry. // This could either be the provided definition, or one of its // sub-definitions. DefinitionResolver resolver = new MyDefinitionResolver(configEntry); AbstractManagedObjectDefinition d = path.getManagedObjectDefinition(); ManagedObjectDefinition mod = d.resolveManagedObjectDefinition(resolver); // Build the managed object's properties. List exceptions = new LinkedList<>(); Map, SortedSet> properties = new HashMap<>(); for (PropertyDefinition propertyDef : mod.getAllPropertyDefinitions()) { List attributeValues = getAttributeValues(mod, propertyDef, configEntry); try { SortedSet pvalues = decodeProperty(path, propertyDef, attributeValues, newConfigEntry); properties.put(propertyDef, pvalues); } catch (PropertyException e) { exceptions.add(e); } } // If there were no decoding problems then return the managed // object, otherwise throw an operations exception. ServerManagedObject managedObject = decodeAux(path, mod, properties, configEntry.getName()); if (exceptions.isEmpty()) { return managedObject; } else { throw new ServerManagedObjectDecodingException(managedObject, exceptions); } } /** Decode helper method required to avoid generics warning. */ private ServerManagedObject decodeAux( ManagedObjectPath path, ManagedObjectDefinition d, Map, SortedSet> properties, DN configDN) { ManagedObjectPath newPath = path.asSubType(d); return new ServerManagedObject<>(newPath, d, properties, configDN, this); } /** Decode a property using the provided attribute values. */ private SortedSet decodeProperty(ManagedObjectPath path, PropertyDefinition propertyDef, List attributeValues, Entry newConfigEntry) { PropertyException exception = null; SortedSet pvalues = new TreeSet<>(propertyDef); if (attributeValues.size() > 0) { // The property has values defined for it. for (String value : attributeValues) { try { pvalues.add(ValueDecoder.decode(propertyDef, value)); } catch (PropertyException e) { exception = e; } } } else { // No values defined so get the defaults. try { pvalues.addAll(getDefaultValues(path, propertyDef, newConfigEntry)); } catch (PropertyException e) { exception = e; } } if (pvalues.size() > 1 && !propertyDef.hasOption(PropertyOption.MULTI_VALUED)) { // This exception takes precedence over previous exceptions. exception = PropertyException.propertyIsSingleValuedException(propertyDef); T value = pvalues.first(); pvalues.clear(); pvalues.add(value); } if (pvalues.isEmpty() && propertyDef.hasOption(PropertyOption.MANDATORY) && exception == null) { exception = PropertyException.propertyIsMandatoryException(propertyDef); } if (exception != null) { throw exception; } return pvalues; } /** Gets the attribute values associated with a property from a ConfigEntry. */ private List getAttributeValues(ManagedObjectDefinition d, PropertyDefinition pd, Entry configEntry) { // TODO: we create a default attribute type if it is undefined. // We should log a warning here if this is the case // since the attribute should have been defined. String attrID = LDAPProfile.getInstance().getAttributeName(d, pd); AttributeType type = Schema.getDefaultSchema().getAttributeType(attrID); Iterable attributes = configEntry.getAllAttributes(AttributeDescription.create(type)); List values = new ArrayList<>(); for (Attribute attribute : attributes) { for (ByteString byteValue : attribute) { values.add(byteValue.toString()); } } return values; } /** Get the default values for the specified property. */ private Collection getDefaultValues(ManagedObjectPath p, PropertyDefinition pd, Entry newConfigEntry) { DefaultValueFinder v = new DefaultValueFinder<>(newConfigEntry); return v.find(p, pd); } /** * Retrieves a configuration entry corresponding to the provided DN. * * @param dn * DN of the configuration entry. * @return the configuration entry * @throws ConfigException * If a problem occurs. */ public Entry getConfigEntry(DN dn) throws ConfigException { return configRepository.getEntry(dn); } /** * Returns the repository containing all configuration entries. * * @return the repository */ public ConfigurationRepository getConfigRepository() { return configRepository; } /** * Gets a config entry required for a managed object and throws a * config exception on failure. */ private Entry getManagedObjectConfigEntry(DN dn) throws ConfigException { Entry configEntry; try { configEntry = configRepository.getEntry(dn); } catch (ConfigException e) { debugLogger.trace("Unable to perform post add", e); LocalizableMessage message = ERR_ADMIN_CANNOT_GET_MANAGED_OBJECT.get(String.valueOf(dn), stackTraceToSingleLineString(e, true)); throw new ConfigException(message, e); } // The configuration handler is free to return null indicating // that the entry does not exist. if (configEntry == null) { LocalizableMessage message = ERR_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST.get(String.valueOf(dn)); throw new ConfigException(message); } return configEntry; } /** Validate that a relation definition belongs to the path. */ private void validateRelationDefinition(ManagedObjectPath path, RelationDefinition rd) { AbstractManagedObjectDefinition d = path.getManagedObjectDefinition(); RelationDefinition tmp = d.getRelationDefinition(rd.getName()); if (tmp != rd) { throw new IllegalArgumentException("The relation " + rd.getName() + " is not associated with a " + d.getName()); } } }