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