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 2010 Sun Microsystems, Inc.
025 *      Portions copyright 2011-2014 ForgeRock AS
026 */
027package org.forgerock.opendj.ldap.controls;
028
029import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage;
030import static com.forgerock.opendj.ldap.CoreMessages.ERR_SUBENTRIES_CANNOT_DECODE_VALUE;
031import static com.forgerock.opendj.ldap.CoreMessages.ERR_SUBENTRIES_CONTROL_BAD_OID;
032import static com.forgerock.opendj.ldap.CoreMessages.ERR_SUBENTRIES_NO_CONTROL_VALUE;
033
034import java.io.IOException;
035
036import org.forgerock.i18n.LocalizableMessage;
037import org.forgerock.i18n.slf4j.LocalizedLogger;
038import org.forgerock.opendj.io.ASN1;
039import org.forgerock.opendj.io.ASN1Reader;
040import org.forgerock.opendj.io.ASN1Writer;
041import org.forgerock.opendj.ldap.ByteString;
042import org.forgerock.opendj.ldap.ByteStringBuilder;
043import org.forgerock.opendj.ldap.DecodeException;
044import org.forgerock.opendj.ldap.DecodeOptions;
045import org.forgerock.util.Reject;
046
047/**
048 * The sub-entries request control as defined in RFC 3672. This control may be
049 * included in a search request to indicate that sub-entries should be included
050 * in the search results.
051 * <p>
052 * In the absence of the sub-entries request control, sub-entries are not
053 * visible to search operations unless the target/base of the operation is a
054 * sub-entry. In the presence of the sub-entry request control, sub-entries are
055 * visible if and only if the control's value is {@code TRUE}.
056 * <p>
057 * Consider "Class of Service" sub-entries such as the following:
058 *
059 * <pre>
060 * dn: cn=Gold Class of Service,dc=example,dc=com
061 * objectClass: collectiveAttributeSubentry
062 * objectClass: extensibleObject
063 * objectClass: subentry
064 * objectClass: top
065 * cn: Gold Class of Service
066 * diskQuota;collective: 100 GB
067 * mailQuota;collective: 10 GB
068 * subtreeSpecification: { base "ou=People", specificationFilter "(classOfService=
069 *  gold)" }
070 * </pre>
071 *
072 * To access the sub-entries in your search, use the control with value
073 * {@code TRUE}.
074 *
075 * <pre>
076 * Connection connection = ...;
077 *
078 * SearchRequest request = Requests.newSearchRequest("dc=example,dc=com",
079 *         SearchScope.WHOLE_SUBTREE, "cn=*Class of Service", "cn", "subtreeSpecification")
080 *         .addControl(SubentriesRequestControl.newControl(true, true));
081 * ?
082 * ConnectionEntryReader reader = connection.search(request);
083 * while (reader.hasNext()) {
084 *     if (reader.isEntry()) {
085 *         SearchResultEntry entry = reader.readEntry();
086 *         // ...
087 *     }
088 * }
089 * </pre>
090 *
091 * @see <a href="http://tools.ietf.org/html/rfc3672">RFC 3672 - Subentries in
092 *      the Lightweight Directory Access Protocol </a>
093 */
094public final class SubentriesRequestControl implements Control {
095
096    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
097    /**
098     * The OID for the sub-entries request control.
099     */
100    public static final String OID = "1.3.6.1.4.1.4203.1.10.1";
101
102    private static final SubentriesRequestControl CRITICAL_VISIBLE_INSTANCE =
103            new SubentriesRequestControl(true, true);
104    private static final SubentriesRequestControl NONCRITICAL_VISIBLE_INSTANCE =
105            new SubentriesRequestControl(false, true);
106    private static final SubentriesRequestControl CRITICAL_INVISIBLE_INSTANCE =
107            new SubentriesRequestControl(true, false);
108    private static final SubentriesRequestControl NONCRITICAL_INVISIBLE_INSTANCE =
109            new SubentriesRequestControl(false, false);
110
111    /**
112     * A decoder which can be used for decoding the sub-entries request control.
113     */
114    public static final ControlDecoder<SubentriesRequestControl> DECODER =
115            new ControlDecoder<SubentriesRequestControl>() {
116
117                public SubentriesRequestControl decodeControl(final Control control,
118                        final DecodeOptions options) throws DecodeException {
119                    Reject.ifNull(control);
120
121                    if (control instanceof SubentriesRequestControl) {
122                        return (SubentriesRequestControl) control;
123                    }
124
125                    if (!control.getOID().equals(OID)) {
126                        final LocalizableMessage message =
127                                ERR_SUBENTRIES_CONTROL_BAD_OID.get(control.getOID(), OID);
128                        throw DecodeException.error(message);
129                    }
130
131                    if (!control.hasValue()) {
132                        // The response control must always have a value.
133                        final LocalizableMessage message = ERR_SUBENTRIES_NO_CONTROL_VALUE.get();
134                        throw DecodeException.error(message);
135                    }
136
137                    final ASN1Reader reader = ASN1.getReader(control.getValue());
138                    final boolean visibility;
139                    try {
140                        visibility = reader.readBoolean();
141                    } catch (final IOException e) {
142                        logger.debug(LocalizableMessage.raw("Unable to read visbility", e));
143                        final LocalizableMessage message =
144                                ERR_SUBENTRIES_CANNOT_DECODE_VALUE.get(getExceptionMessage(e));
145                        throw DecodeException.error(message);
146                    }
147
148                    return newControl(control.isCritical(), visibility);
149                }
150
151                public String getOID() {
152                    return OID;
153                }
154            };
155
156    /**
157     * Creates a new sub-entries request control having the provided criticality
158     * and sub-entry visibility.
159     *
160     * @param isCritical
161     *            {@code true} if it is unacceptable to perform the operation
162     *            without applying the semantics of this control, or
163     *            {@code false} if it can be ignored.
164     * @param visibility
165     *            {@code true} if sub-entries should be included in the search
166     *            results and normal entries excluded, or {@code false} if
167     *            normal entries should be included and sub-entries excluded.
168     * @return The new control.
169     */
170    public static SubentriesRequestControl newControl(final boolean isCritical,
171            final boolean visibility) {
172        if (isCritical) {
173            return visibility ? CRITICAL_VISIBLE_INSTANCE : CRITICAL_INVISIBLE_INSTANCE;
174        } else {
175            return visibility ? NONCRITICAL_VISIBLE_INSTANCE : NONCRITICAL_INVISIBLE_INSTANCE;
176        }
177    }
178
179    private final boolean isCritical;
180    private final boolean visibility;
181
182    private SubentriesRequestControl(final boolean isCritical, final boolean visibility) {
183        this.isCritical = isCritical;
184        this.visibility = visibility;
185    }
186
187    /** {@inheritDoc} */
188    public String getOID() {
189        return OID;
190    }
191
192    /** {@inheritDoc} */
193    public ByteString getValue() {
194        final ByteStringBuilder buffer = new ByteStringBuilder();
195        final ASN1Writer writer = ASN1.getWriter(buffer);
196        try {
197            writer.writeBoolean(visibility);
198            return buffer.toByteString();
199        } catch (final IOException ioe) {
200            // This should never happen unless there is a bug somewhere.
201            throw new RuntimeException(ioe);
202        }
203    }
204
205    /**
206     * Returns a boolean indicating whether or not sub-entries should be
207     * included in the search results.
208     *
209     * @return {@code true} if sub-entries should be included in the search
210     *         results and normal entries excluded, or {@code false} if normal
211     *         entries should be included and sub-entries excluded.
212     */
213    public boolean getVisibility() {
214        return visibility;
215    }
216
217    /** {@inheritDoc} */
218    public boolean hasValue() {
219        return false;
220    }
221
222    /** {@inheritDoc} */
223    public boolean isCritical() {
224        return isCritical;
225    }
226
227    /** {@inheritDoc} */
228    @Override
229    public String toString() {
230        final StringBuilder builder = new StringBuilder();
231        builder.append("SubentriesRequestControl(oid=");
232        builder.append(getOID());
233        builder.append(", criticality=");
234        builder.append(isCritical());
235        builder.append(", visibility=");
236        builder.append(getVisibility());
237        builder.append(")");
238        return builder.toString();
239    }
240
241}