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