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