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