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