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