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