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