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 */
017
018package org.forgerock.opendj.ldap.controls;
019
020import static com.forgerock.opendj.ldap.CoreMessages.ERR_LDAPASSERT_CONTROL_BAD_OID;
021import static com.forgerock.opendj.ldap.CoreMessages.ERR_LDAPASSERT_INVALID_CONTROL_VALUE;
022import static com.forgerock.opendj.ldap.CoreMessages.ERR_LDAPASSERT_NO_CONTROL_VALUE;
023import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage;
024
025import java.io.IOException;
026
027import org.forgerock.i18n.LocalizableMessage;
028import org.forgerock.opendj.io.ASN1;
029import org.forgerock.opendj.io.ASN1Reader;
030import org.forgerock.opendj.io.ASN1Writer;
031import org.forgerock.opendj.io.LDAP;
032import org.forgerock.opendj.ldap.ByteString;
033import org.forgerock.opendj.ldap.ByteStringBuilder;
034import org.forgerock.opendj.ldap.DecodeException;
035import org.forgerock.opendj.ldap.DecodeOptions;
036import org.forgerock.opendj.ldap.Filter;
037
038import org.forgerock.util.Reject;
039
040/**
041 * The assertion request control as defined in RFC 4528. The Assertion control
042 * allows a client to specify that a directory operation should only be
043 * processed if an assertion applied to the target entry of the operation is
044 * true. It can be used to construct "test and set", "test and clear", and other
045 * conditional operations.
046 * <p>
047 * The following excerpt shows how to check that no description exists on an
048 * entry before adding a description.
049 *
050 * <pre>
051 * Connection connection = ...;
052 * connection.bind(...);
053 *
054 * String entryDN = ...;
055 * ModifyRequest request =
056 *         Requests.newModifyRequest(entryDN)
057 *             .addControl(AssertionRequestControl.newControl(
058 *                     true, Filter.valueOf("!(description=*)")))
059 *             .addModification(ModificationType.ADD, "description",
060 *                     "Created using LDAP assertion control");
061 *
062 * connection.modify(request);
063 * ...
064 * </pre>
065 *
066 * @see <a href="http://tools.ietf.org/html/rfc4528">RFC 4528 - Lightweight
067 *      Directory Access Protocol (LDAP) Assertion Control </a>
068 */
069public final class AssertionRequestControl implements Control {
070    /** The IANA-assigned OID for the LDAP assertion request control. */
071    public static final String OID = "1.3.6.1.1.12";
072
073    /** A decoder which can be used for decoding the LDAP assertion request control. */
074    public static final ControlDecoder<AssertionRequestControl> DECODER =
075            new ControlDecoder<AssertionRequestControl>() {
076
077                @Override
078                public AssertionRequestControl decodeControl(final Control control,
079                        final DecodeOptions options) throws DecodeException {
080                    Reject.ifNull(control);
081
082                    if (control instanceof AssertionRequestControl) {
083                        return (AssertionRequestControl) control;
084                    }
085
086                    if (!control.getOID().equals(OID)) {
087                        final LocalizableMessage message =
088                                ERR_LDAPASSERT_CONTROL_BAD_OID.get(control.getOID(), OID);
089                        throw DecodeException.error(message);
090                    }
091
092                    if (!control.hasValue()) {
093                        // The response control must always have a value.
094                        final LocalizableMessage message = ERR_LDAPASSERT_NO_CONTROL_VALUE.get();
095                        throw DecodeException.error(message);
096                    }
097
098                    try {
099                        final ASN1Reader reader = ASN1.getReader(control.getValue());
100                        final Filter filter = LDAP.readFilter(reader);
101                        return new AssertionRequestControl(control.isCritical(), filter);
102                    } catch (final IOException e) {
103                        throw DecodeException.error(ERR_LDAPASSERT_INVALID_CONTROL_VALUE
104                                .get(getExceptionMessage(e)), e);
105                    }
106                }
107
108                @Override
109                public String getOID() {
110                    return OID;
111                }
112            };
113
114    /**
115     * Creates a new assertion using the provided criticality and assertion
116     * filter.
117     *
118     * @param isCritical
119     *            {@code true} if it is unacceptable to perform the operation
120     *            without applying the semantics of this control, or
121     *            {@code false} if it can be ignored.
122     * @param filter
123     *            The assertion filter.
124     * @return The new control.
125     * @throws NullPointerException
126     *             If {@code filter} was {@code null}.
127     */
128    public static AssertionRequestControl newControl(final boolean isCritical, final Filter filter) {
129        return new AssertionRequestControl(isCritical, filter);
130    }
131
132    /** The assertion filter. */
133    private final Filter filter;
134
135    private final boolean isCritical;
136
137    /** Prevent direct instantiation. */
138    private AssertionRequestControl(final boolean isCritical, final Filter filter) {
139        Reject.ifNull(filter);
140        this.isCritical = isCritical;
141        this.filter = filter;
142    }
143
144    /**
145     * Returns the assertion filter.
146     *
147     * @return The assertion filter.
148     */
149    public Filter getFilter() {
150        return filter;
151    }
152
153    @Override
154    public String getOID() {
155        return OID;
156    }
157
158    @Override
159    public ByteString getValue() {
160        final ByteStringBuilder buffer = new ByteStringBuilder();
161        final ASN1Writer writer = ASN1.getWriter(buffer);
162        try {
163            LDAP.writeFilter(writer, filter);
164            return buffer.toByteString();
165        } catch (final IOException ioe) {
166            // This should never happen unless there is a bug somewhere.
167            throw new RuntimeException(ioe);
168        }
169    }
170
171    @Override
172    public boolean hasValue() {
173        return true;
174    }
175
176    @Override
177    public boolean isCritical() {
178        return isCritical;
179    }
180
181    @Override
182    public String toString() {
183        final StringBuilder builder = new StringBuilder();
184        builder.append("AssertionRequestControl(oid=");
185        builder.append(getOID());
186        builder.append(", criticality=");
187        builder.append(isCritical());
188        builder.append(", filter=\"");
189        builder.append(filter);
190        builder.append("\")");
191        return builder.toString();
192    }
193}