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