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-2010 Sun Microsystems, Inc.
025 *      Portions copyright 2012-2014 ForgeRock AS.
026 */
027package org.forgerock.opendj.ldap.controls;
028
029import static com.forgerock.opendj.ldap.CoreMessages.*;
030
031import java.io.IOException;
032
033import org.forgerock.i18n.LocalizableMessage;
034import org.forgerock.i18n.slf4j.LocalizedLogger;
035import org.forgerock.opendj.io.ASN1;
036import org.forgerock.opendj.io.ASN1Reader;
037import org.forgerock.opendj.io.LDAP;
038import org.forgerock.opendj.ldap.ByteString;
039import org.forgerock.opendj.ldap.ByteStringBuilder;
040import org.forgerock.opendj.ldap.DecodeException;
041import org.forgerock.opendj.ldap.DecodeOptions;
042import org.forgerock.opendj.ldap.Entries;
043import org.forgerock.opendj.ldap.Entry;
044import org.forgerock.util.Reject;
045
046/**
047 * The post-read response control as defined in RFC 4527. This control is
048 * returned by the server in response to a successful update operation which
049 * included a post-read request control. The control contains a Search Result
050 * Entry containing, subject to access controls and other constraints, values of
051 * the requested attributes.
052 * <p>
053 * The following example gets a modified entry from the result of a modify
054 * operation.
055 *
056 * <pre>
057 * Connection connection = ...;
058 * String DN = ...;
059 *
060 * ModifyRequest request =
061 *         Requests.newModifyRequest(DN)
062 *         .addControl(PostReadRequestControl.newControl(true, "description"))
063 *         .addModification(ModificationType.REPLACE,
064 *                 "description", "Using the PostReadRequestControl");
065 *
066 * Result result = connection.modify(request);
067 * PostReadResponseControl control =
068 *         result.getControl(PostReadResponseControl.DECODER,
069 *                 new DecodeOptions());
070 * Entry modifiedEntry = control.getEntry();
071 * </pre>
072 *
073 * @see PostReadRequestControl
074 * @see <a href="http://tools.ietf.org/html/rfc4527">RFC 4527 - Lightweight
075 *      Directory Access Protocol (LDAP) Read Entry Controls </a>
076 */
077public final class PostReadResponseControl implements Control {
078
079    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
080    /**
081     * The IANA-assigned OID for the LDAP post-read response control used for
082     * retrieving an entry in the state it had immediately after an update was
083     * applied.
084     */
085    public static final String OID = PostReadRequestControl.OID;
086
087    /**
088     * A decoder which can be used for decoding the post-read response control.
089     */
090    public static final ControlDecoder<PostReadResponseControl> DECODER =
091            new ControlDecoder<PostReadResponseControl>() {
092
093                public PostReadResponseControl decodeControl(final Control control,
094                        final DecodeOptions options) throws DecodeException {
095                    Reject.ifNull(control);
096
097                    if (control instanceof PostReadResponseControl) {
098                        return (PostReadResponseControl) control;
099                    }
100
101                    if (!control.getOID().equals(OID)) {
102                        final LocalizableMessage message =
103                                ERR_POSTREAD_CONTROL_BAD_OID.get(control.getOID(), OID);
104                        throw DecodeException.error(message);
105                    }
106
107                    if (!control.hasValue()) {
108                        // The control must always have a value.
109                        final LocalizableMessage message = ERR_POSTREADRESP_NO_CONTROL_VALUE.get();
110                        throw DecodeException.error(message);
111                    }
112
113                    final ASN1Reader reader = ASN1.getReader(control.getValue());
114                    final Entry entry;
115                    try {
116                        entry = LDAP.readEntry(reader, options);
117                    } catch (final IOException le) {
118                        logger.debug(LocalizableMessage.raw("Unable to read result entry ", le));
119                        final LocalizableMessage message =
120                                ERR_POSTREADRESP_CANNOT_DECODE_VALUE.get(le.getMessage());
121                        throw DecodeException.error(message, le);
122                    }
123
124                    /*
125                     * FIXME: the RFC states that the control contains a
126                     * SearchResultEntry rather than an Entry. Can we assume
127                     * that the response will not contain a nested set of
128                     * controls?
129                     */
130                    return new PostReadResponseControl(control.isCritical(), Entries
131                            .unmodifiableEntry(entry));
132                }
133
134                public String getOID() {
135                    return OID;
136                }
137            };
138
139    /**
140     * Creates a new post-read response control.
141     *
142     * @param entry
143     *            The entry whose contents reflect the state of the updated
144     *            entry immediately after the update operation was performed.
145     * @return The new control.
146     * @throws NullPointerException
147     *             If {@code entry} was {@code null}.
148     */
149    public static PostReadResponseControl newControl(final Entry entry) {
150        /*
151         * FIXME: all other control implementations are fully immutable. We
152         * should really do a defensive copy here in order to be consistent,
153         * rather than just wrap it. Also, the RFC states that the control
154         * contains a SearchResultEntry rather than an Entry. Can we assume that
155         * the response will not contain a nested set of controls?
156         */
157        return new PostReadResponseControl(false, Entries.unmodifiableEntry(entry));
158    }
159
160    private final Entry entry;
161
162    private final boolean isCritical;
163
164    private PostReadResponseControl(final boolean isCritical, final Entry entry) {
165        this.isCritical = isCritical;
166        this.entry = entry;
167    }
168
169    /**
170     * Returns an unmodifiable entry whose contents reflect the state of the
171     * updated entry immediately after the update operation was performed.
172     *
173     * @return The unmodifiable entry whose contents reflect the state of the
174     *         updated entry immediately after the update operation was
175     *         performed.
176     */
177    public Entry getEntry() {
178        return entry;
179    }
180
181    /** {@inheritDoc} */
182    public String getOID() {
183        return OID;
184    }
185
186    /** {@inheritDoc} */
187    public ByteString getValue() {
188        try {
189            final ByteStringBuilder buffer = new ByteStringBuilder();
190            LDAP.writeEntry(ASN1.getWriter(buffer), entry);
191            return buffer.toByteString();
192        } catch (final IOException ioe) {
193            // This should never happen unless there is a bug somewhere.
194            throw new RuntimeException(ioe);
195        }
196    }
197
198    /** {@inheritDoc} */
199    public boolean hasValue() {
200        return true;
201    }
202
203    /** {@inheritDoc} */
204    public boolean isCritical() {
205        return isCritical;
206    }
207
208    /** {@inheritDoc} */
209    @Override
210    public String toString() {
211        final StringBuilder builder = new StringBuilder();
212        builder.append("PostReadResponseControl(oid=");
213        builder.append(getOID());
214        builder.append(", criticality=");
215        builder.append(isCritical());
216        builder.append(", entry=");
217        builder.append(entry);
218        builder.append(")");
219        return builder.toString();
220    }
221}