/*
* 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 2007-2009 Sun Microsystems, Inc.
* Portions Copyright 2014-2015 ForgeRock AS
*/
package org.forgerock.opendj.config.client.ldap;
import java.util.List;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.config.AggregationPropertyDefinition;
import org.forgerock.opendj.config.Configuration;
import org.forgerock.opendj.config.ConfigurationClient;
import org.forgerock.opendj.config.InstantiableRelationDefinition;
import org.forgerock.opendj.config.ManagedObjectAlreadyExistsException;
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.PropertyOption;
import org.forgerock.opendj.config.PropertyValueVisitor;
import org.forgerock.opendj.config.Reference;
import org.forgerock.opendj.config.RelationDefinition;
import org.forgerock.opendj.config.SetRelationDefinition;
import org.forgerock.opendj.config.client.ConcurrentModificationException;
import org.forgerock.opendj.config.client.ManagedObject;
import org.forgerock.opendj.config.client.OperationRejectedException;
import org.forgerock.opendj.config.client.OperationRejectedException.OperationType;
import org.forgerock.opendj.config.client.spi.AbstractManagedObject;
import org.forgerock.opendj.config.client.spi.Driver;
import org.forgerock.opendj.config.client.spi.Property;
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.Entry;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LinkedAttribute;
import org.forgerock.opendj.ldap.LinkedHashMapEntry;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.Requests;
/**
* A managed object bound to an LDAP connection.
*
* @param <T>
* The type of client configuration represented by the client managed
* object.
*/
final class LDAPManagedObject<T extends ConfigurationClient> extends AbstractManagedObject<T> {
/**
* A visitor which is used to encode property LDAP values.
*/
private static final class ValueEncoder extends PropertyValueVisitor<Object, Void> {
/** Prevent instantiation. */
private ValueEncoder() {
// No implementation required.
}
/** {@inheritDoc} */
@Override
public <C extends ConfigurationClient, S extends Configuration> Object visitAggregation(
AggregationPropertyDefinition<C, S> pd, String v, Void p) {
// Aggregations values are stored as full DNs in LDAP, but
// just their common name is exposed in the admin framework.
Reference<C, S> reference = Reference.parseName(pd.getParentPath(), pd.getRelationDefinition(), v);
return reference.toDN().toString();
}
/** {@inheritDoc} */
@Override
public <P> Object visitUnknown(PropertyDefinition<P> propertyDef, P value, Void p) {
return propertyDef.encodeValue(value);
}
}
/** The LDAP management driver associated with this managed object. */
private final LDAPDriver driver;
/**
* Creates a new LDAP managed object instance.
*
* @param driver
* The underlying LDAP management driver.
* @param d
* The managed object's definition.
* @param path
* The managed object's path.
* @param properties
* The managed object's properties.
* @param existsOnServer
* Indicates whether or not the managed object already exists.
* @param namingPropertyDefinition
* The managed object's naming property definition if there is
* one.
*/
LDAPManagedObject(LDAPDriver driver, ManagedObjectDefinition<T, ? extends Configuration> d,
ManagedObjectPath<T, ? extends Configuration> path, PropertySet properties, boolean existsOnServer,
PropertyDefinition<?> namingPropertyDefinition) {
super(d, path, properties, existsOnServer, namingPropertyDefinition);
this.driver = driver;
}
/** {@inheritDoc} */
@Override
protected void addNewManagedObject() throws LdapException, OperationRejectedException,
ConcurrentModificationException, ManagedObjectAlreadyExistsException {
// First make sure that the parent managed object still exists.
ManagedObjectDefinition<?, ?> d = getManagedObjectDefinition();
ManagedObjectPath<?, ?> path = getManagedObjectPath();
ManagedObjectPath<?, ?> parent = path.parent();
try {
if (!driver.managedObjectExists(parent)) {
throw new ConcurrentModificationException();
}
} catch (ManagedObjectNotFoundException e) {
throw new ConcurrentModificationException();
}
// We may need to create the parent "relation" entry if this is a
// child of an instantiable or set relation.
RelationDefinition<?, ?> r = path.getRelationDefinition();
if (r instanceof InstantiableRelationDefinition || r instanceof SetRelationDefinition) {
// TODO: this implementation does not handle relations which
// comprise of more than one RDN arc (this will probably never
// be required anyway).
DN dn;
if (r instanceof InstantiableRelationDefinition) {
dn = DNBuilder.create(parent, (InstantiableRelationDefinition<?, ?>) r,
driver.getLDAPProfile());
} else {
dn = DNBuilder.create(parent, (SetRelationDefinition<?, ?>) r,
driver.getLDAPProfile());
}
if (!driver.entryExists(dn)) {
Entry entry = new LinkedHashMapEntry(dn);
// Create the branch's object class attribute.
List<String> objectClasses = driver.getLDAPProfile().getRelationObjectClasses(r);
addObjectClassesToEntry(objectClasses, entry);
// Create the branch's naming attribute.
entry.addAttribute(dn.rdn().getFirstAVA().toAttribute());
// Create the entry.
try {
driver.getLDAPConnection().add(entry);
} catch (LdapException e) {
if (e.getResult().getResultCode() == ResultCode.UNWILLING_TO_PERFORM) {
LocalizableMessage m = LocalizableMessage.raw("%s", e.getLocalizedMessage());
throw new OperationRejectedException(OperationType.CREATE, d.getUserFriendlyName(), m);
} else {
throw e;
}
}
}
}
// Now add the entry representing this new managed object.
DN dn = DNBuilder.create(path, driver.getLDAPProfile());
Entry entry = new LinkedHashMapEntry(dn);
// Create the object class attribute.
ManagedObjectDefinition<?, ?> definition = getManagedObjectDefinition();
List<String> objectClasses = driver.getLDAPProfile().getObjectClasses(definition);
addObjectClassesToEntry(objectClasses, entry);
// Create the naming attribute if there is not naming property.
PropertyDefinition<?> namingPropertyDef = getNamingPropertyDefinition();
if (namingPropertyDef == null) {
entry.addAttribute(dn.rdn().getFirstAVA().toAttribute());
}
// Create the remaining attributes.
for (PropertyDefinition<?> propertyDef : definition.getAllPropertyDefinitions()) {
String attrID = driver.getLDAPProfile().getAttributeName(definition, propertyDef);
Attribute attribute = new LinkedAttribute(attrID);
encodeProperty(attribute, propertyDef);
if (attribute.size() != 0) {
entry.addAttribute(attribute);
}
}
try {
// Create the entry.
driver.getLDAPConnection().add(entry);
} catch (LdapException e) {
if (e.getResult().getResultCode() == ResultCode.ENTRY_ALREADY_EXISTS) {
throw new ManagedObjectAlreadyExistsException();
} else if (e.getResult().getResultCode() == ResultCode.UNWILLING_TO_PERFORM) {
LocalizableMessage m = LocalizableMessage.raw("%s", e.getLocalizedMessage());
throw new OperationRejectedException(OperationType.CREATE, d.getUserFriendlyName(), m);
} else {
throw e;
}
}
}
private void addObjectClassesToEntry(List<String> objectClasses, Entry entry) {
for (String objectClass : objectClasses) {
Attribute attr = new LinkedAttribute("objectClass");
attr.add(ByteString.valueOfUtf8(objectClass));
entry.addAttribute(attr);
}
}
/** {@inheritDoc} */
@Override
protected Driver getDriver() {
return driver;
}
/** {@inheritDoc} */
@Override
protected void modifyExistingManagedObject() throws ConcurrentModificationException, OperationRejectedException,
LdapException {
// Build the modify request
ManagedObjectPath<?, ?> path = getManagedObjectPath();
DN dn = DNBuilder.create(path, driver.getLDAPProfile());
ModifyRequest request = Requests.newModifyRequest(dn);
ManagedObjectDefinition<?, ?> d = getManagedObjectDefinition();
for (PropertyDefinition<?> pd : d.getAllPropertyDefinitions()) {
Property<?> p = getProperty(pd);
if (p.isModified()) {
String attrID = driver.getLDAPProfile().getAttributeName(d, pd);
Attribute attribute = new LinkedAttribute(attrID);
encodeProperty(attribute, pd);
request.addModification(ModificationType.REPLACE, attrID,
attribute.toArray(new Object[attribute.size()]));
}
}
// Perform the LDAP modification if something has changed.
if (!request.getModifications().isEmpty()) {
try {
driver.getLDAPConnection().modify(request);
} catch (LdapException e) {
if (e.getResult().getResultCode() == ResultCode.UNWILLING_TO_PERFORM) {
LocalizableMessage m = LocalizableMessage.raw("%s", e.getLocalizedMessage());
throw new OperationRejectedException(OperationType.MODIFY, d.getUserFriendlyName(), m);
} else {
throw e;
}
}
}
}
/** {@inheritDoc} */
@Override
protected <M extends ConfigurationClient> ManagedObject<M> newInstance(ManagedObjectDefinition<M, ?> d,
ManagedObjectPath<M, ?> path, PropertySet properties, boolean existsOnServer,
PropertyDefinition<?> namingPropertyDefinition) {
return new LDAPManagedObject<>(driver, d, path, properties, existsOnServer, namingPropertyDefinition);
}
/** Encode a property into LDAP string values. */
private <P> void encodeProperty(Attribute attribute, PropertyDefinition<P> propertyDef) {
PropertyValueVisitor<Object, Void> visitor = new ValueEncoder();
Property<P> property = getProperty(propertyDef);
if (propertyDef.hasOption(PropertyOption.MANDATORY)) {
// For mandatory properties we fall-back to the default values
// if defined which can sometimes be the case e.g when a
// mandatory property is overridden.
for (P value : property.getEffectiveValues()) {
attribute.add(propertyDef.accept(visitor, value, null));
}
} else {
for (P value : property.getPendingValues()) {
attribute.add(propertyDef.accept(visitor, value, null));
}
}
}
/** {@inheritDoc} */
public boolean isModified() {
ManagedObjectDefinition<?, ?> d = getManagedObjectDefinition();
for (PropertyDefinition<?> pd : d.getAllPropertyDefinitions()) {
Property<?> p = getProperty(pd);
if (p.isModified()) {
return true;
}
}
return false;
}
}