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}