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