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 2013-2015 ForgeRock AS 026 */ 027package org.opends.server.core; 028 029import java.util.ArrayList; 030import java.util.HashMap; 031import java.util.List; 032import java.util.Map; 033 034import org.forgerock.i18n.slf4j.LocalizedLogger; 035import org.forgerock.opendj.ldap.ByteString; 036import org.forgerock.opendj.ldap.ResultCode; 037import org.opends.server.api.ClientConnection; 038import org.opends.server.protocols.ldap.LDAPAttribute; 039import org.opends.server.protocols.ldap.LDAPResultCode; 040import org.opends.server.types.*; 041import org.opends.server.types.operation.PostResponseAddOperation; 042import org.opends.server.types.operation.PreParseAddOperation; 043import org.opends.server.workflowelement.localbackend.LocalBackendAddOperation; 044 045import static org.opends.messages.CoreMessages.*; 046import static org.opends.server.config.ConfigConstants.*; 047import static org.opends.server.core.DirectoryServer.*; 048import static org.opends.server.loggers.AccessLogger.*; 049import static org.opends.server.util.CollectionUtils.*; 050import static org.opends.server.util.StaticUtils.*; 051import static org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement.*; 052 053/** 054 * This class defines an operation that may be used to add a new entry to the 055 * Directory Server. 056 */ 057public class AddOperationBasis 058 extends AbstractOperation 059 implements PreParseAddOperation, AddOperation, PostResponseAddOperation 060{ 061 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 062 063 /** The set of response controls to send to the client. */ 064 private final ArrayList<Control> responseControls = new ArrayList<>(); 065 066 /** The raw, unprocessed entry DN as provided in the request. This may or may not be a valid DN. */ 067 private ByteString rawEntryDN; 068 /** The processed DN of the entry to add. */ 069 private DN entryDN; 070 /** The proxied authorization target DN for this operation. */ 071 private DN proxiedAuthorizationDN; 072 073 /** 074 * The set of attributes (including the objectclass attribute) in a raw, 075 * unprocessed form as provided in the request. One or more of these 076 * attributes may be invalid. 077 */ 078 private List<RawAttribute> rawAttributes; 079 /** The set of operational attributes for the entry to add. */ 080 private Map<AttributeType,List<Attribute>> operationalAttributes; 081 /** The set of user attributes for the entry to add. */ 082 private Map<AttributeType,List<Attribute>> userAttributes; 083 /** The set of objectclasses for the entry to add. */ 084 private Map<ObjectClass,String> objectClasses; 085 086 /** The flag indicates if an LDAP error was reported. */ 087 private boolean ldapError; 088 089 /** 090 * Creates a new add operation with the provided information. 091 * 092 * @param clientConnection The client connection with which this operation 093 * is associated. 094 * @param operationID The operation ID for this operation. 095 * @param messageID The message ID of the request with which this 096 * operation is associated. 097 * @param requestControls The set of controls included in the request. 098 * @param rawEntryDN The raw DN of the entry to add from the client 099 * request. This may or may not be a valid DN. 100 * @param rawAttributes The raw set of attributes from the client 101 * request (including the objectclass attribute). 102 * This may contain invalid attributes. 103 */ 104 public AddOperationBasis(ClientConnection clientConnection, long operationID, 105 int messageID, List<Control> requestControls, 106 ByteString rawEntryDN, List<RawAttribute> rawAttributes) 107 { 108 super(clientConnection, operationID, messageID, requestControls); 109 110 111 this.rawEntryDN = rawEntryDN; 112 this.rawAttributes = rawAttributes; 113 114 entryDN = null; 115 userAttributes = null; 116 operationalAttributes = null; 117 objectClasses = null; 118 } 119 120 121 122 /** 123 * Creates a new add operation with the provided information. 124 * 125 * @param clientConnection The client connection with which this 126 * operation is associated. 127 * @param operationID The operation ID for this operation. 128 * @param messageID The message ID of the request with which 129 * this operation is associated. 130 * @param requestControls The set of controls included in the request. 131 * @param entryDN The DN for the entry. 132 * @param objectClasses The set of objectclasses for the entry. 133 * @param userAttributes The set of user attributes for the entry. 134 * @param operationalAttributes The set of operational attributes for the 135 * entry. 136 */ 137 public AddOperationBasis(ClientConnection clientConnection, long operationID, 138 int messageID, List<Control> requestControls, 139 DN entryDN, Map<ObjectClass,String> objectClasses, 140 Map<AttributeType,List<Attribute>> userAttributes, 141 Map<AttributeType,List<Attribute>> operationalAttributes) 142 { 143 super(clientConnection, operationID, messageID, requestControls); 144 145 146 this.entryDN = entryDN; 147 this.objectClasses = objectClasses; 148 this.userAttributes = userAttributes; 149 this.operationalAttributes = operationalAttributes; 150 151 rawEntryDN = ByteString.valueOfUtf8(entryDN.toString()); 152 153 ArrayList<String> values = new ArrayList<>(objectClasses.values()); 154 rawAttributes = new ArrayList<>(); 155 rawAttributes.add(new LDAPAttribute(ATTR_OBJECTCLASS, values)); 156 addAll(rawAttributes, userAttributes); 157 addAll(rawAttributes, operationalAttributes); 158 } 159 160 private void addAll(List<RawAttribute> rawAttributes, Map<AttributeType, List<Attribute>> attributesToAdd) 161 { 162 for (List<Attribute> attrList : attributesToAdd.values()) 163 { 164 for (Attribute a : attrList) 165 { 166 rawAttributes.add(new LDAPAttribute(a)); 167 } 168 } 169 } 170 171 @Override 172 public final ByteString getRawEntryDN() 173 { 174 return rawEntryDN; 175 } 176 177 @Override 178 public final void setRawEntryDN(ByteString rawEntryDN) 179 { 180 this.rawEntryDN = rawEntryDN; 181 182 entryDN = null; 183 } 184 185 @Override 186 public final DN getEntryDN() 187 { 188 try 189 { 190 if (entryDN == null) 191 { 192 entryDN = DN.decode(rawEntryDN); 193 } 194 } 195 catch (DirectoryException de) 196 { 197 logger.traceException(de); 198 setResponseData(de); 199 } 200 return entryDN; 201 } 202 203 @Override 204 public final List<RawAttribute> getRawAttributes() 205 { 206 return rawAttributes; 207 } 208 209 @Override 210 public final void addRawAttribute(RawAttribute rawAttribute) 211 { 212 rawAttributes.add(rawAttribute); 213 214 objectClasses = null; 215 userAttributes = null; 216 operationalAttributes = null; 217 } 218 219 @Override 220 public final void setRawAttributes(List<RawAttribute> rawAttributes) 221 { 222 this.rawAttributes = rawAttributes; 223 224 objectClasses = null; 225 userAttributes = null; 226 operationalAttributes = null; 227 } 228 229 @Override 230 public final Map<ObjectClass,String> getObjectClasses() 231 { 232 if (objectClasses == null){ 233 computeObjectClassesAndAttributes(); 234 } 235 return objectClasses; 236 } 237 238 @Override 239 public final void addObjectClass(ObjectClass objectClass, String name) 240 { 241 objectClasses.put(objectClass, name); 242 } 243 244 @Override 245 public final void removeObjectClass(ObjectClass objectClass) 246 { 247 objectClasses.remove(objectClass); 248 } 249 250 @Override 251 public final Map<AttributeType,List<Attribute>> getUserAttributes() 252 { 253 if (userAttributes == null){ 254 computeObjectClassesAndAttributes(); 255 } 256 return userAttributes; 257 } 258 259 @Override 260 public final Map<AttributeType,List<Attribute>> getOperationalAttributes() 261 { 262 if (operationalAttributes == null){ 263 computeObjectClassesAndAttributes(); 264 } 265 return operationalAttributes; 266 } 267 268 /** 269 * Build the objectclasses, the user attributes and the operational attributes 270 * if there are not already computed. 271 */ 272 private final void computeObjectClassesAndAttributes() 273 { 274 if (!ldapError 275 && (objectClasses == null || userAttributes == null 276 || operationalAttributes == null)) 277 { 278 objectClasses = new HashMap<>(); 279 userAttributes = new HashMap<>(); 280 operationalAttributes = new HashMap<>(); 281 282 for (RawAttribute a : rawAttributes) 283 { 284 try 285 { 286 Attribute attr = a.toAttribute(); 287 AttributeType attrType = attr.getAttributeType(); 288 289 // If the attribute type is marked "NO-USER-MODIFICATION" then fail 290 // unless this is an internal operation or is related to 291 // synchronization in some way. 292 if (attrType.isNoUserModification() 293 && !isInternalOperation() 294 && !isSynchronizationOperation()) 295 { 296 throw new LDAPException(LDAPResultCode.UNWILLING_TO_PERFORM, 297 ERR_ADD_ATTR_IS_NO_USER_MOD.get(entryDN, attr.getName())); 298 } 299 300 if(attrType.getSyntax().isBEREncodingRequired()) 301 { 302 if(!attr.hasOption("binary")) 303 { 304 //A binary option wasn't provided by the client so add it. 305 AttributeBuilder builder = new AttributeBuilder(attr); 306 builder.setOption("binary"); 307 attr = builder.toAttribute(); 308 } 309 } 310 else if (attr.hasOption("binary")) 311 { 312 // binary option is not honored for non-BER-encodable attributes. 313 throw new LDAPException(LDAPResultCode.UNDEFINED_ATTRIBUTE_TYPE, 314 ERR_ADD_ATTR_IS_INVALID_OPTION.get(entryDN, attr.getName())); 315 } 316 317 if (attrType.isObjectClass()) 318 { 319 for (ByteString os : a.getValues()) 320 { 321 String ocName = os.toString(); 322 ObjectClass oc = 323 DirectoryServer.getObjectClass(toLowerCase(ocName)); 324 if (oc == null) 325 { 326 oc = DirectoryServer.getDefaultObjectClass(ocName); 327 } 328 329 objectClasses.put(oc,ocName); 330 } 331 } 332 else if (attrType.isOperational()) 333 { 334 List<Attribute> attrs = operationalAttributes.get(attrType); 335 if (attrs == null) 336 { 337 attrs = new ArrayList<>(1); 338 operationalAttributes.put(attrType, attrs); 339 } 340 attrs.add(attr); 341 } 342 else 343 { 344 List<Attribute> attrs = userAttributes.get(attrType); 345 if (attrs == null) 346 { 347 attrs = newArrayList(attr); 348 userAttributes.put(attrType, attrs); 349 } 350 else 351 { 352 // Check to see if any of the existing attributes in the list 353 // have the same set of options. If so, then add the values 354 // to that attribute. 355 boolean attributeSeen = false; 356 for (int i = 0; i < attrs.size(); i++) { 357 Attribute ea = attrs.get(i); 358 if (ea.optionsEqual(attr.getOptions())) 359 { 360 AttributeBuilder builder = new AttributeBuilder(ea); 361 builder.addAll(attr); 362 attrs.set(i, builder.toAttribute()); 363 attributeSeen = true; 364 } 365 } 366 367 if (!attributeSeen) 368 { 369 // This is the first occurrence of the attribute and options. 370 attrs.add(attr); 371 } 372 } 373 } 374 } 375 catch (LDAPException le) 376 { 377 setResultCode(ResultCode.valueOf(le.getResultCode())); 378 appendErrorMessage(le.getMessageObject()); 379 380 objectClasses = null; 381 userAttributes = null; 382 operationalAttributes = null; 383 ldapError = true; 384 return; 385 } 386 } 387 } 388 } 389 390 @Override 391 public final void setAttribute(AttributeType attributeType, 392 List<Attribute> attributeList) 393 { 394 Map<AttributeType, List<Attribute>> attributes = 395 getAttributes(attributeType.isOperational()); 396 if (attributeList == null || attributeList.isEmpty()) 397 { 398 attributes.remove(attributeType); 399 } 400 else 401 { 402 attributes.put(attributeType, attributeList); 403 } 404 } 405 406 @Override 407 public final void removeAttribute(AttributeType attributeType) 408 { 409 getAttributes(attributeType.isOperational()).remove(attributeType); 410 } 411 412 private Map<AttributeType, List<Attribute>> getAttributes(boolean isOperational) 413 { 414 if (isOperational) 415 { 416 return operationalAttributes; 417 } 418 return userAttributes; 419 } 420 421 @Override 422 public final OperationType getOperationType() 423 { 424 // Note that no debugging will be done in this method because it is a likely 425 // candidate for being called by the logging subsystem. 426 427 return OperationType.ADD; 428 } 429 430 @Override 431 public DN getProxiedAuthorizationDN() 432 { 433 return proxiedAuthorizationDN; 434 } 435 436 @Override 437 public final ArrayList<Control> getResponseControls() 438 { 439 return responseControls; 440 } 441 442 @Override 443 public final void addResponseControl(Control control) 444 { 445 responseControls.add(control); 446 } 447 448 @Override 449 public final void removeResponseControl(Control control) 450 { 451 responseControls.remove(control); 452 } 453 454 @Override 455 public final void toString(StringBuilder buffer) 456 { 457 buffer.append("AddOperation(connID="); 458 buffer.append(clientConnection.getConnectionID()); 459 buffer.append(", opID="); 460 buffer.append(operationID); 461 buffer.append(", dn="); 462 buffer.append(rawEntryDN); 463 buffer.append(")"); 464 } 465 466 @Override 467 public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN) 468 { 469 this.proxiedAuthorizationDN = proxiedAuthorizationDN; 470 } 471 472 @Override 473 public final void run() 474 { 475 setResultCode(ResultCode.UNDEFINED); 476 477 // Start the processing timer. 478 setProcessingStartTime(); 479 480 logAddRequest(this); 481 482 // This flag is set to true as soon as a workflow has been executed. 483 boolean workflowExecuted = false; 484 try 485 { 486 // Check for and handle a request to cancel this operation. 487 checkIfCanceled(false); 488 489 // Invoke the pre-parse add plugins. 490 if (!processOperationResult(getPluginConfigManager().invokePreParseAddPlugins(this))) 491 { 492 return; 493 } 494 495 // Check for and handle a request to cancel this operation. 496 checkIfCanceled(false); 497 498 // Process the entry DN and set of attributes to convert them from their 499 // raw forms as provided by the client to the forms required for the rest 500 // of the add processing. 501 DN entryDN = getEntryDN(); 502 if (entryDN == null){ 503 return; 504 } 505 506 workflowExecuted = execute(this, entryDN); 507 } 508 catch(CanceledOperationException coe) 509 { 510 logger.traceException(coe); 511 512 setResultCode(ResultCode.CANCELLED); 513 cancelResult = new CancelResult(ResultCode.CANCELLED, null); 514 515 appendErrorMessage(coe.getCancelRequest().getCancelReason()); 516 } 517 finally 518 { 519 // Stop the processing timer. 520 setProcessingStopTime(); 521 522 // Log the add response message. 523 logAddResponse(this); 524 525 if(cancelRequest == null || cancelResult == null || 526 cancelResult.getResultCode() != ResultCode.CANCELLED || 527 cancelRequest.notifyOriginalRequestor() || 528 DirectoryServer.notifyAbandonedOperations()) 529 { 530 clientConnection.sendResponse(this); 531 } 532 533 534 // Invoke the post-response callbacks. 535 if (workflowExecuted) { 536 invokePostResponseCallbacks(); 537 } 538 539 // Invoke the post-response add plugins. 540 invokePostResponsePlugins(workflowExecuted); 541 542 // If no cancel result, set it 543 if(cancelResult == null) 544 { 545 cancelResult = new CancelResult(ResultCode.TOO_LATE, null); 546 } 547 } 548 } 549 550 551 /** 552 * Invokes the post response plugins. If a workflow has been executed 553 * then invoke the post response plugins provided by the workflow 554 * elements of the workflow, otherwise invoke the post response plugins 555 * that have been registered with the current operation. 556 * 557 * @param workflowExecuted <code>true</code> if a workflow has been executed 558 */ 559 @SuppressWarnings({ "unchecked", "rawtypes" }) 560 private void invokePostResponsePlugins(boolean workflowExecuted) 561 { 562 // Invoke the post response plugins 563 if (workflowExecuted) 564 { 565 // Invoke the post response plugins that have been registered by 566 // the workflow elements 567 List<LocalBackendAddOperation> localOperations = 568 (List) getAttachment(Operation.LOCALBACKENDOPERATIONS); 569 570 if (localOperations != null) 571 { 572 for (LocalBackendAddOperation localOp : localOperations) 573 { 574 getPluginConfigManager().invokePostResponseAddPlugins(localOp); 575 } 576 } 577 } 578 else 579 { 580 // Invoke the post response plugins that have been registered with 581 // the current operation 582 getPluginConfigManager().invokePostResponseAddPlugins(this); 583 } 584 } 585 586 @Override 587 public void updateOperationErrMsgAndResCode() 588 { 589 DN entryDN = getEntryDN(); 590 DN parentDN = entryDN.getParentDNInSuffix(); 591 if (parentDN == null) 592 { 593 // Either this entry is a suffix or doesn't belong in the directory. 594 if (DirectoryServer.isNamingContext(entryDN)) 595 { 596 // This is fine. This entry is one of the configured suffixes. 597 return; 598 } 599 if (entryDN.isRootDN()) 600 { 601 // This is not fine. The root DSE cannot be added. 602 setResultCode(ResultCode.UNWILLING_TO_PERFORM); 603 appendErrorMessage(ERR_ADD_CANNOT_ADD_ROOT_DSE.get()); 604 return; 605 } 606 // The entry doesn't have a parent but isn't a suffix. This is not allowed. 607 setResultCode(ResultCode.NO_SUCH_OBJECT); 608 appendErrorMessage(ERR_ADD_ENTRY_NOT_SUFFIX.get(entryDN)); 609 return; 610 } 611 // The suffix does not exist 612 setResultCode(ResultCode.NO_SUCH_OBJECT); 613 appendErrorMessage(ERR_ADD_ENTRY_UNKNOWN_SUFFIX.get(entryDN)); 614 } 615 616 617 /** 618 * {@inheritDoc} 619 * 620 * This method always returns null. 621 */ 622 @Override 623 public Entry getEntryToAdd() 624 { 625 return null; 626 } 627}