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-2010 Sun Microsystems, Inc. 015 * Portions copyright 2012-2016 ForgeRock AS. 016 */ 017package org.forgerock.opendj.ldap.controls; 018 019import static com.forgerock.opendj.ldap.CoreMessages.*; 020 021import java.io.IOException; 022 023import org.forgerock.i18n.LocalizableMessage; 024import org.forgerock.i18n.slf4j.LocalizedLogger; 025import org.forgerock.opendj.io.ASN1; 026import org.forgerock.opendj.io.ASN1Reader; 027import org.forgerock.opendj.io.LDAP; 028import org.forgerock.opendj.ldap.ByteString; 029import org.forgerock.opendj.ldap.ByteStringBuilder; 030import org.forgerock.opendj.ldap.DecodeException; 031import org.forgerock.opendj.ldap.DecodeOptions; 032import org.forgerock.opendj.ldap.Entries; 033import org.forgerock.opendj.ldap.Entry; 034import org.forgerock.util.Reject; 035 036/** 037 * The pre-read response control as defined in RFC 4527. This control is 038 * returned by the server in response to a successful update operation which 039 * included a pre-read request control. The control contains a Search Result 040 * Entry containing, subject to access controls and other constraints, values of 041 * the requested attributes. 042 * <p> 043 * The following example gets the entry as it was before the modify operation. 044 * 045 * <pre> 046 * Connection connection = ...; 047 * String DN = ...; 048 * 049 * ModifyRequest request = 050 * Requests.newModifyRequest(DN) 051 * .addControl(PreReadRequestControl.newControl(true, "mail")) 052 * .addModification(ModificationType.REPLACE, 053 * "mail", "modified@example.com"); 054 * 055 * Result result = connection.modify(request); 056 * PreReadResponseControl control = 057 * result.getControl(PreReadResponseControl.DECODER, 058 * new DecodeOptions()); 059 * Entry unmodifiedEntry = control.getEntry(); 060 * </pre> 061 * 062 * @see PreReadRequestControl 063 * @see <a href="http://tools.ietf.org/html/rfc4527">RFC 4527 - Lightweight 064 * Directory Access Protocol (LDAP) Read Entry Controls </a> 065 */ 066public final class PreReadResponseControl implements Control { 067 068 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 069 /** 070 * The IANA-assigned OID for the LDAP pre-read response control used for 071 * retrieving an entry in the state it had immediately before an update was 072 * applied. 073 */ 074 public static final String OID = PreReadRequestControl.OID; 075 076 /** A decoder which can be used for decoding the pre-read response control. */ 077 public static final ControlDecoder<PreReadResponseControl> DECODER = 078 new ControlDecoder<PreReadResponseControl>() { 079 080 @Override 081 public PreReadResponseControl decodeControl(final Control control, 082 final DecodeOptions options) throws DecodeException { 083 Reject.ifNull(control); 084 085 if (control instanceof PreReadResponseControl) { 086 return (PreReadResponseControl) control; 087 } 088 089 if (!control.getOID().equals(OID)) { 090 final LocalizableMessage message = 091 ERR_PREREAD_CONTROL_BAD_OID.get(control.getOID(), OID); 092 throw DecodeException.error(message); 093 } 094 095 if (!control.hasValue()) { 096 // The control must always have a value. 097 final LocalizableMessage message = ERR_PREREADRESP_NO_CONTROL_VALUE.get(); 098 throw DecodeException.error(message); 099 } 100 101 final ASN1Reader reader = ASN1.getReader(control.getValue()); 102 final Entry entry; 103 try { 104 entry = LDAP.readEntry(reader, options); 105 } catch (final IOException le) { 106 logger.debug(LocalizableMessage.raw("Unable to read result entry", le)); 107 final LocalizableMessage message = 108 ERR_PREREADRESP_CANNOT_DECODE_VALUE.get(le.getMessage()); 109 throw DecodeException.error(message, le); 110 } 111 112 /* 113 * FIXME: the RFC states that the control contains a 114 * SearchResultEntry rather than an Entry. Can we assume 115 * that the response will not contain a nested set of 116 * controls? 117 */ 118 return new PreReadResponseControl(control.isCritical(), Entries 119 .unmodifiableEntry(entry)); 120 } 121 122 @Override 123 public String getOID() { 124 return OID; 125 } 126 }; 127 128 /** 129 * Creates a new pre-read response control. 130 * 131 * @param entry 132 * The entry whose contents reflect the state of the updated 133 * entry immediately before the update operation was performed. 134 * @return The new control. 135 * @throws NullPointerException 136 * If {@code entry} was {@code null}. 137 */ 138 public static PreReadResponseControl newControl(final Entry entry) { 139 /* 140 * FIXME: all other control implementations are fully immutable. We 141 * should really do a defensive copy here in order to be consistent, 142 * rather than just wrap it. Also, the RFC states that the control 143 * contains a SearchResultEntry rather than an Entry. Can we assume that 144 * the response will not contain a nested set of controls? 145 */ 146 return new PreReadResponseControl(false, Entries.unmodifiableEntry(entry)); 147 } 148 149 private final Entry entry; 150 151 private final boolean isCritical; 152 153 private PreReadResponseControl(final boolean isCritical, final Entry entry) { 154 this.isCritical = isCritical; 155 this.entry = entry; 156 } 157 158 /** 159 * Returns an unmodifiable entry whose contents reflect the state of the 160 * updated entry immediately before the update operation was performed. 161 * 162 * @return The unmodifiable entry whose contents reflect the state of the 163 * updated entry immediately before the update operation was 164 * performed. 165 */ 166 public Entry getEntry() { 167 return entry; 168 } 169 170 @Override 171 public String getOID() { 172 return OID; 173 } 174 175 @Override 176 public ByteString getValue() { 177 try { 178 final ByteStringBuilder buffer = new ByteStringBuilder(); 179 LDAP.writeEntry(ASN1.getWriter(buffer), entry); 180 return buffer.toByteString(); 181 } catch (final IOException ioe) { 182 // This should never happen unless there is a bug somewhere. 183 throw new RuntimeException(ioe); 184 } 185 } 186 187 @Override 188 public boolean hasValue() { 189 return true; 190 } 191 192 @Override 193 public boolean isCritical() { 194 return isCritical; 195 } 196 197 @Override 198 public String toString() { 199 final StringBuilder builder = new StringBuilder(); 200 builder.append("PreReadResponseControl(oid="); 201 builder.append(getOID()); 202 builder.append(", criticality="); 203 builder.append(isCritical()); 204 builder.append(", entry="); 205 builder.append(entry); 206 builder.append(")"); 207 return builder.toString(); 208 } 209}