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 2009 Sun Microsystems, Inc. 015 * Portions copyright 2012-2016 ForgeRock AS. 016 */ 017package org.forgerock.opendj.ldap.controls; 018 019import static java.util.Arrays.asList; 020import static java.util.Collections.*; 021import static com.forgerock.opendj.ldap.CoreMessages.*; 022 023import java.io.IOException; 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.List; 028 029import org.forgerock.i18n.LocalizableMessage; 030import org.forgerock.i18n.slf4j.LocalizedLogger; 031import org.forgerock.opendj.io.ASN1; 032import org.forgerock.opendj.io.ASN1Reader; 033import org.forgerock.opendj.io.ASN1Writer; 034import org.forgerock.opendj.ldap.ByteString; 035import org.forgerock.opendj.ldap.ByteStringBuilder; 036import org.forgerock.opendj.ldap.DecodeException; 037import org.forgerock.opendj.ldap.DecodeOptions; 038import org.forgerock.util.Reject; 039 040/** 041 * The post-read request control as defined in RFC 4527. This control allows the 042 * client to read the target entry of an update operation immediately after the 043 * modifications are applied. These reads are done as an atomic part of the 044 * update operation. 045 * <p> 046 * The following example gets a modified entry from the result of a modify 047 * operation. 048 * 049 * <pre> 050 * Connection connection = ...; 051 * String DN = ...; 052 * 053 * ModifyRequest request = 054 * Requests.newModifyRequest(DN) 055 * .addControl(PostReadRequestControl.newControl(true, "description")) 056 * .addModification(ModificationType.REPLACE, 057 * "description", "Using the PostReadRequestControl"); 058 * 059 * Result result = connection.modify(request); 060 * PostReadResponseControl control = 061 * result.getControl(PostReadResponseControl.DECODER, 062 * new DecodeOptions()); 063 * Entry modifiedEntry = control.getEntry(); 064 * </pre> 065 * 066 * @see PostReadResponseControl 067 * @see <a href="http://tools.ietf.org/html/rfc4527">RFC 4527 - Lightweight 068 * Directory Access Protocol (LDAP) Read Entry Controls </a> 069 */ 070public final class PostReadRequestControl implements Control { 071 072 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 073 /** 074 * The IANA-assigned OID for the LDAP post-read request control used for 075 * retrieving an entry in the state it had immediately after an update was 076 * applied. 077 */ 078 public static final String OID = "1.3.6.1.1.13.2"; 079 080 /** The list of raw attributes to return in the entry. */ 081 private final List<String> attributes; 082 083 private final boolean isCritical; 084 085 private static final PostReadRequestControl CRITICAL_EMPTY_INSTANCE = 086 new PostReadRequestControl(true, Collections.<String> emptyList()); 087 088 private static final PostReadRequestControl NONCRITICAL_EMPTY_INSTANCE = 089 new PostReadRequestControl(false, Collections.<String> emptyList()); 090 091 /** A decoder which can be used for decoding the post-read request control. */ 092 public static final ControlDecoder<PostReadRequestControl> DECODER = 093 new ControlDecoder<PostReadRequestControl>() { 094 095 @Override 096 public PostReadRequestControl decodeControl(final Control control, 097 final DecodeOptions options) throws DecodeException { 098 Reject.ifNull(control); 099 100 if (control instanceof PostReadRequestControl) { 101 return (PostReadRequestControl) control; 102 } 103 104 if (!control.getOID().equals(OID)) { 105 final LocalizableMessage message = 106 ERR_POSTREAD_CONTROL_BAD_OID.get(control.getOID(), OID); 107 throw DecodeException.error(message); 108 } 109 110 if (!control.hasValue()) { 111 // The control must always have a value. 112 final LocalizableMessage message = ERR_POSTREADREQ_NO_CONTROL_VALUE.get(); 113 throw DecodeException.error(message); 114 } 115 116 final ASN1Reader reader = ASN1.getReader(control.getValue()); 117 List<String> attributes; 118 try { 119 reader.readStartSequence(); 120 if (reader.hasNextElement()) { 121 final String firstAttribute = reader.readOctetStringAsString(); 122 if (reader.hasNextElement()) { 123 attributes = new ArrayList<>(); 124 attributes.add(firstAttribute); 125 do { 126 attributes.add(reader.readOctetStringAsString()); 127 } while (reader.hasNextElement()); 128 attributes = unmodifiableList(attributes); 129 } else { 130 attributes = singletonList(firstAttribute); 131 } 132 } else { 133 attributes = emptyList(); 134 } 135 reader.readEndSequence(); 136 } catch (final Exception ae) { 137 logger.debug(LocalizableMessage.raw("Unable to read sequence", ae)); 138 139 final LocalizableMessage message = 140 ERR_POSTREADREQ_CANNOT_DECODE_VALUE.get(ae.getMessage()); 141 throw DecodeException.error(message, ae); 142 } 143 144 if (attributes.isEmpty()) { 145 return control.isCritical() ? CRITICAL_EMPTY_INSTANCE 146 : NONCRITICAL_EMPTY_INSTANCE; 147 } else { 148 return new PostReadRequestControl(control.isCritical(), attributes); 149 } 150 } 151 152 @Override 153 public String getOID() { 154 return OID; 155 } 156 }; 157 158 /** 159 * Creates a new post-read request control. 160 * 161 * @param isCritical 162 * {@code true} if it is unacceptable to perform the operation 163 * without applying the semantics of this control, or 164 * {@code false} if it can be ignored 165 * @param attributes 166 * The list of attributes to be included with the response 167 * control. Attributes that are sub-types of listed attributes 168 * are implicitly included. The list may be empty, indicating 169 * that all user attributes should be returned. 170 * @return The new control. 171 * @throws NullPointerException 172 * If {@code attributes} was {@code null}. 173 */ 174 public static PostReadRequestControl newControl(final boolean isCritical, 175 final Collection<String> attributes) { 176 Reject.ifNull(attributes); 177 178 if (attributes.isEmpty()) { 179 return isCritical ? CRITICAL_EMPTY_INSTANCE : NONCRITICAL_EMPTY_INSTANCE; 180 } else if (attributes.size() == 1) { 181 return new PostReadRequestControl(isCritical, singletonList(attributes.iterator() 182 .next())); 183 } else { 184 return new PostReadRequestControl(isCritical, unmodifiableList(new ArrayList<String>( 185 attributes))); 186 } 187 } 188 189 /** 190 * Creates a new post-read request control. 191 * 192 * @param isCritical 193 * {@code true} if it is unacceptable to perform the operation 194 * without applying the semantics of this control, or 195 * {@code false} if it can be ignored 196 * @param attributes 197 * The list of attributes to be included with the response 198 * control. Attributes that are sub-types of listed attributes 199 * are implicitly included. The list may be empty, indicating 200 * that all user attributes should be returned. 201 * @return The new control. 202 * @throws NullPointerException 203 * If {@code attributes} was {@code null}. 204 */ 205 public static PostReadRequestControl newControl(final boolean isCritical, 206 final String... attributes) { 207 Reject.ifNull((Object) attributes); 208 209 if (attributes.length == 0) { 210 return isCritical ? CRITICAL_EMPTY_INSTANCE : NONCRITICAL_EMPTY_INSTANCE; 211 } else if (attributes.length == 1) { 212 return new PostReadRequestControl(isCritical, singletonList(attributes[0])); 213 } else { 214 return new PostReadRequestControl(isCritical, unmodifiableList(new ArrayList<String>( 215 asList(attributes)))); 216 } 217 } 218 219 private PostReadRequestControl(final boolean isCritical, final List<String> attributes) { 220 this.isCritical = isCritical; 221 this.attributes = attributes; 222 } 223 224 /** 225 * Returns an unmodifiable list containing the names of attributes to be 226 * included with the response control. Attributes that are sub-types of 227 * listed attributes are implicitly included. The returned list may be 228 * empty, indicating that all user attributes should be returned. 229 * 230 * @return An unmodifiable list containing the names of attributes to be 231 * included with the response control. 232 */ 233 public List<String> getAttributes() { 234 return attributes; 235 } 236 237 @Override 238 public String getOID() { 239 return OID; 240 } 241 242 @Override 243 public ByteString getValue() { 244 final ByteStringBuilder buffer = new ByteStringBuilder(); 245 final ASN1Writer writer = ASN1.getWriter(buffer); 246 try { 247 writer.writeStartSequence(); 248 if (attributes != null) { 249 for (final String attr : attributes) { 250 writer.writeOctetString(attr); 251 } 252 } 253 writer.writeEndSequence(); 254 return buffer.toByteString(); 255 } catch (final IOException ioe) { 256 // This should never happen unless there is a bug somewhere. 257 throw new RuntimeException(ioe); 258 } 259 } 260 261 @Override 262 public boolean hasValue() { 263 return true; 264 } 265 266 @Override 267 public boolean isCritical() { 268 return isCritical; 269 } 270 271 @Override 272 public String toString() { 273 final StringBuilder builder = new StringBuilder(); 274 builder.append("PostReadRequestControl(oid="); 275 builder.append(getOID()); 276 builder.append(", criticality="); 277 builder.append(isCritical()); 278 builder.append(", attributes="); 279 builder.append(attributes); 280 builder.append(")"); 281 return builder.toString(); 282 } 283 284}