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