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-2016 ForgeRock AS.
015 */
016package org.forgerock.opendj.rest2ldap;
017
018import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
019
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.List;
023import java.util.Set;
024
025import org.forgerock.json.JsonPointer;
026import org.forgerock.json.JsonValue;
027import org.forgerock.json.resource.ResourceException;
028import org.forgerock.opendj.ldap.Attribute;
029import org.forgerock.opendj.ldap.AttributeDescription;
030import org.forgerock.opendj.ldap.ByteString;
031import org.forgerock.opendj.ldap.Connection;
032import org.forgerock.opendj.ldap.Entry;
033import org.forgerock.opendj.ldap.Filter;
034import org.forgerock.util.Function;
035import org.forgerock.util.promise.NeverThrowsException;
036import org.forgerock.util.promise.Promise;
037
038import static java.util.Collections.*;
039
040import static org.forgerock.opendj.ldap.Filter.*;
041import static org.forgerock.opendj.rest2ldap.Rest2Ldap.asResourceException;
042import static org.forgerock.opendj.rest2ldap.Utils.*;
043import static org.forgerock.util.promise.Promises.newResultPromise;
044
045/** An property mapper which provides a simple mapping from a JSON value to a single LDAP attribute. */
046public final class SimplePropertyMapper extends AbstractLdapPropertyMapper<SimplePropertyMapper> {
047    private Function<ByteString, ?, NeverThrowsException> decoder;
048    private Function<Object, ByteString, NeverThrowsException> encoder;
049
050    SimplePropertyMapper(final AttributeDescription ldapAttributeName) {
051        super(ldapAttributeName);
052    }
053
054    /**
055     * Sets the decoder which will be used for converting LDAP attribute values
056     * to JSON values.
057     *
058     * @param f
059     *            The function to use for decoding LDAP attribute values.
060     * @return This property mapper.
061     */
062    public SimplePropertyMapper decoder(final Function<ByteString, ?, NeverThrowsException> f) {
063        this.decoder = f;
064        return this;
065    }
066
067    /**
068     * Sets the default JSON value which should be substituted when the LDAP attribute is not found in the LDAP entry.
069     *
070     * @param defaultValue
071     *            The default JSON value.
072     * @return This property mapper.
073     */
074    public SimplePropertyMapper defaultJsonValue(final Object defaultValue) {
075        this.defaultJsonValues = defaultValue != null ? singletonList(defaultValue) : emptyList();
076        return this;
077    }
078
079    /**
080     * Sets the default JSON values which should be substituted when the LDAP attribute is not found in the LDAP entry.
081     *
082     * @param defaultValues
083     *            The default JSON values.
084     * @return This property mapper.
085     */
086    public SimplePropertyMapper defaultJsonValues(final Collection<?> defaultValues) {
087        this.defaultJsonValues = defaultValues != null ? new ArrayList<>(defaultValues) : emptyList();
088        return this;
089    }
090
091    /**
092     * Sets the encoder which will be used for converting JSON values to LDAP
093     * attribute values.
094     *
095     * @param f
096     *            The function to use for encoding LDAP attribute values.
097     * @return This property mapper.
098     */
099    public SimplePropertyMapper encoder(final Function<Object, ByteString, NeverThrowsException> f) {
100        this.encoder = f;
101        return this;
102    }
103
104    /**
105     * Indicates that JSON values are base 64 encodings of binary data. Calling
106     * this method with the value {@code true} is equivalent to the following:
107     *
108     * <pre>
109     * mapper.decoder(...); // function that converts binary data to base 64
110     * mapper.encoder(...); // function that converts base 64 to binary data
111     * </pre>
112     *
113     * Passing in a value of {@code false} resets the encoding and decoding
114     * functions to the default.
115     *
116     * @param isBinary {@code true} if this property is binary.
117     * @return This property mapper.
118     */
119    public SimplePropertyMapper isBinary(final boolean isBinary) {
120        if (isBinary) {
121            decoder = byteStringToBase64();
122            encoder = base64ToByteString();
123        } else {
124            decoder = null;
125            encoder = null;
126        }
127        return this;
128    }
129
130    @Override
131    public String toString() {
132        return "simple(" + ldapAttributeName + ")";
133    }
134
135    @Override
136    Promise<Filter, ResourceException> getLdapFilter(final Connection connection, final Resource resource,
137                                                     final JsonPointer path, final JsonPointer subPath,
138                                                     final FilterType type, final String operator,
139                                                     final Object valueAssertion) {
140        if (subPath.isEmpty()) {
141            try {
142                final ByteString va = valueAssertion != null ? encoder().apply(valueAssertion) : null;
143                return newResultPromise(toFilter(type, ldapAttributeName.toString(), va));
144            } catch (final Exception e) {
145                // Invalid assertion value - bad request.
146                return newBadRequestException(
147                        ERR_ILLEGAL_FILTER_ASSERTION_VALUE.get(String.valueOf(valueAssertion), path), e).asPromise();
148            }
149        } else {
150            // This property mapper does not support partial filtering.
151            return newResultPromise(alwaysFalse());
152        }
153    }
154
155    @Override
156    Promise<Attribute, ResourceException> getNewLdapAttributes(final Connection connection, final Resource resource,
157                                                               final JsonPointer path, final List<Object> newValues) {
158        try {
159            return newResultPromise(jsonToAttribute(newValues, ldapAttributeName, encoder()));
160        } catch (final Exception ex) {
161            return newBadRequestException(ERR_ENCODING_VALUES_FOR_FIELD.get(path, ex.getMessage())).asPromise();
162        }
163    }
164
165    @Override
166    SimplePropertyMapper getThis() {
167        return this;
168    }
169
170    @SuppressWarnings("fallthrough")
171    @Override
172    Promise<JsonValue, ResourceException> read(final Connection connection, final Resource resource,
173                                               final JsonPointer path, final Entry e) {
174        try {
175            final Set<Object> s = e.parseAttribute(ldapAttributeName).asSetOf(decoder(), defaultJsonValues);
176            switch (s.size()) {
177            case 0:
178                return newResultPromise(null);
179            case 1:
180                if (attributeIsSingleValued()) {
181                    return newResultPromise(new JsonValue(s.iterator().next()));
182                }
183                // Fall-though: unexpectedly got multiple values. It's probably best to just return them.
184            default:
185                return newResultPromise(new JsonValue(new ArrayList<>(s)));
186            }
187        } catch (final Exception ex) {
188            // The LDAP attribute could not be decoded.
189            return asResourceException(ex).asPromise();
190        }
191    }
192
193    private Function<ByteString, ?, NeverThrowsException> decoder() {
194        return decoder == null ? byteStringToJson(ldapAttributeName) : decoder;
195    }
196
197    private Function<Object, ByteString, NeverThrowsException> encoder() {
198        return encoder == null ? jsonToByteString(ldapAttributeName) : encoder;
199    }
200
201}