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