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