001/* 002 * Copyright 2007-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2020 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2008-2020 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.controls; 037 038 039 040import com.unboundid.asn1.ASN1Element; 041import com.unboundid.asn1.ASN1OctetString; 042import com.unboundid.asn1.ASN1Sequence; 043import com.unboundid.ldap.sdk.Control; 044import com.unboundid.ldap.sdk.LDAPException; 045import com.unboundid.ldap.sdk.ResultCode; 046import com.unboundid.util.Debug; 047import com.unboundid.util.NotMutable; 048import com.unboundid.util.StaticUtils; 049import com.unboundid.util.ThreadSafety; 050import com.unboundid.util.ThreadSafetyLevel; 051 052import static com.unboundid.ldap.sdk.controls.ControlMessages.*; 053 054 055 056/** 057 * This class provides an implementation of the LDAP post-read request control 058 * as defined in <A HREF="http://www.ietf.org/rfc/rfc4527.txt">RFC 4527</A>. It 059 * may be used to request that the server retrieve a copy of the target entry as 060 * it appeared immediately after processing an add, modify, or modify DN 061 * operation. 062 * <BR><BR> 063 * If this control is included in an add, modify, or modify DN request, then the 064 * corresponding response may include a {@link PostReadResponseControl} 065 * containing a version of the entry as it appeared after applying that change. 066 * Note that this response control will only be included if the operation was 067 * successful, so it will not be provided if the operation failed for some 068 * reason (e.g., if the change would have violated the server schema, or if the 069 * requester did not have sufficient permission to perform that operation). 070 * <BR><BR> 071 * The value of this control should contain a set of requested attributes to 072 * include in the entry that is returned. The server should treat this set of 073 * requested attributes exactly as it treats the requested attributes from a 074 * {@link com.unboundid.ldap.sdk.SearchRequest}. As is the case with a search 075 * request, if no attributes are specified, then all user attributes will be 076 * included. 077 * <BR><BR> 078 * <H2>Example</H2> 079 * The following example demonstrates the use of the pre-read and post-read 080 * controls. It will modify an entry to increment the value of the 081 * {@code test-counter} attribute by one, and will use the pre-read and 082 * post-read controls to determine what the previous and updated values are: 083 * <PRE> 084 * // Create a modify request that we can use to increment the value of a 085 * // custom attribute named "test-counter". 086 * ModifyRequest modifyRequest = new ModifyRequest( 087 * "uid=test.user,ou=People,dc=example,dc=com", 088 * new Modification(ModificationType.INCREMENT, 089 * "test-counter", // The attribute to increment. 090 * "1")); // The amount by which to increment the value. 091 * 092 * // Update the modify request to add both pre-read and post-read request 093 * // controls to see what the entry value was before and after the change. 094 * // We only care about getting the test-counter attribute. 095 * modifyRequest.setControls( 096 * new PreReadRequestControl("test-counter"), 097 * new PostReadRequestControl("test-counter")); 098 * 099 * // Process the modify operation in the server. 100 * LDAPResult modifyResult; 101 * try 102 * { 103 * modifyResult = connection.modify(modifyRequest); 104 * // If we got here, then the modification should have been successful. 105 * } 106 * catch (LDAPException le) 107 * { 108 * // This indicates that the operation did not complete successfully. 109 * modifyResult = le.toLDAPResult(); 110 * ResultCode resultCode = le.getResultCode(); 111 * String errorMessageFromServer = le.getDiagnosticMessage(); 112 * } 113 * LDAPTestUtils.assertResultCodeEquals(modifyResult, ResultCode.SUCCESS); 114 * 115 * // Get the pre-read and post-read response controls from the server and 116 * // retrieve the before and after values for the test-counter attribute. 117 * LDAPTestUtils.assertHasControl(modifyResult, 118 * PreReadResponseControl.PRE_READ_RESPONSE_OID); 119 * PreReadResponseControl preReadResponse = 120 * PreReadResponseControl.get(modifyResult); 121 * Integer beforeValue = 122 * preReadResponse.getEntry().getAttributeValueAsInteger("test-counter"); 123 * 124 * LDAPTestUtils.assertHasControl(modifyResult, 125 * PostReadResponseControl.POST_READ_RESPONSE_OID); 126 * PostReadResponseControl postReadResponse = 127 * PostReadResponseControl.get(modifyResult); 128 * Integer afterValue = 129 * postReadResponse.getEntry().getAttributeValueAsInteger("test-counter"); 130 * </PRE> 131 */ 132@NotMutable() 133@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 134public final class PostReadRequestControl 135 extends Control 136{ 137 /** 138 * The OID (1.3.6.1.1.13.2) for the post-read request control. 139 */ 140 public static final String POST_READ_REQUEST_OID = "1.3.6.1.1.13.2"; 141 142 143 144 /** 145 * The set of requested attributes that will be used if none are provided. 146 */ 147 private static final String[] NO_ATTRIBUTES = StaticUtils.NO_STRINGS; 148 149 150 151 /** 152 * The serial version UID for this serializable class. 153 */ 154 private static final long serialVersionUID = -4210061989410209462L; 155 156 157 158 // The set of requested attributes to retrieve from the target entry. 159 private final String[] attributes; 160 161 162 163 /** 164 * Creates a new post-read request control that will retrieve the specified 165 * set of attributes from the target entry. It will be marked critical. 166 * 167 * @param attributes The set of attributes to retrieve from the target 168 * entry. It behaves in the same way as the set of 169 * requested attributes for a search operation. If this 170 * is empty or {@code null}, then all user attributes 171 * will be returned. 172 */ 173 public PostReadRequestControl(final String... attributes) 174 { 175 this(true, attributes); 176 } 177 178 179 180 /** 181 * Creates a new post-read request control that will retrieve the specified 182 * set of attributes from the target entry. 183 * 184 * @param isCritical Indicates whether this control should be marked 185 * critical. 186 * @param attributes The set of attributes to retrieve from the target 187 * entry. It behaves in the same way as the set of 188 * requested attributes for a search operation. If this 189 * is empty or {@code null}, then all user attributes 190 * will be returned. 191 */ 192 public PostReadRequestControl(final boolean isCritical, 193 final String... attributes) 194 { 195 super(POST_READ_REQUEST_OID, isCritical, encodeValue(attributes)); 196 197 if (attributes == null) 198 { 199 this.attributes = NO_ATTRIBUTES; 200 } 201 else 202 { 203 this.attributes = attributes; 204 } 205 } 206 207 208 209 /** 210 * Creates a new post-read request control which is decoded from the provided 211 * generic control. 212 * 213 * @param control The generic control to be decoded as a post-read request 214 * control. 215 * 216 * @throws LDAPException If the provided control cannot be decoded as a 217 * post-read request control. 218 */ 219 public PostReadRequestControl(final Control control) 220 throws LDAPException 221 { 222 super(control); 223 224 final ASN1OctetString value = control.getValue(); 225 if (value == null) 226 { 227 throw new LDAPException(ResultCode.DECODING_ERROR, 228 ERR_POST_READ_REQUEST_NO_VALUE.get()); 229 } 230 231 try 232 { 233 final ASN1Element valueElement = ASN1Element.decode(value.getValue()); 234 final ASN1Element[] attrElements = 235 ASN1Sequence.decodeAsSequence(valueElement).elements(); 236 attributes = new String[attrElements.length]; 237 for (int i=0; i < attrElements.length; i++) 238 { 239 attributes[i] = 240 ASN1OctetString.decodeAsOctetString(attrElements[i]).stringValue(); 241 } 242 } 243 catch (final Exception e) 244 { 245 Debug.debugException(e); 246 throw new LDAPException(ResultCode.DECODING_ERROR, 247 ERR_POST_READ_REQUEST_CANNOT_DECODE.get(e), e); 248 } 249 } 250 251 252 253 /** 254 * Encodes the provided information into an octet string that can be used as 255 * the value for this control. 256 * 257 * @param attributes The set of attributes to retrieve from the target 258 * entry. It behaves in the same way as the set of 259 * requested attributes for a search operation. If this 260 * is empty or {@code null}, then all user attributes 261 * will be returned. 262 * 263 * @return An ASN.1 octet string that can be used as the value for this 264 * control. 265 */ 266 private static ASN1OctetString encodeValue(final String[] attributes) 267 { 268 if ((attributes == null) || (attributes.length == 0)) 269 { 270 return new ASN1OctetString(new ASN1Sequence().encode()); 271 } 272 273 final ASN1OctetString[] elements = new ASN1OctetString[attributes.length]; 274 for (int i=0; i < attributes.length; i++) 275 { 276 elements[i] = new ASN1OctetString(attributes[i]); 277 } 278 279 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 280 } 281 282 283 284 /** 285 * Retrieves the set of attributes that will be requested for inclusion in the 286 * entry returned in the response control. 287 * 288 * @return The set of attributes that will be requested for inclusion in the 289 * entry returned in the response control, or an empty array if all 290 * user attributes should be returned. 291 */ 292 public String[] getAttributes() 293 { 294 return attributes; 295 } 296 297 298 299 /** 300 * {@inheritDoc} 301 */ 302 @Override() 303 public String getControlName() 304 { 305 return INFO_CONTROL_NAME_POST_READ_REQUEST.get(); 306 } 307 308 309 310 /** 311 * {@inheritDoc} 312 */ 313 @Override() 314 public void toString(final StringBuilder buffer) 315 { 316 buffer.append("PostReadRequestControl(attributes={"); 317 for (int i=0; i < attributes.length; i++) 318 { 319 if (i > 0) 320 { 321 buffer.append(", "); 322 } 323 buffer.append('\''); 324 buffer.append(attributes[i]); 325 buffer.append('\''); 326 } 327 buffer.append("}, isCritical="); 328 buffer.append(isCritical()); 329 buffer.append(')'); 330 } 331}