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 2012-2015 ForgeRock AS.
015 */
016package org.forgerock.opendj.rest2ldap;
017
018import java.util.ArrayList;
019import java.util.List;
020import java.util.Set;
021
022import org.forgerock.json.JsonPointer;
023import org.forgerock.json.JsonValue;
024import org.forgerock.json.resource.BadRequestException;
025import org.forgerock.json.resource.ResourceException;
026import org.forgerock.opendj.ldap.Attribute;
027import org.forgerock.opendj.ldap.AttributeDescription;
028import org.forgerock.opendj.ldap.ByteString;
029import org.forgerock.opendj.ldap.Entry;
030import org.forgerock.opendj.ldap.Filter;
031import org.forgerock.util.Function;
032import org.forgerock.util.promise.NeverThrowsException;
033import org.forgerock.util.promise.Promise;
034
035import static java.util.Collections.*;
036
037import static org.forgerock.opendj.ldap.Filter.*;
038import static org.forgerock.opendj.rest2ldap.Rest2LDAP.*;
039import static org.forgerock.opendj.rest2ldap.Utils.*;
040import static org.forgerock.util.promise.Promises.newExceptionPromise;
041import static org.forgerock.util.promise.Promises.newResultPromise;
042
043/**
044 * An attribute mapper which provides a simple mapping from a JSON value to a
045 * single LDAP attribute.
046 */
047public final class SimpleAttributeMapper extends AbstractLDAPAttributeMapper<SimpleAttributeMapper> {
048    private Function<ByteString, ?, NeverThrowsException> decoder;
049    private Function<Object, ByteString, NeverThrowsException> encoder;
050
051    SimpleAttributeMapper(final AttributeDescription ldapAttributeName) {
052        super(ldapAttributeName);
053    }
054
055    /**
056     * Sets the decoder which will be used for converting LDAP attribute values
057     * to JSON values.
058     *
059     * @param f
060     *            The function to use for decoding LDAP attribute values.
061     * @return This attribute mapper.
062     */
063    public SimpleAttributeMapper decoder(final Function<ByteString, ?, NeverThrowsException> f) {
064        this.decoder = f;
065        return this;
066    }
067
068    /**
069     * Sets the default JSON value which should be substituted when the LDAP
070     * attribute is not found in the LDAP entry.
071     *
072     * @param defaultValue
073     *            The default JSON value.
074     * @return This attribute mapper.
075     */
076    public SimpleAttributeMapper defaultJSONValue(final Object defaultValue) {
077        this.defaultJSONValues = defaultValue != null ? singletonList(defaultValue) : emptyList();
078        return this;
079    }
080
081    /**
082     * Sets the encoder which will be used for converting JSON values to LDAP
083     * attribute values.
084     *
085     * @param f
086     *            The function to use for encoding LDAP attribute values.
087     * @return This attribute mapper.
088     */
089    public SimpleAttributeMapper encoder(final Function<Object, ByteString, NeverThrowsException> f) {
090        this.encoder = f;
091        return this;
092    }
093
094    /**
095     * Indicates that JSON values are base 64 encodings of binary data. Calling
096     * this method is equivalent to the following:
097     *
098     * <pre>
099     * mapper.decoder(...); // function that converts binary data to base 64
100     * mapper.encoder(...); // function that converts base 64 to binary data
101     * </pre>
102     *
103     * @return This attribute mapper.
104     */
105    public SimpleAttributeMapper isBinary() {
106        decoder = byteStringToBase64();
107        encoder = base64ToByteString();
108        return this;
109    }
110
111    @Override
112    public String toString() {
113        return "simple(" + ldapAttributeName + ")";
114    }
115
116    @Override
117    Promise<Filter, ResourceException> getLDAPFilter(final RequestState requestState, final JsonPointer path,
118            final JsonPointer subPath, final FilterType type, final String operator, final Object valueAssertion) {
119        if (subPath.isEmpty()) {
120            try {
121                final ByteString va =
122                        valueAssertion != null ? encoder().apply(valueAssertion) : null;
123                return newResultPromise(toFilter(type, ldapAttributeName.toString(), va));
124            } catch (final Exception e) {
125                // Invalid assertion value - bad request.
126                return newExceptionPromise((ResourceException) new BadRequestException(i18n(
127                        "The request cannot be processed because it contained an "
128                                + "illegal filter assertion value '%s' for field '%s'",
129                        String.valueOf(valueAssertion), path), e));
130            }
131        } else {
132            // This attribute mapper does not support partial filtering.
133            return newResultPromise(alwaysFalse());
134        }
135    }
136
137    @Override
138    Promise<Attribute, ResourceException> getNewLDAPAttributes(
139            final RequestState requestState, final JsonPointer path, final List<Object> newValues) {
140        try {
141            return newResultPromise(jsonToAttribute(newValues, ldapAttributeName, encoder()));
142        } catch (final Exception ex) {
143            return newExceptionPromise((ResourceException) new BadRequestException(i18n(
144                    "The request cannot be processed because an error occurred while "
145                            + "encoding the values for the field '%s': %s", path, ex.getMessage())));
146        }
147    }
148
149    @Override
150    SimpleAttributeMapper getThis() {
151        return this;
152    }
153
154    @Override
155    Promise<JsonValue, ResourceException> read(final RequestState requestState, final JsonPointer path, final Entry e) {
156        try {
157            final Object value;
158            if (attributeIsSingleValued()) {
159                value =
160                        e.parseAttribute(ldapAttributeName).as(decoder(),
161                                defaultJSONValues.isEmpty() ? null : defaultJSONValues.get(0));
162            } else {
163                final Set<Object> s =
164                        e.parseAttribute(ldapAttributeName).asSetOf(decoder(), defaultJSONValues);
165                value = s.isEmpty() ? null : new ArrayList<>(s);
166            }
167            return newResultPromise(value != null ? new JsonValue(value) : null);
168        } catch (final Exception ex) {
169            // The LDAP attribute could not be decoded.
170            return newExceptionPromise(asResourceException(ex));
171        }
172    }
173
174    private Function<ByteString, ?, NeverThrowsException> decoder() {
175        return decoder == null ? byteStringToJson(ldapAttributeName) : decoder;
176    }
177
178    private Function<Object, ByteString, NeverThrowsException> encoder() {
179        return encoder == null ? jsonToByteString(ldapAttributeName) : encoder;
180    }
181
182}