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