/* * 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 2008-2009 Sun Microsystems, Inc. * Portions Copyright 2013 ForgeRock, AS. */ package org.forgerock.opendj.config.client.ldap; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.opendj.server.config.client.RootCfgClient; import org.forgerock.opendj.server.config.meta.RootCfgDefn; import org.forgerock.opendj.config.AbstractManagedObjectDefinition; import org.forgerock.opendj.config.AggregationPropertyDefinition; import org.forgerock.opendj.config.Configuration; import org.forgerock.opendj.config.ConfigurationClient; import org.forgerock.opendj.config.PropertyException; import org.forgerock.opendj.config.DefinitionDecodingException; import org.forgerock.opendj.config.DefinitionResolver; import org.forgerock.opendj.config.InstantiableRelationDefinition; import org.forgerock.opendj.config.LDAPProfile; import org.forgerock.opendj.config.ManagedObjectDefinition; import org.forgerock.opendj.config.ManagedObjectNotFoundException; import org.forgerock.opendj.config.ManagedObjectPath; import org.forgerock.opendj.config.PropertyDefinition; import org.forgerock.opendj.config.PropertyDefinitionVisitor; import org.forgerock.opendj.config.PropertyOption; import org.forgerock.opendj.config.Reference; import org.forgerock.opendj.config.RelationDefinition; import org.forgerock.opendj.config.SetRelationDefinition; import org.forgerock.opendj.config.DefinitionDecodingException.Reason; import org.forgerock.opendj.config.client.ManagedObject; import org.forgerock.opendj.config.client.ManagedObjectDecodingException; import org.forgerock.opendj.config.client.OperationRejectedException; import org.forgerock.opendj.config.client.OperationRejectedException.OperationType; import org.forgerock.opendj.config.client.spi.Driver; import org.forgerock.opendj.config.client.spi.PropertySet; import org.forgerock.opendj.ldap.Attribute; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.ldap.ErrorResultException; import org.forgerock.opendj.ldap.ResultCode; import org.forgerock.opendj.ldap.responses.SearchResultEntry; /** * The LDAP management context driver implementation. */ final class LDAPDriver extends Driver { /** * 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 pd * 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

pd, Object value) { String s = String.valueOf(value); return pd.castValue(pd.accept(new ValueDecoder(), s)); } // 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 LDAPManagementContext context; private final LDAPConnection connection; // The LDAP profile which should be used to construct LDAP // requests and decode LDAP responses. private final LDAPProfile profile; /** * Creates a new LDAP driver using the specified LDAP connection and * profile. * * @param connection * The LDAP connection. * @param profile * The LDAP profile. */ public LDAPDriver(LDAPConnection connection, LDAPProfile profile) { this.connection = connection; this.profile = profile; } void setManagementContext(LDAPManagementContext context) { this.context = context; } /** * {@inheritDoc} */ @Override public void close() { connection.unbind(); } /** * {@inheritDoc} */ @Override public ManagedObject getManagedObject( ManagedObjectPath path) throws DefinitionDecodingException, ManagedObjectDecodingException, ManagedObjectNotFoundException, ErrorResultException { if (!managedObjectExists(path)) { throw new ManagedObjectNotFoundException(); } try { // Read the entry associated with the managed object. DN dn = LDAPNameBuilder.create(path, profile); AbstractManagedObjectDefinition d = path.getManagedObjectDefinition(); ManagedObjectDefinition mod = getEntryDefinition(d, dn); ArrayList attrIds = new ArrayList(); for (PropertyDefinition pd : mod.getAllPropertyDefinitions()) { String attrId = profile.getAttributeName(mod, pd); attrIds.add(attrId); } SearchResultEntry searchResultEntry = connection.readEntry(dn, attrIds); // Build the managed object's properties. List exceptions = new LinkedList(); PropertySet newProperties = new PropertySet(); for (PropertyDefinition pd : mod.getAllPropertyDefinitions()) { String attrID = profile.getAttributeName(mod, pd); Attribute attribute = searchResultEntry.getAttribute(attrID); try { decodeProperty(newProperties, path, pd, attribute); } catch (PropertyException e) { exceptions.add(e); } } // If there were no decoding problems then return the object, // otherwise throw an operations exception. ManagedObject mo = createExistingManagedObject(mod, path, newProperties); if (exceptions.isEmpty()) { return mo; } else { throw new ManagedObjectDecodingException(mo, exceptions); } } catch (ErrorResultException e) { if (e.getResult().getResultCode() == ResultCode.NO_SUCH_OBJECT) { throw new ManagedObjectNotFoundException(); } throw e; } } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public SortedSet

getPropertyValues( ManagedObjectPath path, PropertyDefinition

propertyDef) throws DefinitionDecodingException, ManagedObjectNotFoundException, ErrorResultException { // Check that the requested property is from the definition // associated with the path. AbstractManagedObjectDefinition d = path.getManagedObjectDefinition(); PropertyDefinition tmp = d.getPropertyDefinition(propertyDef.getName()); if (tmp != propertyDef) { throw new IllegalArgumentException("The property " + propertyDef.getName() + " is not associated with a " + d.getName()); } if (!managedObjectExists(path)) { throw new ManagedObjectNotFoundException(); } try { // Read the entry associated with the managed object. DN dn = LDAPNameBuilder.create(path, profile); ManagedObjectDefinition objectDef = getEntryDefinition(d, dn); // Make sure we use the correct property definition, the // provided one might have been overridden in the resolved // definition. propertyDef = (PropertyDefinition

) objectDef.getPropertyDefinition(propertyDef.getName()); String attrID = profile.getAttributeName(objectDef, propertyDef); SearchResultEntry resultEntry = connection.readEntry(dn, Collections.singleton(attrID)); Attribute attribute = resultEntry.getAttribute(attrID); // Decode the values. SortedSet

values = new TreeSet

(propertyDef); if (attribute != null) { for (ByteString byteValue : attribute) { P value = ValueDecoder.decode(propertyDef, byteValue); values.add(value); } } // Sanity check the returned values. if (values.size() > 1 && !propertyDef.hasOption(PropertyOption.MULTI_VALUED)) { throw PropertyException.propertyIsSingleValuedException(propertyDef); } if (values.isEmpty() && propertyDef.hasOption(PropertyOption.MANDATORY)) { throw PropertyException.propertyIsMandatoryException(propertyDef); } if (values.isEmpty()) { // Use the property's default values. values.addAll(findDefaultValues(path.asSubType(objectDef), propertyDef, false)); } return values; } catch (ErrorResultException e) { if (e.getResult().getResultCode() == ResultCode.NO_SUCH_OBJECT) { throw new ManagedObjectNotFoundException(); } throw e; } } /** * {@inheritDoc} */ @Override public ManagedObject getRootConfigurationManagedObject() { return new LDAPManagedObject(this, RootCfgDefn.getInstance(), ManagedObjectPath.emptyPath(), new PropertySet(), true, null); } /** * {@inheritDoc} */ @Override public String[] listManagedObjects( ManagedObjectPath parent, InstantiableRelationDefinition rd, AbstractManagedObjectDefinition d) throws ManagedObjectNotFoundException, ErrorResultException { validateRelationDefinition(parent, rd); if (!managedObjectExists(parent)) { throw new ManagedObjectNotFoundException(); } // Get the search base DN. DN dn = LDAPNameBuilder.create(parent, rd, profile); // Retrieve only those entries which are sub-types of the // specified definition. StringBuilder builder = new StringBuilder(); builder.append("(objectclass="); builder.append(profile.getObjectClass(d)); builder.append(')'); String filter = builder.toString(); List children = new ArrayList(); try { for (DN child : connection.listEntries(dn, filter)) { children.add(child.rdn().getFirstAVA().getAttributeValue().toString()); } } catch (ErrorResultException e) { if (e.getResult().getResultCode() == ResultCode.NO_SUCH_OBJECT) { // Ignore this // It means that the base entry does not exist // It might not if this managed object has just been created. } else { throw e; } } return children.toArray(new String[children.size()]); } /** * {@inheritDoc} */ @Override public String[] listManagedObjects( ManagedObjectPath parent, SetRelationDefinition rd, AbstractManagedObjectDefinition d) throws ManagedObjectNotFoundException, ErrorResultException { validateRelationDefinition(parent, rd); if (!managedObjectExists(parent)) { throw new ManagedObjectNotFoundException(); } // Get the search base DN. DN dn = LDAPNameBuilder.create(parent, rd, profile); // Retrieve only those entries which are sub-types of the // specified definition. StringBuilder builder = new StringBuilder(); builder.append("(objectclass="); builder.append(profile.getObjectClass(d)); builder.append(')'); String filter = builder.toString(); List children = new ArrayList(); try { for (DN child : connection.listEntries(dn, filter)) { children.add(child.rdn().getFirstAVA().getAttributeValue().toString()); } } catch (ErrorResultException e) { if (e.getResult().getResultCode() == ResultCode.NO_SUCH_OBJECT) { // Ignore this // It means that the base entry does not exist // It might not if this managed object has just been created. } else { throw e; } } return children.toArray(new String[children.size()]); } /** * {@inheritDoc} */ @Override public boolean managedObjectExists(ManagedObjectPath path) throws ManagedObjectNotFoundException, ErrorResultException { if (path.isEmpty()) { return true; } ManagedObjectPath parent = path.parent(); DN dn = LDAPNameBuilder.create(parent, profile); if (!entryExists(dn)) { throw new ManagedObjectNotFoundException(); } dn = LDAPNameBuilder.create(path, profile); return entryExists(dn); } /** * {@inheritDoc} */ @Override protected void deleteManagedObject( ManagedObjectPath path) throws OperationRejectedException, ErrorResultException { // Delete the entry and any subordinate entries. DN dn = LDAPNameBuilder.create(path, profile); try { connection.deleteSubtree(dn); } catch (ErrorResultException e) { if (e.getResult().getResultCode() == ResultCode.UNWILLING_TO_PERFORM) { AbstractManagedObjectDefinition d = path.getManagedObjectDefinition(); LocalizableMessage m = LocalizableMessage.raw("%s", e.getMessage()); throw new OperationRejectedException(OperationType.DELETE, d.getUserFriendlyName(), m); } throw e; } } /** * {@inheritDoc} */ @Override protected LDAPManagementContext getManagementContext() { return context; } /** * Determines whether the named LDAP entry exists. * * @param dn * The LDAP entry name. * @return Returns true if the named LDAP entry exists. * @throws ErrorResultException * if a problem occurs. */ boolean entryExists(DN dn) throws ErrorResultException { return connection.entryExists(dn); } /** * Gets the LDAP connection used for interacting with the server. * * @return Returns the LDAP connection used for interacting with the server. */ LDAPConnection getLDAPConnection() { return connection; } /** * Gets the LDAP profile which should be used to construct LDAP requests and * decode LDAP responses. * * @return Returns the LDAP profile which should be used to construct LDAP * requests and decode LDAP responses. */ LDAPProfile getLDAPProfile() { return profile; } // Create a managed object which already exists on the server. private ManagedObject createExistingManagedObject( ManagedObjectDefinition d, ManagedObjectPath p, PropertySet properties) { RelationDefinition rd = p.getRelationDefinition(); PropertyDefinition pd = null; if (rd instanceof InstantiableRelationDefinition) { InstantiableRelationDefinition ird = (InstantiableRelationDefinition) rd; pd = ird.getNamingPropertyDefinition(); } return new LDAPManagedObject(this, d, p.asSubType(d), properties, true, pd); } // Create a property using the provided string values. private

void decodeProperty(PropertySet newProperties, ManagedObjectPath path, PropertyDefinition

propertyDef, Attribute attribute) { PropertyException exception = null; // Get the property's active values. SortedSet

activeValues = new TreeSet

(propertyDef); if (attribute != null) { for (ByteString byteValue : attribute) { P value = ValueDecoder.decode(propertyDef, byteValue); activeValues.add(value); } } if (activeValues.size() > 1 && !propertyDef.hasOption(PropertyOption.MULTI_VALUED)) { // This exception takes precedence over previous exceptions. exception = PropertyException.propertyIsSingleValuedException(propertyDef); P value = activeValues.first(); activeValues.clear(); activeValues.add(value); } // Get the property's default values. Collection

defaultValues; try { defaultValues = findDefaultValues(path, propertyDef, false); } catch (PropertyException e) { defaultValues = Collections.emptySet(); exception = e; } newProperties.addProperty(propertyDef, defaultValues, activeValues); if (activeValues.isEmpty() && defaultValues.isEmpty() && propertyDef.hasOption(PropertyOption.MANDATORY)) { // The active values maybe empty because of a previous // exception. if (exception == null) { exception = PropertyException.propertyIsMandatoryException(propertyDef); } } if (exception != null) { throw exception; } } // Determine the type of managed object associated with the named // entry. // @Checkstyle:off private ManagedObjectDefinition getEntryDefinition(AbstractManagedObjectDefinition d, DN dn) throws ErrorResultException, DefinitionDecodingException { // @Checkstyle:on SearchResultEntry searchResultEntry = connection.readEntry(dn, Collections.singleton("objectclass")); Attribute objectClassAttr = searchResultEntry.getAttribute("objectclass"); if (objectClassAttr == null) { // No object classes. throw new DefinitionDecodingException(d, Reason.NO_TYPE_INFORMATION); } final Set objectClasses = new HashSet(); for (ByteString byteValue : objectClassAttr) { objectClasses.add(byteValue.toString().toLowerCase().trim()); } if (objectClasses.isEmpty()) { // No object classes. throw new DefinitionDecodingException(d, Reason.NO_TYPE_INFORMATION); } // Resolve the appropriate sub-type based on the object classes. DefinitionResolver resolver = new DefinitionResolver() { @Override public boolean matches(AbstractManagedObjectDefinition d) { String objectClass = profile.getObjectClass(d); return objectClasses.contains(objectClass); } }; return d.resolveManagedObjectDefinition(resolver); } }