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