001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2009 Sun Microsystems, Inc.
025 *      Portions copyright 2012-2015 ForgeRock AS.
026 */
027package org.forgerock.opendj.ldap.controls;
028
029import static java.util.Arrays.asList;
030import static java.util.Collections.emptyList;
031import static java.util.Collections.singletonList;
032import static java.util.Collections.unmodifiableList;
033import static com.forgerock.opendj.ldap.CoreMessages.*;
034
035import java.io.IOException;
036import java.util.ArrayList;
037import java.util.Collection;
038import java.util.Collections;
039import java.util.List;
040
041import org.forgerock.i18n.LocalizableMessage;
042import org.forgerock.i18n.slf4j.LocalizedLogger;
043import org.forgerock.opendj.io.ASN1;
044import org.forgerock.opendj.io.ASN1Reader;
045import org.forgerock.opendj.io.ASN1Writer;
046import org.forgerock.opendj.ldap.ByteString;
047import org.forgerock.opendj.ldap.ByteStringBuilder;
048import org.forgerock.opendj.ldap.DecodeException;
049import org.forgerock.opendj.ldap.DecodeOptions;
050import org.forgerock.util.Reject;
051
052/**
053 * The pre-read request control as defined in RFC 4527. This control allows the
054 * client to read the target entry of an update operation immediately before the
055 * modifications are applied. These reads are done as an atomic part of the
056 * update operation.
057 * <p>
058 * The following example gets the entry as it was before the modify operation.
059 *
060 * <pre>
061 * Connection connection = ...;
062 * String DN = ...;
063 *
064 * ModifyRequest request =
065 *         Requests.newModifyRequest(DN)
066 *         .addControl(PreReadRequestControl.newControl(true, "mail"))
067 *         .addModification(ModificationType.REPLACE,
068 *                 "mail", "modified@example.com");
069 *
070 * Result result = connection.modify(request);
071 * PreReadResponseControl control =
072 *             result.getControl(PreReadResponseControl.DECODER,
073 *                     new DecodeOptions());
074 * Entry unmodifiedEntry = control.getEntry();
075 * </pre>
076 *
077 * @see PreReadResponseControl
078 * @see <a href="http://tools.ietf.org/html/rfc4527">RFC 4527 - Lightweight
079 *      Directory Access Protocol (LDAP) Read Entry Controls </a>
080 */
081public final class PreReadRequestControl implements Control {
082
083    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
084    /**
085     * The IANA-assigned OID for the LDAP pre-read request control used for
086     * retrieving an entry in the state it had immediately before an update was
087     * applied.
088     */
089    public static final String OID = "1.3.6.1.1.13.1";
090
091    /** The list of raw attributes to return in the entry. */
092    private final List<String> attributes;
093
094    private final boolean isCritical;
095
096    private static final PreReadRequestControl CRITICAL_EMPTY_INSTANCE = new PreReadRequestControl(
097            true, Collections.<String> emptyList());
098
099    private static final PreReadRequestControl NONCRITICAL_EMPTY_INSTANCE =
100            new PreReadRequestControl(false, Collections.<String> emptyList());
101
102    /**
103     * A decoder which can be used for decoding the pre-read request control.
104     */
105    public static final ControlDecoder<PreReadRequestControl> DECODER =
106            new ControlDecoder<PreReadRequestControl>() {
107
108                public PreReadRequestControl decodeControl(final Control control,
109                        final DecodeOptions options) throws DecodeException {
110                    Reject.ifNull(control);
111
112                    if (control instanceof PreReadRequestControl) {
113                        return (PreReadRequestControl) control;
114                    }
115
116                    if (!control.getOID().equals(OID)) {
117                        final LocalizableMessage message =
118                                ERR_PREREAD_CONTROL_BAD_OID.get(control.getOID(), OID);
119                        throw DecodeException.error(message);
120                    }
121
122                    if (!control.hasValue()) {
123                        // The control must always have a value.
124                        final LocalizableMessage message = ERR_PREREADREQ_NO_CONTROL_VALUE.get();
125                        throw DecodeException.error(message);
126                    }
127
128                    final ASN1Reader reader = ASN1.getReader(control.getValue());
129                    List<String> attributes;
130                    try {
131                        reader.readStartSequence();
132                        if (reader.hasNextElement()) {
133                            final String firstAttribute = reader.readOctetStringAsString();
134                            if (reader.hasNextElement()) {
135                                attributes = new ArrayList<>();
136                                attributes.add(firstAttribute);
137                                do {
138                                    attributes.add(reader.readOctetStringAsString());
139                                } while (reader.hasNextElement());
140                                attributes = unmodifiableList(attributes);
141                            } else {
142                                attributes = singletonList(firstAttribute);
143                            }
144                        } else {
145                            attributes = emptyList();
146                        }
147                        reader.readEndSequence();
148                    } catch (final Exception ex) {
149                        logger.debug(LocalizableMessage.raw("Unable to read sequence", ex));
150
151                        final LocalizableMessage message =
152                                ERR_PREREADREQ_CANNOT_DECODE_VALUE.get(ex.getMessage());
153                        throw DecodeException.error(message, ex);
154                    }
155
156                    if (attributes.isEmpty()) {
157                        return control.isCritical() ? CRITICAL_EMPTY_INSTANCE
158                                : NONCRITICAL_EMPTY_INSTANCE;
159                    } else {
160                        return new PreReadRequestControl(control.isCritical(), attributes);
161                    }
162                }
163
164                public String getOID() {
165                    return OID;
166                }
167            };
168
169    /**
170     * Creates a new pre-read request control.
171     *
172     * @param isCritical
173     *            {@code true} if it is unacceptable to perform the operation
174     *            without applying the semantics of this control, or
175     *            {@code false} if it can be ignored
176     * @param attributes
177     *            The list of attributes to be included with the response
178     *            control. Attributes that are sub-types of listed attributes
179     *            are implicitly included. The list may be empty, indicating
180     *            that all user attributes should be returned.
181     * @return The new control.
182     * @throws NullPointerException
183     *             If {@code attributes} was {@code null}.
184     */
185    public static PreReadRequestControl newControl(final boolean isCritical,
186            final Collection<String> attributes) {
187        Reject.ifNull(attributes);
188
189        if (attributes.isEmpty()) {
190            return isCritical ? CRITICAL_EMPTY_INSTANCE : NONCRITICAL_EMPTY_INSTANCE;
191        } else if (attributes.size() == 1) {
192            return new PreReadRequestControl(isCritical,
193                    singletonList(attributes.iterator().next()));
194        } else {
195            return new PreReadRequestControl(isCritical, unmodifiableList(new ArrayList<String>(
196                    attributes)));
197        }
198    }
199
200    /**
201     * Creates a new pre-read request control.
202     *
203     * @param isCritical
204     *            {@code true} if it is unacceptable to perform the operation
205     *            without applying the semantics of this control, or
206     *            {@code false} if it can be ignored
207     * @param attributes
208     *            The list of attributes to be included with the response
209     *            control. Attributes that are sub-types of listed attributes
210     *            are implicitly included. The list may be empty, indicating
211     *            that all user attributes should be returned.
212     * @return The new control.
213     * @throws NullPointerException
214     *             If {@code attributes} was {@code null}.
215     */
216    public static PreReadRequestControl newControl(final boolean isCritical,
217            final String... attributes) {
218        Reject.ifNull((Object) attributes);
219
220        if (attributes.length == 0) {
221            return isCritical ? CRITICAL_EMPTY_INSTANCE : NONCRITICAL_EMPTY_INSTANCE;
222        } else if (attributes.length == 1) {
223            return new PreReadRequestControl(isCritical, singletonList(attributes[0]));
224        } else {
225            return new PreReadRequestControl(isCritical, unmodifiableList(new ArrayList<String>(
226                    asList(attributes))));
227        }
228    }
229
230    private PreReadRequestControl(final boolean isCritical, final List<String> attributes) {
231        this.isCritical = isCritical;
232        this.attributes = attributes;
233    }
234
235    /**
236     * Returns an unmodifiable list containing the names of attributes to be
237     * included with the response control. Attributes that are sub-types of
238     * listed attributes are implicitly included. The returned list may be
239     * empty, indicating that all user attributes should be returned.
240     *
241     * @return An unmodifiable list containing the names of attributes to be
242     *         included with the response control.
243     */
244    public List<String> getAttributes() {
245        return attributes;
246    }
247
248    /** {@inheritDoc} */
249    public String getOID() {
250        return OID;
251    }
252
253    /** {@inheritDoc} */
254    public ByteString getValue() {
255        final ByteStringBuilder buffer = new ByteStringBuilder();
256        final ASN1Writer writer = ASN1.getWriter(buffer);
257        try {
258            writer.writeStartSequence();
259            if (attributes != null) {
260                for (final String attr : attributes) {
261                    writer.writeOctetString(attr);
262                }
263            }
264            writer.writeEndSequence();
265            return buffer.toByteString();
266        } catch (final IOException ioe) {
267            // This should never happen unless there is a bug somewhere.
268            throw new RuntimeException(ioe);
269        }
270    }
271
272    /** {@inheritDoc} */
273    public boolean hasValue() {
274        return true;
275    }
276
277    /** {@inheritDoc} */
278    public boolean isCritical() {
279        return isCritical;
280    }
281
282    /** {@inheritDoc} */
283    @Override
284    public String toString() {
285        final StringBuilder builder = new StringBuilder();
286        builder.append("PreReadRequestControl(oid=");
287        builder.append(getOID());
288        builder.append(", criticality=");
289        builder.append(isCritical());
290        builder.append(", attributes=");
291        builder.append(attributes);
292        builder.append(")");
293        return builder.toString();
294    }
295
296}