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 2007-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2012-2016 ForgeRock AS. 016 */ 017package org.opends.server.core; 018 019import java.util.ArrayList; 020import java.util.List; 021 022import org.forgerock.i18n.LocalizedIllegalArgumentException; 023import org.forgerock.i18n.slf4j.LocalizedLogger; 024import org.forgerock.opendj.ldap.AttributeDescription; 025import org.forgerock.opendj.ldap.ByteString; 026import org.forgerock.opendj.ldap.DN; 027import org.forgerock.opendj.ldap.ResultCode; 028import org.opends.server.api.ClientConnection; 029import org.opends.server.protocols.ldap.LDAPAttribute; 030import org.opends.server.protocols.ldap.LDAPModification; 031import org.opends.server.protocols.ldap.LDAPResultCode; 032import org.opends.server.types.AbstractOperation; 033import org.opends.server.types.Attribute; 034import org.opends.server.types.AttributeBuilder; 035import org.opends.server.types.CancelResult; 036import org.opends.server.types.CanceledOperationException; 037import org.opends.server.types.Control; 038import org.opends.server.types.DirectoryException; 039import org.opends.server.types.Entry; 040import org.opends.server.types.LDAPException; 041import org.opends.server.types.Modification; 042import org.opends.server.types.Operation; 043import org.opends.server.types.OperationType; 044import org.opends.server.types.RawModification; 045import org.opends.server.types.operation.PostResponseModifyOperation; 046import org.opends.server.types.operation.PreParseModifyOperation; 047import org.opends.server.workflowelement.localbackend.LocalBackendModifyOperation; 048 049import static org.opends.messages.CoreMessages.*; 050import static org.opends.server.core.DirectoryServer.*; 051import static org.opends.server.loggers.AccessLogger.*; 052import static org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement.*; 053 054/** This class defines an operation that may be used to modify an entry in the Directory Server. */ 055public class ModifyOperationBasis 056 extends AbstractOperation implements ModifyOperation, 057 PreParseModifyOperation, 058 PostResponseModifyOperation 059{ 060 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 061 062 /** The raw, unprocessed entry DN as included by the client request. */ 063 private ByteString rawEntryDN; 064 065 /** The DN of the entry for the modify operation. */ 066 private DN entryDN; 067 068 /** The proxied authorization target DN for this operation. */ 069 private DN proxiedAuthorizationDN; 070 071 /** The set of response controls for this modify operation. */ 072 private List<Control> responseControls; 073 074 /** The raw, unprocessed set of modifications as included in the client request. */ 075 private List<RawModification> rawModifications; 076 077 /** The set of modifications for this modify operation. */ 078 private List<Modification> modifications; 079 080 /** 081 * Creates a new modify operation with the provided information. 082 * 083 * @param clientConnection The client connection with which this operation 084 * is associated. 085 * @param operationID The operation ID for this operation. 086 * @param messageID The message ID of the request with which this 087 * operation is associated. 088 * @param requestControls The set of controls included in the request. 089 * @param rawEntryDN The raw, unprocessed DN of the entry to modify, 090 * as included in the client request. 091 * @param rawModifications The raw, unprocessed set of modifications for 092 * this modify operation as included in the client 093 * request. 094 */ 095 public ModifyOperationBasis(ClientConnection clientConnection, 096 long operationID, 097 int messageID, List<Control> requestControls, 098 ByteString rawEntryDN, 099 List<RawModification> rawModifications) 100 { 101 super(clientConnection, operationID, messageID, requestControls); 102 103 this.rawEntryDN = rawEntryDN; 104 this.rawModifications = rawModifications; 105 106 entryDN = null; 107 modifications = null; 108 responseControls = new ArrayList<>(); 109 cancelRequest = null; 110 } 111 112 /** 113 * Creates a new modify operation with the provided information. 114 * 115 * @param clientConnection The client connection with which this operation 116 * is associated. 117 * @param operationID The operation ID for this operation. 118 * @param messageID The message ID of the request with which this 119 * operation is associated. 120 * @param requestControls The set of controls included in the request. 121 * @param entryDN The entry DN for the modify operation. 122 * @param modifications The set of modifications for this modify 123 * operation. 124 */ 125 public ModifyOperationBasis(ClientConnection clientConnection, 126 long operationID, 127 int messageID, List<Control> requestControls, 128 DN entryDN, List<Modification> modifications) 129 { 130 super(clientConnection, operationID, messageID, requestControls); 131 132 this.entryDN = entryDN; 133 this.modifications = modifications; 134 135 rawEntryDN = ByteString.valueOfUtf8(entryDN.toString()); 136 137 rawModifications = new ArrayList<>(modifications.size()); 138 for (Modification m : modifications) 139 { 140 rawModifications.add(new LDAPModification(m.getModificationType(), 141 new LDAPAttribute(m.getAttribute()))); 142 } 143 144 responseControls = new ArrayList<>(); 145 cancelRequest = null; 146 } 147 148 @Override 149 public final ByteString getRawEntryDN() 150 { 151 return rawEntryDN; 152 } 153 154 @Override 155 public final void setRawEntryDN(ByteString rawEntryDN) 156 { 157 this.rawEntryDN = rawEntryDN; 158 159 entryDN = null; 160 } 161 162 @Override 163 public final DN getEntryDN() 164 { 165 if (entryDN == null){ 166 try { 167 entryDN = DN.valueOf(rawEntryDN); 168 } 169 catch (LocalizedIllegalArgumentException e) { 170 logger.traceException(e); 171 172 setResultCode(ResultCode.INVALID_DN_SYNTAX); 173 appendErrorMessage(e.getMessageObject()); 174 } 175 } 176 return entryDN; 177 } 178 179 @Override 180 public final List<RawModification> getRawModifications() 181 { 182 return rawModifications; 183 } 184 185 @Override 186 public final void addRawModification(RawModification rawModification) 187 { 188 rawModifications.add(rawModification); 189 190 modifications = null; 191 } 192 193 @Override 194 public final void setRawModifications(List<RawModification> rawModifications) 195 { 196 this.rawModifications = rawModifications; 197 198 modifications = null; 199 } 200 201 @Override 202 public final List<Modification> getModifications() 203 { 204 if (modifications == null) 205 { 206 modifications = new ArrayList<>(rawModifications.size()); 207 try { 208 for (RawModification m : rawModifications) 209 { 210 Modification mod = m.toModification(); 211 Attribute attr = mod.getAttribute(); 212 AttributeDescription attrDesc = attr.getAttributeDescription(); 213 214 boolean hasBinaryOption = attrDesc.hasOption("binary"); 215 if (attrDesc.getAttributeType().getSyntax().isBEREncodingRequired()) 216 { 217 if (!hasBinaryOption) 218 { 219 //A binary option wasn't provided by the client so add it. 220 AttributeBuilder builder = new AttributeBuilder(attr); 221 builder.setOption("binary"); 222 mod.setAttribute(builder.toAttribute()); 223 } 224 } 225 else if (hasBinaryOption) 226 { 227 // binary option is not honored for non-BER-encodable attributes. 228 throw new LDAPException(LDAPResultCode.UNDEFINED_ATTRIBUTE_TYPE, 229 ERR_ADD_ATTR_IS_INVALID_OPTION.get(entryDN, attrDesc)); 230 } 231 232 modifications.add(mod); 233 } 234 } 235 catch (LDAPException le) 236 { 237 logger.traceException(le); 238 setResultCode(ResultCode.valueOf(le.getResultCode())); 239 appendErrorMessage(le.getMessageObject()); 240 modifications = null; 241 } 242 } 243 return modifications; 244 } 245 246 @Override 247 public final void addModification(Modification modification) 248 throws DirectoryException 249 { 250 modifications.add(modification); 251 } 252 253 @Override 254 public final OperationType getOperationType() 255 { 256 // Note that no debugging will be done in this method because it is a likely 257 // candidate for being called by the logging subsystem. 258 259 return OperationType.MODIFY; 260 } 261 262 @Override 263 public DN getProxiedAuthorizationDN() 264 { 265 return proxiedAuthorizationDN; 266 } 267 268 @Override 269 public final List<Control> getResponseControls() 270 { 271 return responseControls; 272 } 273 274 @Override 275 public final void addResponseControl(Control control) 276 { 277 responseControls.add(control); 278 } 279 280 @Override 281 public final void removeResponseControl(Control control) 282 { 283 responseControls.remove(control); 284 } 285 286 @Override 287 public final void toString(StringBuilder buffer) 288 { 289 buffer.append("ModifyOperation(connID="); 290 buffer.append(clientConnection.getConnectionID()); 291 buffer.append(", opID="); 292 buffer.append(operationID); 293 buffer.append(", dn="); 294 buffer.append(rawEntryDN); 295 buffer.append(")"); 296 } 297 298 @Override 299 public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN) 300 { 301 this.proxiedAuthorizationDN = proxiedAuthorizationDN; 302 } 303 304 @Override 305 public final void run() 306 { 307 setResultCode(ResultCode.UNDEFINED); 308 309 // Start the processing timer. 310 setProcessingStartTime(); 311 312 logModifyRequest(this); 313 314 // This flag is set to true as soon as a workflow has been executed. 315 boolean workflowExecuted = false; 316 try 317 { 318 // Check for and handle a request to cancel this operation. 319 checkIfCanceled(false); 320 321 // Invoke the pre-parse modify plugins. 322 if (!processOperationResult(getPluginConfigManager().invokePreParseModifyPlugins(this))) 323 { 324 return; 325 } 326 327 // Check for and handle a request to cancel this operation. 328 checkIfCanceled(false); 329 330 // Process the entry DN to convert it from the raw form to the form 331 // required for the rest of the modify processing. 332 DN entryDN = getEntryDN(); 333 if (entryDN == null){ 334 return; 335 } 336 337 workflowExecuted = execute(this, entryDN); 338 } 339 catch(CanceledOperationException coe) 340 { 341 logger.traceException(coe); 342 343 setResultCode(ResultCode.CANCELLED); 344 cancelResult = new CancelResult(ResultCode.CANCELLED, null); 345 346 appendErrorMessage(coe.getCancelRequest().getCancelReason()); 347 } 348 finally 349 { 350 // Stop the processing timer. 351 setProcessingStopTime(); 352 353 // Log the modify response. 354 logModifyResponse(this); 355 356 if(cancelRequest == null || cancelResult == null || 357 cancelResult.getResultCode() != ResultCode.CANCELLED || 358 cancelRequest.notifyOriginalRequestor() || 359 DirectoryServer.notifyAbandonedOperations()) 360 { 361 clientConnection.sendResponse(this); 362 } 363 364 // Invoke the post-response callbacks. 365 if (workflowExecuted) { 366 invokePostResponseCallbacks(); 367 } 368 369 // Invoke the post-response add plugins. 370 invokePostResponsePlugins(workflowExecuted); 371 372 // If no cancel result, set it 373 if(cancelResult == null) 374 { 375 cancelResult = new CancelResult(ResultCode.TOO_LATE, null); 376 } 377 } 378 } 379 380 /** 381 * Invokes the post response plugins. If a workflow has been executed 382 * then invoke the post response plugins provided by the workflow 383 * elements of the workflow, otherwise invoke the post response plugins 384 * that have been registered with the current operation. 385 * 386 * @param workflowExecuted <code>true</code> if a workflow has been executed 387 */ 388 private void invokePostResponsePlugins(boolean workflowExecuted) 389 { 390 // Invoke the post response plugins 391 if (workflowExecuted) 392 { 393 // Invoke the post response plugins that have been registered by 394 // the workflow elements 395 @SuppressWarnings("unchecked") 396 List<LocalBackendModifyOperation> localOperations = 397 (List<LocalBackendModifyOperation>) getAttachment( 398 Operation.LOCALBACKENDOPERATIONS); 399 if (localOperations != null) 400 { 401 for (LocalBackendModifyOperation localOperation : localOperations) 402 { 403 getPluginConfigManager().invokePostResponseModifyPlugins(localOperation); 404 } 405 } 406 } 407 else 408 { 409 // Invoke the post response plugins that have been registered with 410 // the current operation 411 getPluginConfigManager().invokePostResponseModifyPlugins(this); 412 } 413 } 414 415 @Override 416 public void updateOperationErrMsgAndResCode() 417 { 418 setResultCode(ResultCode.NO_SUCH_OBJECT); 419 appendErrorMessage(ERR_MODIFY_NO_SUCH_ENTRY.get(getEntryDN())); 420 } 421 422 423 /** 424 * {@inheritDoc} 425 * 426 * This method always returns null. 427 */ 428 @Override 429 public Entry getCurrentEntry() { 430 return null; 431 } 432 433 /** 434 * {@inheritDoc} 435 * 436 * This method always returns null. 437 */ 438 @Override 439 public List<ByteString> getCurrentPasswords() 440 { 441 return null; 442 } 443 444 /** 445 * {@inheritDoc} 446 * 447 * This method always returns null. 448 */ 449 @Override 450 public Entry getModifiedEntry() 451 { 452 return null; 453 } 454 455 /** 456 * {@inheritDoc} 457 * 458 * This method always returns null. 459 */ 460 @Override 461 public List<ByteString> getNewPasswords() 462 { 463 return null; 464 } 465}