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 2012-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_VLVRES_CONTROL_BAD_OID; 031import static com.forgerock.opendj.ldap.CoreMessages.INFO_VLVRES_CONTROL_CANNOT_DECODE_VALUE; 032import static com.forgerock.opendj.ldap.CoreMessages.INFO_VLVRES_CONTROL_NO_VALUE; 033 034import java.io.IOException; 035 036import org.forgerock.i18n.LocalizableMessage; 037import org.forgerock.opendj.io.ASN1; 038import org.forgerock.opendj.io.ASN1Reader; 039import org.forgerock.opendj.io.ASN1Writer; 040import org.forgerock.opendj.ldap.ByteString; 041import org.forgerock.opendj.ldap.ByteStringBuilder; 042import org.forgerock.opendj.ldap.DecodeException; 043import org.forgerock.opendj.ldap.DecodeOptions; 044import org.forgerock.opendj.ldap.ResultCode; 045import org.forgerock.util.Reject; 046 047/** 048 * The virtual list view response control as defined in 049 * draft-ietf-ldapext-ldapv3-vlv. This control is included with a search result 050 * in response to a virtual list view request included with a search request. 051 * <p> 052 * If the result code included with this control indicates that the virtual list 053 * view request succeeded then the content count and target position give 054 * sufficient information for the client to update a list box slider position to 055 * match the newly retrieved entries and identify the target entry. 056 * <p> 057 * The content count and context ID should be used in a subsequent virtual list 058 * view requests. 059 * <p> 060 * The following example demonstrates use of the virtual list view controls. 061 * 062 * <pre> 063 * ByteString contextID = ByteString.empty(); 064 * 065 * // Add a window of 2 entries on either side of the first sn=Jensen entry. 066 * SearchRequest request = Requests.newSearchRequest("ou=People,dc=example,dc=com", 067 * SearchScope.WHOLE_SUBTREE, "(sn=*)", "sn", "givenName") 068 * .addControl(ServerSideSortRequestControl.newControl(true, new SortKey("sn"))) 069 * .addControl(VirtualListViewRequestControl.newAssertionControl( 070 * true, ByteString.valueOf("Jensen"), 2, 2, contextID)); 071 * 072 * SearchResultHandler resultHandler = new MySearchResultHandler(); 073 * Result result = connection.search(request, resultHandler); 074 * 075 * ServerSideSortResponseControl sssControl = 076 * result.getControl(ServerSideSortResponseControl.DECODER, new DecodeOptions()); 077 * if (sssControl != null && sssControl.getResult() == ResultCode.SUCCESS) { 078 * // Entries are sorted. 079 * } else { 080 * // Entries not necessarily sorted 081 * } 082 * 083 * VirtualListViewResponseControl vlvControl = 084 * result.getControl(VirtualListViewResponseControl.DECODER, new DecodeOptions()); 085 * // Position in list: vlvControl.getTargetPosition()/vlvControl.getContentCount() 086 * </pre> 087 * 088 * The search result handler in this case displays pages of results as LDIF on 089 * standard out. 090 * 091 * <pre> 092 * private static class MySearchResultHandler implements SearchResultHandler { 093 * 094 * {@literal @}Override 095 * public void handleExceptionResult(LdapException error) { 096 * // Ignore. 097 * } 098 * 099 * {@literal @}Override 100 * public void handleResult(Result result) { 101 * // Ignore. 102 * } 103 * 104 * {@literal @}Override 105 * public boolean handleEntry(SearchResultEntry entry) { 106 * final LDIFEntryWriter writer = new LDIFEntryWriter(System.out); 107 * try { 108 * writer.writeEntry(entry); 109 * writer.flush(); 110 * } catch (final IOException e) { 111 * // The writer could not write to System.out. 112 * } 113 * return true; 114 * } 115 * 116 * {@literal @}Override 117 * public boolean handleReference(SearchResultReference reference) { 118 * System.out.println("Got a reference: " + reference.toString()); 119 * return false; 120 * } 121 * } 122 * </pre> 123 * 124 * @see VirtualListViewRequestControl 125 * @see <a href="http://tools.ietf.org/html/draft-ietf-ldapext-ldapv3-vlv"> 126 * draft-ietf-ldapext-ldapv3-vlv - LDAP Extensions for Scrolling View 127 * Browsing of Search Results </a> 128 */ 129public final class VirtualListViewResponseControl implements Control { 130 /** 131 * The OID for the virtual list view request control. 132 */ 133 public static final String OID = "2.16.840.1.113730.3.4.10"; 134 135 /** 136 * A decoder which can be used for decoding the virtual list view response 137 * control. 138 */ 139 public static final ControlDecoder<VirtualListViewResponseControl> DECODER = 140 new ControlDecoder<VirtualListViewResponseControl>() { 141 142 public VirtualListViewResponseControl decodeControl(final Control control, 143 final DecodeOptions options) throws DecodeException { 144 Reject.ifNull(control); 145 146 if (control instanceof VirtualListViewResponseControl) { 147 return (VirtualListViewResponseControl) control; 148 } 149 150 if (!control.getOID().equals(OID)) { 151 final LocalizableMessage message = 152 ERR_VLVRES_CONTROL_BAD_OID.get(control.getOID(), OID); 153 throw DecodeException.error(message); 154 } 155 156 if (!control.hasValue()) { 157 // The response control must always have a value. 158 final LocalizableMessage message = INFO_VLVRES_CONTROL_NO_VALUE.get(); 159 throw DecodeException.error(message); 160 } 161 162 final ASN1Reader reader = ASN1.getReader(control.getValue()); 163 try { 164 reader.readStartSequence(); 165 166 final int targetPosition = (int) reader.readInteger(); 167 final int contentCount = (int) reader.readInteger(); 168 final ResultCode result = ResultCode.valueOf(reader.readEnumerated()); 169 ByteString contextID = null; 170 if (reader.hasNextElement()) { 171 contextID = reader.readOctetString(); 172 } 173 174 return new VirtualListViewResponseControl(control.isCritical(), 175 targetPosition, contentCount, result, contextID); 176 } catch (final IOException e) { 177 final LocalizableMessage message = 178 INFO_VLVRES_CONTROL_CANNOT_DECODE_VALUE.get(getExceptionMessage(e)); 179 throw DecodeException.error(message, e); 180 } 181 } 182 183 public String getOID() { 184 return OID; 185 } 186 }; 187 188 /** 189 * Creates a new virtual list view response control. 190 * 191 * @param targetPosition 192 * The position of the target entry in the result set. 193 * @param contentCount 194 * An estimate of the total number of entries in the result set. 195 * @param result 196 * The result code indicating the outcome of the virtual list 197 * view request. 198 * @param contextID 199 * A server-defined octet string. If present, the contextID 200 * should be sent back to the server by the client in a 201 * subsequent virtual list request. 202 * @return The new control. 203 * @throws IllegalArgumentException 204 * If {@code targetPosition} or {@code contentCount} were less 205 * than {@code 0}. 206 * @throws NullPointerException 207 * If {@code result} was {@code null}. 208 */ 209 public static VirtualListViewResponseControl newControl(final int targetPosition, 210 final int contentCount, final ResultCode result, final ByteString contextID) { 211 Reject.ifNull(result); 212 Reject.ifFalse(targetPosition >= 0, "targetPosition is less than 0"); 213 Reject.ifFalse(contentCount >= 0, "contentCount is less than 0"); 214 215 return new VirtualListViewResponseControl(false, targetPosition, contentCount, result, 216 contextID); 217 } 218 219 private final int targetPosition; 220 221 private final int contentCount; 222 223 private final ResultCode result; 224 225 private final ByteString contextID; 226 227 private final boolean isCritical; 228 229 private VirtualListViewResponseControl(final boolean isCritical, final int targetPosition, 230 final int contentCount, final ResultCode result, final ByteString contextID) { 231 this.isCritical = isCritical; 232 this.targetPosition = targetPosition; 233 this.contentCount = contentCount; 234 this.result = result; 235 this.contextID = contextID; 236 } 237 238 /** 239 * Returns the estimated total number of entries in the result set. 240 * 241 * @return The estimated total number of entries in the result set. 242 */ 243 public int getContentCount() { 244 return contentCount; 245 } 246 247 /** 248 * Returns a server-defined octet string which, if present, should be sent 249 * back to the server by the client in a subsequent virtual list request. 250 * 251 * @return A server-defined octet string which, if present, should be sent 252 * back to the server by the client in a subsequent virtual list 253 * request, or {@code null} if there is no context ID. 254 */ 255 public ByteString getContextID() { 256 return contextID; 257 } 258 259 /** {@inheritDoc} */ 260 public String getOID() { 261 return OID; 262 } 263 264 /** 265 * Returns result code indicating the outcome of the virtual list view 266 * request. 267 * 268 * @return The result code indicating the outcome of the virtual list view 269 * request. 270 */ 271 public ResultCode getResult() { 272 return result; 273 } 274 275 /** 276 * Returns the position of the target entry in the result set. 277 * 278 * @return The position of the target entry in the result set. 279 */ 280 public int getTargetPosition() { 281 return targetPosition; 282 } 283 284 /** {@inheritDoc} */ 285 public ByteString getValue() { 286 final ByteStringBuilder buffer = new ByteStringBuilder(); 287 final ASN1Writer writer = ASN1.getWriter(buffer); 288 try { 289 writer.writeStartSequence(); 290 writer.writeInteger(targetPosition); 291 writer.writeInteger(contentCount); 292 writer.writeEnumerated(result.intValue()); 293 if (contextID != null) { 294 writer.writeOctetString(contextID); 295 } 296 writer.writeEndSequence(); 297 return buffer.toByteString(); 298 } catch (final IOException ioe) { 299 // This should never happen unless there is a bug somewhere. 300 throw new RuntimeException(ioe); 301 } 302 } 303 304 /** {@inheritDoc} */ 305 public boolean hasValue() { 306 return true; 307 } 308 309 /** {@inheritDoc} */ 310 public boolean isCritical() { 311 return isCritical; 312 } 313 314 /** {@inheritDoc} */ 315 @Override 316 public String toString() { 317 final StringBuilder builder = new StringBuilder(); 318 builder.append("VirtualListViewResponseControl(oid="); 319 builder.append(getOID()); 320 builder.append(", criticality="); 321 builder.append(isCritical()); 322 builder.append(", targetPosition="); 323 builder.append(targetPosition); 324 builder.append(", contentCount="); 325 builder.append(contentCount); 326 builder.append(", result="); 327 builder.append(result); 328 if (contextID != null) { 329 builder.append(", contextID="); 330 builder.append(contextID); 331 } 332 builder.append(")"); 333 return builder.toString(); 334 } 335 336}