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 2006-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2017 ForgeRock AS. 016 */ 017package org.opends.server.types; 018 019import java.io.BufferedWriter; 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.Iterator; 026import java.util.LinkedHashMap; 027import java.util.LinkedHashSet; 028import java.util.LinkedList; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 032 033import org.forgerock.i18n.LocalizableMessage; 034import org.forgerock.i18n.LocalizableMessageBuilder; 035import org.forgerock.i18n.LocalizedIllegalArgumentException; 036import org.forgerock.i18n.slf4j.LocalizedLogger; 037import org.forgerock.opendj.ldap.AVA; 038import org.forgerock.opendj.ldap.AttributeDescription; 039import org.forgerock.opendj.ldap.ByteSequence; 040import org.forgerock.opendj.ldap.ByteSequenceReader; 041import org.forgerock.opendj.ldap.ByteString; 042import org.forgerock.opendj.ldap.ByteStringBuilder; 043import org.forgerock.opendj.ldap.DN; 044import org.forgerock.opendj.ldap.DecodeException; 045import org.forgerock.opendj.ldap.RDN; 046import org.forgerock.opendj.ldap.ResultCode; 047import org.forgerock.opendj.ldap.SearchScope; 048import org.forgerock.opendj.ldap.schema.AttributeType; 049import org.forgerock.opendj.ldap.schema.CoreSchema; 050import org.forgerock.opendj.ldap.schema.DITContentRule; 051import org.forgerock.opendj.ldap.schema.DITStructureRule; 052import org.forgerock.opendj.ldap.schema.MatchingRule; 053import org.forgerock.opendj.ldap.schema.NameForm; 054import org.forgerock.opendj.ldap.schema.ObjectClass; 055import org.forgerock.opendj.ldap.schema.ObjectClassType; 056import org.opends.server.api.CompressedSchema; 057import org.opends.server.api.ProtocolElement; 058import org.opends.server.api.plugin.PluginResult; 059import org.opends.server.core.DirectoryServer; 060import org.opends.server.core.PluginConfigManager; 061import org.opends.server.core.SubentryManager; 062import org.opends.server.types.SubEntry.CollectiveConflictBehavior; 063import org.opends.server.util.LDIFException; 064import org.opends.server.util.LDIFWriter; 065 066import static org.forgerock.opendj.ldap.ResultCode.*; 067import static org.opends.messages.CoreMessages.*; 068import static org.opends.messages.UtilityMessages.*; 069import static org.opends.server.util.CollectionUtils.*; 070import static org.opends.server.util.LDIFWriter.*; 071import static org.opends.server.util.ServerConstants.*; 072import static org.opends.server.util.StaticUtils.*; 073 074/** 075 * This class defines a data structure for a Directory Server entry. 076 * It includes a DN and a set of attributes. 077 * <BR><BR> 078 * The entry also contains a volatile attachment object, which should 079 * be used to associate the entry with a special type of object that 080 * is based on its contents. For example, if the entry holds access 081 * control information, then the attachment might be an object that 082 * contains a representation of that access control definition in a 083 * more useful form. This is only useful if the entry is to be 084 * cached, since the attachment may be accessed if the entry is 085 * retrieved from the cache, but if the entry is retrieved from the 086 * backend repository it cannot be guaranteed to contain any 087 * attachment (and in most cases will not). This attachment is 088 * volatile in that it is not always guaranteed to be present, it may 089 * be removed or overwritten at any time, and it will be invalidated 090 * and removed if the entry is altered in any way. 091 */ 092@org.opends.server.types.PublicAPI( 093 stability=org.opends.server.types.StabilityLevel.UNCOMMITTED, 094 mayInstantiate=true, 095 mayExtend=false, 096 mayInvoke=true) 097public class Entry 098 implements ProtocolElement 099{ 100 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 101 102 /** The set of operational attributes for this entry. */ 103 private Map<AttributeType,List<Attribute>> operationalAttributes; 104 105 /** The set of user attributes for this entry. */ 106 private Map<AttributeType,List<Attribute>> userAttributes; 107 108 /** 109 * The set of suppressed real attributes for this entry. It contains real 110 * attributes that have been overridden by virtual attributes. 111 */ 112 private final Map<AttributeType, List<Attribute>> suppressedAttributes = new LinkedHashMap<>(); 113 114 /** The set of objectclasses for this entry. */ 115 private Map<ObjectClass,String> objectClasses; 116 117 private Attribute objectClassAttribute; 118 119 /** The DN for this entry. */ 120 private DN dn; 121 122 /** 123 * A generic attachment that may be used to associate this entry with some 124 * other object. 125 */ 126 private transient Object attachment; 127 128 /** 129 * Creates a new entry with the provided information. 130 * 131 * @param dn The distinguished name for this 132 * entry. 133 * @param objectClasses The set of objectclasses for this 134 * entry as a mapping between the 135 * objectclass and the name to use to 136 * reference it. 137 * @param userAttributes The set of user attributes for 138 * this entry as a mapping between 139 * the attribute type and the list of 140 * attributes with that type. 141 * @param operationalAttributes The set of operational attributes 142 * for this entry as a mapping 143 * between the attribute type and the 144 * list of attributes with that type. 145 */ 146 public Entry(DN dn, Map<ObjectClass,String> objectClasses, 147 Map<AttributeType,List<Attribute>> userAttributes, 148 Map<AttributeType,List<Attribute>> operationalAttributes) 149 { 150 setDN(dn); 151 152 this.objectClasses = newMapIfNull(objectClasses); 153 this.userAttributes = newMapIfNull(userAttributes); 154 this.operationalAttributes = newMapIfNull(operationalAttributes); 155 } 156 157 /** 158 * Returns a new Map if the passed in Map is null. 159 * 160 * @param <K> 161 * the type of the key 162 * @param <V> 163 * the type of the value 164 * @param map 165 * the map to test 166 * @return a new Map if the passed in Map is null. 167 */ 168 private <K, V> Map<K, V> newMapIfNull(Map<K, V> map) 169 { 170 if (map != null) 171 { 172 return map; 173 } 174 return new HashMap<>(); 175 } 176 177 178 179 /** 180 * Retrieves the distinguished name for this entry. 181 * 182 * @return The distinguished name for this entry. 183 */ 184 public DN getName() 185 { 186 return dn; 187 } 188 189 190 191 /** 192 * Specifies the distinguished name for this entry. 193 * 194 * @param dn The distinguished name for this entry. 195 */ 196 public void setDN(DN dn) 197 { 198 if (dn == null) 199 { 200 this.dn = DN.rootDN(); 201 } 202 else 203 { 204 this.dn = dn; 205 } 206 207 attachment = null; 208 } 209 210 211 212 /** 213 * Retrieves the set of objectclasses defined for this entry. The 214 * caller should be allowed to modify the contents of this list, but 215 * if it does then it should also invalidate the attachment. 216 * 217 * @return The set of objectclasses defined for this entry. 218 */ 219 public Map<ObjectClass,String> getObjectClasses() 220 { 221 return objectClasses; 222 } 223 224 225 226 /** 227 * Indicates whether this entry has the specified objectclass. 228 * 229 * @param objectClass The objectclass for which to make the 230 * determination. 231 * 232 * @return <CODE>true</CODE> if this entry has the specified 233 * objectclass, or <CODE>false</CODE> if not. 234 */ 235 public boolean hasObjectClass(ObjectClass objectClass) 236 { 237 return objectClasses.containsKey(objectClass); 238 } 239 240 241 242 /** 243 * Retrieves the structural objectclass for this entry. 244 * 245 * @return The structural objectclass for this entry, or 246 * <CODE>null</CODE> if there is none for some reason. If 247 * there are multiple structural classes in the entry, then 248 * the first will be returned. 249 */ 250 public ObjectClass getStructuralObjectClass() 251 { 252 ObjectClass structuralClass = null; 253 254 for (ObjectClass oc : objectClasses.keySet()) 255 { 256 if (oc.getObjectClassType() == ObjectClassType.STRUCTURAL) 257 { 258 if (structuralClass == null) 259 { 260 structuralClass = oc; 261 } 262 else if (oc.isDescendantOf(structuralClass)) 263 { 264 structuralClass = oc; 265 } 266 } 267 } 268 269 return structuralClass; 270 } 271 272 273 274 /** 275 * Adds the provided objectClass to this entry. 276 * 277 * @param oc The objectClass to add to this entry. 278 * 279 * @throws DirectoryException If a problem occurs while attempting 280 * to add the objectclass to this 281 * entry. 282 */ 283 public void addObjectClass(ObjectClass oc) 284 throws DirectoryException 285 { 286 attachment = null; 287 288 if (objectClasses.containsKey(oc)) 289 { 290 LocalizableMessage message = ERR_ENTRY_ADD_DUPLICATE_OC.get(oc.getNameOrOID(), dn); 291 throw new DirectoryException(OBJECTCLASS_VIOLATION, message); 292 } 293 294 objectClasses.put(oc, oc.getNameOrOID()); 295 } 296 297 298 299 /** 300 * Retrieves the entire set of attributes for this entry. This will 301 * include both user and operational attributes. The caller must 302 * not modify the contents of this list. Also note that this method 303 * is less efficient than calling either (or both) 304 * <CODE>getUserAttributes</CODE> or 305 * <CODE>getOperationalAttributes</CODE>, so it should only be used 306 * when calls to those methods are not appropriate. 307 * 308 * @return The entire set of attributes for this entry. 309 */ 310 public List<Attribute> getAttributes() 311 { 312 // Estimate the size. 313 int size = userAttributes.size() + operationalAttributes.size(); 314 315 final List<Attribute> attributes = new ArrayList<>(size); 316 for (List<Attribute> attrs : userAttributes.values()) 317 { 318 attributes.addAll(attrs); 319 } 320 for (List<Attribute> attrs : operationalAttributes.values()) 321 { 322 attributes.addAll(attrs); 323 } 324 return attributes; 325 } 326 327 /** 328 * Retrieves the entire set of user (i.e., non-operational) 329 * attributes for this entry. The caller should be allowed to 330 * modify the contents of this list, but if it does then it should 331 * also invalidate the attachment. 332 * 333 * @return The entire set of user attributes for this entry. 334 */ 335 public Map<AttributeType,List<Attribute>> getUserAttributes() 336 { 337 return userAttributes; 338 } 339 340 341 342 /** 343 * Retrieves the entire set of operational attributes for this 344 * entry. The caller should be allowed to modify the contents of 345 * this list, but if it does then it should also invalidate the 346 * attachment. 347 * 348 * @return The entire set of operational attributes for this entry. 349 */ 350 public Map<AttributeType,List<Attribute>> getOperationalAttributes() 351 { 352 return operationalAttributes; 353 } 354 355 356 357 /** 358 * Retrieves an attribute holding the objectclass information for 359 * this entry. The returned attribute must not be altered. 360 * 361 * @return An attribute holding the objectclass information for 362 * this entry, or <CODE>null</CODE> if it does not have any 363 * objectclass information. 364 */ 365 public Attribute getObjectClassAttribute() 366 { 367 if (objectClasses == null || objectClasses.isEmpty()) 368 { 369 return null; 370 } 371 372 if(objectClassAttribute == null) 373 { 374 AttributeBuilder builder = new AttributeBuilder(CoreSchema.getObjectClassAttributeType()); 375 builder.addAllStrings(objectClasses.values()); 376 objectClassAttribute = builder.toAttribute(); 377 } 378 379 return objectClassAttribute; 380 } 381 382 383 384 /** 385 * Indicates whether this entry contains the specified attribute. 386 * Any subordinate attribute of the specified attribute will also be 387 * used in the determination. 388 * 389 * @param attributeType 390 * The attribute type for which to make the determination. 391 * @return <CODE>true</CODE> if this entry contains the specified 392 * attribute, or <CODE>false</CODE> if not. 393 */ 394 public boolean hasAttribute(AttributeType attributeType) 395 { 396 return hasAttribute(AttributeDescription.create(attributeType), true); 397 } 398 399 400 /** 401 * Indicates whether this entry contains the specified attribute. 402 * 403 * @param attributeType The attribute type for which to 404 * make the determination. 405 * @param includeSubordinates Whether to include any subordinate 406 * attributes of the attribute type 407 * being retrieved. 408 * 409 * @return <CODE>true</CODE> if this entry contains the specified 410 * attribute, or <CODE>false</CODE> if not. 411 */ 412 public boolean hasAttribute(AttributeType attributeType, 413 boolean includeSubordinates) 414 { 415 return hasAttribute(AttributeDescription.create(attributeType), includeSubordinates); 416 } 417 418 419 420 /** 421 * Indicates whether this entry contains the specified attribute 422 * with all of the options in the provided set. Any subordinate 423 * attribute of the specified attribute will also be used in the 424 * determination. 425 * 426 * @param attributeDescription 427 * The attribute description for which to make the determination. 428 * @return <CODE>true</CODE> if this entry contains the specified 429 * attribute, or <CODE>false</CODE> if not. 430 */ 431 public boolean hasAttribute(AttributeDescription attributeDescription) 432 { 433 return hasAttribute(attributeDescription, true); 434 } 435 436 /** 437 * Indicates whether this entry contains the specified attribute with all of the options in the 438 * provided set. 439 * 440 * @param attributeDescription 441 * The attribute description for which to make the determination. 442 * @param includeSubordinates 443 * Whether to include any subordinate attributes of the attribute type being retrieved. 444 * @return <CODE>true</CODE> if this entry contains the specified attribute, or <CODE>false</CODE> 445 * if not. 446 */ 447 public boolean hasAttribute(AttributeDescription attributeDescription, boolean includeSubordinates) 448 { 449 AttributeType attributeType = attributeDescription.getAttributeType(); 450 if (attributeType.isObjectClass()) 451 { 452 return !objectClasses.isEmpty() && !attributeDescription.hasOptions(); 453 } 454 455 if (!includeSubordinates) 456 { 457 // It's possible that there could be an attribute without any 458 // values, which we should treat as not having the requested attribute. 459 Attribute attribute = getExactAttribute(attributeDescription); 460 return attribute != null && !attribute.isEmpty(); 461 } 462 463 return hasAttributeOrSubType(attributeDescription, userAttributes) 464 || hasAttributeOrSubType(attributeDescription, operationalAttributes); 465 } 466 467 /** 468 * Returns the attributes Map corresponding to the operational status of the 469 * supplied attribute type. 470 * 471 * @param attrType 472 * the attribute type 473 * @return the user of operational attributes Map 474 */ 475 private Map<AttributeType, List<Attribute>> getUserOrOperationalAttributes( 476 AttributeType attrType) 477 { 478 if (attrType.isOperational()) 479 { 480 return operationalAttributes; 481 } 482 return userAttributes; 483 } 484 485 /** 486 * Return the List of attributes for the passed in attribute type. 487 * 488 * @param attrType 489 * the attribute type 490 * @return the List of user or operational attributes 491 */ 492 private List<Attribute> getAttributes(AttributeType attrType) 493 { 494 return getUserOrOperationalAttributes(attrType).get(attrType); 495 } 496 497 /** 498 * Puts the supplied List of attributes for the passed in attribute type into 499 * the map of attributes. 500 * 501 * @param attrType 502 * the attribute type 503 * @param attributes 504 * the List of user or operational attributes to put 505 */ 506 private void putAttributes(AttributeType attrType, List<Attribute> attributes) 507 { 508 getUserOrOperationalAttributes(attrType).put(attrType, attributes); 509 } 510 511 /** 512 * Removes the List of attributes for the passed in attribute type from the 513 * map of attributes. 514 * 515 * @param attrType 516 * the attribute type 517 */ 518 private void removeAttributes(AttributeType attrType) 519 { 520 getUserOrOperationalAttributes(attrType).remove(attrType); 521 } 522 523 /** 524 * Retrieves the requested attribute element(s) for the specified 525 * attribute type. The list returned may include multiple elements 526 * if the same attribute exists in the entry multiple times with 527 * different sets of options. It may also include any subordinate 528 * attributes of the attribute being retrieved. 529 * 530 * @param attributeType 531 * The attribute type to retrieve. 532 * @return The requested attribute element(s) for the specified 533 * attribute type, or an empty list if the specified 534 * attribute type is not present in this entry. 535 */ 536 public List<Attribute> getAttribute(AttributeType attributeType) 537 { 538 return getAttribute(attributeType, true); 539 } 540 541 542 /** 543 * Retrieves the requested attribute element(s) for the specified 544 * attribute type. The list returned may include multiple elements 545 * if the same attribute exists in the entry multiple times with 546 * different sets of options. 547 * 548 * @param attributeType The attribute type to retrieve. 549 * @param includeSubordinates Whether to include any subordinate 550 * attributes of the attribute type 551 * being retrieved. 552 * 553 * @return The requested attribute element(s) for the specified 554 * attribute type, or an empty list if the specified 555 * attribute type is not present in this entry. 556 */ 557 public List<Attribute> getAttribute(AttributeType attributeType, 558 boolean includeSubordinates) 559 { 560 if (includeSubordinates && !attributeType.isObjectClass()) 561 { 562 List<Attribute> attributes = new LinkedList<>(); 563 addAttributeTypeOrSubTypeValue(attributes, attributeType, userAttributes); 564 addAttributeTypeOrSubTypeValue(attributes, attributeType, operationalAttributes); 565 return attributes; 566 } 567 568 List<Attribute> attributes = userAttributes.get(attributeType); 569 if (attributes != null) 570 { 571 return attributes; 572 } 573 attributes = operationalAttributes.get(attributeType); 574 if (attributes != null) 575 { 576 return attributes; 577 } 578 if (attributeType.isObjectClass() && !objectClasses.isEmpty()) 579 { 580 return newArrayList(getObjectClassAttribute()); 581 } 582 return Collections.emptyList(); 583 } 584 585 private void addAttributeTypeOrSubTypeValue(Collection<Attribute> results, AttributeType attrType, 586 Map<AttributeType, List<Attribute>> attrsMap) 587 { 588 for (Map.Entry<AttributeType, List<Attribute>> mapEntry : attrsMap.entrySet()) 589 { 590 if (attrType.isSuperTypeOf(mapEntry.getKey())) 591 { 592 results.addAll(mapEntry.getValue()); 593 } 594 } 595 } 596 597 private void addAttributeTypeOrSubTypeValue(Collection<Attribute> results, AttributeDescription attrDesc, 598 Map<AttributeType, List<Attribute>> attrsMap) 599 { 600 for (Map.Entry<AttributeType, List<Attribute>> mapEntry : attrsMap.entrySet()) 601 { 602 if (!attrDesc.getAttributeType().isSuperTypeOf(mapEntry.getKey())) 603 { 604 continue; 605 } 606 607 for (Attribute attribute : mapEntry.getValue()) 608 { 609 if (attrDesc.isSuperTypeOf(attribute.getAttributeDescription())) 610 { 611 results.add(attribute); 612 } 613 } 614 } 615 } 616 617 private boolean hasAttributeOrSubType(AttributeDescription attrDesc, Map<AttributeType, List<Attribute>> attrsMap) 618 { 619 for (Map.Entry<AttributeType, List<Attribute>> mapEntry : attrsMap.entrySet()) 620 { 621 if (!attrDesc.getAttributeType().isSuperTypeOf(mapEntry.getKey())) 622 { 623 continue; 624 } 625 626 for (Attribute attribute : mapEntry.getValue()) 627 { 628 // It's possible that there could be an attribute without any values, 629 // which we should treat as not having the requested attribute. 630 if (!attribute.isEmpty() && attrDesc.isSuperTypeOf(attribute.getAttributeDescription())) 631 { 632 return true; 633 } 634 } 635 } 636 return false; 637 } 638 639 /** 640 * Retrieves the requested attribute element(s) for the attribute 641 * with the specified name or OID. The list returned may include 642 * multiple elements if the same attribute exists in the entry 643 * multiple times with different sets of options. It may also 644 * include any subordinate attributes of the attribute being 645 * retrieved. 646 * <BR><BR> 647 * Note that this method should only be used in cases in which the 648 * Directory Server schema has no reference of an attribute type 649 * with the specified name. It is not as accurate or efficient as 650 * the version of this method that takes an 651 * <CODE>AttributeType</CODE> argument. 652 * 653 * @param nameOrOID The name or OID of the attribute to return 654 * @return The requested attribute element(s) for the specified 655 * attribute type, or an empty list if the specified 656 * attribute type is not present in this entry. 657 */ 658 public List<Attribute> getAttribute(String nameOrOID) 659 { 660 for (AttributeType attr : userAttributes.keySet()) 661 { 662 if (attr.hasNameOrOID(nameOrOID)) 663 { 664 return getAttribute(attr); 665 } 666 } 667 668 for (AttributeType attr : operationalAttributes.keySet()) 669 { 670 if (attr.hasNameOrOID(nameOrOID)) 671 { 672 return getAttribute(attr); 673 } 674 } 675 676 if (CoreSchema.getObjectClassAttributeType().hasNameOrOID(nameOrOID) 677 && !objectClasses.isEmpty()) 678 { 679 return newLinkedList(getObjectClassAttribute()); 680 } 681 return Collections.emptyList(); 682 } 683 684 /** 685 * Retrieves the requested attribute element(s) for the specified 686 * attribute description. The list returned may include multiple elements 687 * if the same attribute exists in the entry multiple times with 688 * different sets of options. It may also include any subordinate 689 * attributes of the attribute being retrieved. 690 * 691 * @param attributeDescription The attribute description to retrieve. 692 * @return The requested attribute element(s) for the specified 693 * attribute type, or an empty list if the specified 694 * attribute type is not present in this entry with the 695 * provided set of options. 696 */ 697 public List<Attribute> getAttribute(AttributeDescription attributeDescription) 698 { 699 AttributeType attributeType = attributeDescription.getAttributeType(); 700 701 final List<Attribute> attributes = new LinkedList<>(); 702 if (!attributeType.isObjectClass()) 703 { 704 addAttributeTypeOrSubTypeValue(attributes, attributeDescription, userAttributes); 705 addAttributeTypeOrSubTypeValue(attributes, attributeDescription, operationalAttributes); 706 return attributes; 707 } 708 709 List<Attribute> attrs = userAttributes.get(attributeType); 710 if (attrs == null) 711 { 712 attrs = operationalAttributes.get(attributeType); 713 if (attrs == null) 714 { 715 if (attributeType.isObjectClass() 716 && !objectClasses.isEmpty() 717 && !attributeDescription.hasOptions()) 718 { 719 attributes.add(getObjectClassAttribute()); 720 return attributes; 721 } 722 return Collections.emptyList(); 723 } 724 } 725 attributes.addAll(attrs); 726 727 onlyKeepAttributesWithAllOptions(attributes, attributeDescription); 728 729 return attributes; 730 } 731 732 /** 733 * Returns a parser for the named attribute contained in this entry. 734 * <p> 735 * The attribute description will be decoded using the schema associated 736 * with this entry (usually the default schema). 737 * 738 * @param attributeDescription 739 * The name of the attribute to be parsed. 740 * @return A parser for the named attribute. 741 * @throws LocalizedIllegalArgumentException 742 * If {@code attributeDescription} could not be decoded using 743 * the schema associated with this entry. 744 * @throws NullPointerException 745 * If {@code attributeDescription} was {@code null}. 746 */ 747 public AttributeParser parseAttribute(String attributeDescription) 748 throws LocalizedIllegalArgumentException, NullPointerException 749 { 750 final List<Attribute> attribute = getAttribute(attributeDescription); 751 return AttributeParser.parseAttribute(!attribute.isEmpty() ? attribute.get(0) : null); 752 } 753 754 755 756 /** 757 * Indicates whether this entry contains the specified user 758 * attribute. 759 * 760 * @param attributeType 761 * The attribute type for which to make the determination. 762 * @return <CODE>true</CODE> if this entry contains the specified 763 * user attribute, or <CODE>false</CODE> if not. 764 */ 765 public boolean hasUserAttribute(AttributeType attributeType) 766 { 767 return hasAttribute(userAttributes, attributeType); 768 } 769 770 771 772 /** 773 * Retrieves the requested user attribute element(s) for the 774 * specified attribute type. The list returned may include multiple 775 * elements if the same attribute exists in the entry multiple times 776 * with different sets of options. 777 * 778 * @param attributeType The attribute type to retrieve. 779 * 780 * @return The requested attribute element(s) for the specified 781 * attribute type, or an empty list if there is no such 782 * user attribute. 783 */ 784 public List<Attribute> getUserAttribute(AttributeType attributeType) 785 { 786 return getAttribute(attributeType, userAttributes); 787 } 788 789 private List<Attribute> getAttribute(AttributeType attributeType, 790 Map<AttributeType, List<Attribute>> attrs) 791 { 792 List<Attribute> results = new LinkedList<>(); 793 addAttributeTypeOrSubTypeValue(results, attributeType, attrs); 794 return results; 795 } 796 797 private List<Attribute> getAttribute(AttributeDescription attributeDescription, 798 Map<AttributeType, List<Attribute>> attrs) 799 { 800 List<Attribute> results = new LinkedList<>(); 801 addAttributeTypeOrSubTypeValue(results, attributeDescription, attrs); 802 return results; 803 } 804 805 /** 806 * Removes all the attributes that do not have all the supplied options. 807 * 808 * @param attributes 809 * the attributes to filter. 810 * @param attributeDescription 811 * contains the options to look for 812 */ 813 private void onlyKeepAttributesWithAllOptions(List<Attribute> attributes, AttributeDescription attributeDescription) 814 { 815 Iterator<Attribute> iterator = attributes.iterator(); 816 while (iterator.hasNext()) 817 { 818 Attribute a = iterator.next(); 819 if (!a.getAttributeDescription().isSubTypeOf(attributeDescription)) 820 { 821 iterator.remove(); 822 } 823 } 824 } 825 826 /** 827 * Indicates whether this entry contains the specified operational 828 * attribute. 829 * 830 * @param attributeType The attribute type for which to make the 831 * determination. 832 * 833 * @return <CODE>true</CODE> if this entry contains the specified 834 * operational attribute, or <CODE>false</CODE> if not. 835 */ 836 public boolean hasOperationalAttribute(AttributeType attributeType) 837 { 838 return hasAttribute(operationalAttributes, attributeType); 839 } 840 841 private boolean hasAttribute(Map<AttributeType, List<Attribute>> attributes, AttributeType attributeType) 842 { 843 for (AttributeType key : attributes.keySet()) 844 { 845 if (attributeType.isSuperTypeOf(key)) 846 { 847 return true; 848 } 849 } 850 return false; 851 } 852 853 /** 854 * Retrieves the requested operational attribute element(s) for the 855 * specified attribute type. The list returned may include multiple 856 * elements if the same attribute exists in the entry multiple times 857 * with different sets of options. 858 * 859 * @param attributeType The attribute type to retrieve. 860 * 861 * @return The requested attribute element(s) for the specified 862 * attribute type, or an empty list if there is no such 863 * operational attribute. 864 */ 865 public List<Attribute> getOperationalAttribute(AttributeType attributeType) 866 { 867 return getAttribute(attributeType, operationalAttributes); 868 } 869 870 871 872 873 /** 874 * Retrieves the requested operational attribute element(s) for the 875 * specified attribute type. The list returned may include multiple 876 * elements if the same attribute exists in the entry multiple times 877 * with different sets of options. 878 * 879 * @param attributeDescription The attribute description to retrieve. 880 * 881 * @return The requested attribute element(s) for the specified 882 * attribute type, or an empty list if there is no such 883 * operational attribute with the specified set of options. 884 */ 885 public List<Attribute> getOperationalAttribute(AttributeDescription attributeDescription) 886 { 887 return getAttribute(attributeDescription, operationalAttributes); 888 } 889 890 891 892 /** 893 * Puts the provided attribute in this entry. If an attribute 894 * already exists with the provided type, it will be overwritten. 895 * Otherwise, a new attribute will be added. Note that no 896 * validation will be performed. 897 * 898 * @param attributeType The attribute type for the set of 899 * attributes to add. 900 * @param attributeList The set of attributes to add for the given 901 * type. 902 */ 903 public void putAttribute(AttributeType attributeType, 904 List<Attribute> attributeList) 905 { 906 attachment = null; 907 908 909 // See if there is already a set of attributes with the specified 910 // type. If so, then overwrite it. 911 List<Attribute> attrList = userAttributes.get(attributeType); 912 if (attrList != null) 913 { 914 userAttributes.put(attributeType, attributeList); 915 return; 916 } 917 918 attrList = operationalAttributes.get(attributeType); 919 if (attrList != null) 920 { 921 operationalAttributes.put(attributeType, attributeList); 922 return; 923 } 924 925 putAttributes(attributeType, attributeList); 926 } 927 928 929 930 /** 931 * Ensures that this entry contains the provided attribute and its 932 * values. If an attribute with the provided type already exists, 933 * then its attribute values will be merged. 934 * <p> 935 * This method handles object class additions but will not perform 936 * any object class validation. In particular, it will create 937 * default object classes when an object class is unknown. 938 * <p> 939 * This method implements LDAP modification add semantics, with the 940 * exception that it allows empty attributes to be added. 941 * 942 * @param attribute 943 * The attribute to add or merge with this entry. 944 * @param duplicateValues 945 * A list to which any duplicate values will be added. 946 */ 947 public void addAttribute(Attribute attribute, List<ByteString> duplicateValues) 948 { 949 setAttribute(attribute, duplicateValues, false /* merge */); 950 } 951 952 953 954 /** 955 * Puts the provided attribute into this entry. If an attribute with 956 * the provided type and options already exists, then it will be 957 * replaced. If the provided attribute is empty then any existing 958 * attribute will be completely removed. 959 * <p> 960 * This method handles object class replacements but will not 961 * perform any object class validation. In particular, it will 962 * create default object classes when an object class is unknown. 963 * <p> 964 * This method implements LDAP modification replace semantics. 965 * 966 * @param attribute 967 * The attribute to replace in this entry. 968 */ 969 public void replaceAttribute(Attribute attribute) 970 { 971 // There can never be duplicate values for a replace. 972 setAttribute(attribute, null, true /* replace */); 973 } 974 975 976 977 /** 978 * Increments an attribute in this entry by the amount specified in 979 * the provided attribute. 980 * 981 * @param attribute 982 * The attribute identifying the attribute to be increment 983 * and the amount it is to be incremented by. The attribute 984 * must contain a single value. 985 * @throws DirectoryException 986 * If a problem occurs while attempting to increment the 987 * provided attribute. This may occur if the provided 988 * attribute was not single valued or if it could not be 989 * parsed as an integer of if the existing attribute 990 * values could not be parsed as integers. 991 */ 992 public void incrementAttribute(Attribute attribute) throws DirectoryException 993 { 994 AttributeDescription attrDesc = attribute.getAttributeDescription(); 995 Attribute a = getExactAttribute(attrDesc); 996 if (a == null) 997 { 998 LocalizableMessage message = ERR_ENTRY_INCREMENT_NO_SUCH_ATTRIBUTE.get(attrDesc); 999 throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE, message); 1000 } 1001 1002 // Decode the increment. 1003 Iterator<ByteString> i = attribute.iterator(); 1004 if (!i.hasNext()) 1005 { 1006 LocalizableMessage message = ERR_ENTRY_INCREMENT_INVALID_VALUE_COUNT.get(attrDesc); 1007 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 1008 } 1009 1010 String incrementValue = i.next().toString(); 1011 long increment = parseLong(incrementValue, attrDesc); 1012 1013 if (i.hasNext()) 1014 { 1015 LocalizableMessage message = ERR_ENTRY_INCREMENT_INVALID_VALUE_COUNT.get(attrDesc); 1016 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 1017 } 1018 1019 // Increment each attribute value by the specified amount. 1020 AttributeBuilder builder = new AttributeBuilder(a.getAttributeDescription()); 1021 for (ByteString v : a) 1022 { 1023 long currentValue = parseLong(v.toString(), attrDesc); 1024 long newValue = currentValue + increment; 1025 builder.add(String.valueOf(newValue)); 1026 } 1027 1028 replaceAttribute(builder.toAttribute()); 1029 } 1030 1031 private long parseLong(String value, AttributeDescription attrDesc) throws DirectoryException 1032 { 1033 try 1034 { 1035 return Long.parseLong(value); 1036 } 1037 catch (NumberFormatException e) 1038 { 1039 LocalizableMessage message = ERR_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT.get(attrDesc); 1040 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 1041 } 1042 } 1043 1044 /** 1045 * Removes all instances of the specified attribute type from this 1046 * entry, including any instances with options. If the provided 1047 * attribute type is the objectclass type, then all objectclass 1048 * values will be removed (but must be replaced for the entry to be 1049 * valid). If the specified attribute type is not present in this 1050 * entry, then this method will have no effect. 1051 * 1052 * @param attributeType 1053 * The attribute type for the attribute to remove from this 1054 * entry. 1055 * @return <CODE>true</CODE> if the attribute was found and 1056 * removed, or <CODE>false</CODE> if it was not present in 1057 * the entry. 1058 */ 1059 public boolean removeAttribute(AttributeType attributeType) 1060 { 1061 attachment = null; 1062 1063 if (attributeType.isObjectClass()) 1064 { 1065 objectClasses.clear(); 1066 return true; 1067 } 1068 return userAttributes.remove(attributeType) != null 1069 || operationalAttributes.remove(attributeType) != null; 1070 } 1071 1072 1073 1074 /** 1075 * Ensures that this entry does not contain the provided attribute 1076 * values. If the provided attribute is empty, then all values of 1077 * the associated attribute type will be removed. Otherwise, only 1078 * the specified values will be removed. 1079 * <p> 1080 * This method handles object class deletions. 1081 * <p> 1082 * This method implements LDAP modification delete semantics. 1083 * 1084 * @param attribute 1085 * The attribute containing the information to use to 1086 * perform the removal. 1087 * @param missingValues 1088 * A list to which any values contained in the provided 1089 * attribute but not present in the entry will be added. 1090 * @return <CODE>true</CODE> if the attribute type was present and 1091 * the specified values that were present were removed, or 1092 * <CODE>false</CODE> if the attribute type was not 1093 * present in the entry. If the attribute type was present 1094 * but only contained some of the values in the provided 1095 * attribute, then this method will return <CODE>true</CODE> 1096 * but will add those values to the provided list. 1097 */ 1098 public boolean removeAttribute(Attribute attribute, 1099 List<ByteString> missingValues) 1100 { 1101 attachment = null; 1102 1103 AttributeDescription attrDesc = attribute.getAttributeDescription(); 1104 AttributeType attrType = attrDesc.getAttributeType(); 1105 if (attrType.isObjectClass()) 1106 { 1107 if (attribute.isEmpty()) 1108 { 1109 objectClasses.clear(); 1110 return true; 1111 } 1112 1113 boolean allSuccessful = true; 1114 1115 MatchingRule rule = attrType.getEqualityMatchingRule(); 1116 for (ByteString v : attribute) 1117 { 1118 String ocName = toLowerName(rule, v); 1119 1120 boolean matchFound = false; 1121 for (ObjectClass oc : objectClasses.keySet()) 1122 { 1123 if (oc.hasNameOrOID(ocName)) 1124 { 1125 matchFound = true; 1126 objectClasses.remove(oc); 1127 break; 1128 } 1129 } 1130 1131 if (!matchFound) 1132 { 1133 allSuccessful = false; 1134 missingValues.add(v); 1135 } 1136 } 1137 1138 return allSuccessful; 1139 } 1140 1141 List<Attribute> attributes = getAttributes(attrType); 1142 if (attributes == null) 1143 { 1144 // There are no attributes with the same attribute type. 1145 for (ByteString v : attribute) 1146 { 1147 missingValues.add(v); 1148 } 1149 return false; 1150 } 1151 1152 // There are already attributes with the same attribute type. 1153 for (int i = 0; i < attributes.size(); i++) 1154 { 1155 Attribute a = attributes.get(i); 1156 if (a.getAttributeDescription().equals(attrDesc)) 1157 { 1158 if (attribute.isEmpty()) 1159 { 1160 // Remove the entire attribute. 1161 attributes.remove(i); 1162 } 1163 else 1164 { 1165 // Remove Specified values. 1166 AttributeBuilder builder = new AttributeBuilder(a); 1167 for (ByteString v : attribute) 1168 { 1169 if (!builder.remove(v)) 1170 { 1171 missingValues.add(v); 1172 } 1173 } 1174 1175 // Remove / replace the attribute as necessary. 1176 if (!builder.isEmpty()) 1177 { 1178 attributes.set(i, builder.toAttribute()); 1179 } 1180 else 1181 { 1182 attributes.remove(i); 1183 } 1184 } 1185 1186 // If the attribute list is now empty remove it. 1187 if (attributes.isEmpty()) 1188 { 1189 removeAttributes(attrType); 1190 } 1191 1192 return true; 1193 } 1194 } 1195 1196 // No matching attribute found. 1197 return false; 1198 } 1199 1200 private String toLowerName(MatchingRule rule, ByteString value) 1201 { 1202 try 1203 { 1204 return normalize(rule, value).toString(); 1205 } 1206 catch (Exception e) 1207 { 1208 logger.traceException(e); 1209 return toLowerCase(value.toString()); 1210 } 1211 } 1212 1213 /** 1214 * Indicates whether this entry contains the specified attribute value. 1215 * 1216 * @param attributeDescription 1217 * The attribute description for the attribute. 1218 * @param value 1219 * The value for the attribute. 1220 * @return {@code true} if this entry contains the specified attribute value, {@code false} 1221 * otherwise. 1222 */ 1223 public boolean hasValue(AttributeDescription attributeDescription, ByteString value) 1224 { 1225 Attribute attr = getExactAttribute(attributeDescription); 1226 return attr != null && attr.contains(value); 1227 } 1228 1229 /** 1230 * Indicates whether this entry contains the specified attribute value. 1231 * 1232 * @param attributeType 1233 * The attribute type for the attribute. 1234 * @param value 1235 * The value for the attribute. 1236 * @return {@code true} if this entry contains the specified attribute value, {@code false} 1237 * otherwise. 1238 */ 1239 public boolean hasValue(AttributeType attributeType, ByteString value) 1240 { 1241 for (Attribute a : getAttribute(attributeType)) 1242 { 1243 if (!a.getAttributeDescription().hasOptions() && a.contains(value)) 1244 { 1245 return true; 1246 } 1247 } 1248 return false; 1249 } 1250 1251 1252 1253 /** 1254 * Applies the provided modification to this entry. No schema 1255 * checking will be performed. 1256 * 1257 * @param mod The modification to apply to this entry. 1258 * @param relaxConstraints indicates if the modification 1259 * constraints are relaxed to match 1260 * the ones of a set (add existing 1261 * value and delete absent value do not fail) 1262 * 1263 * @throws DirectoryException If a problem occurs while 1264 * attempting to apply the 1265 * modification. Note 1266 * that even if a problem occurs, then 1267 * the entry may have been altered in some way. 1268 */ 1269 public void applyModification(Modification mod, boolean relaxConstraints) 1270 throws DirectoryException 1271 { 1272 AttributeType t = mod.getAttribute().getAttributeDescription().getAttributeType(); 1273 if (t.isObjectClass()) 1274 { 1275 applyModificationToObjectclass(mod, relaxConstraints); 1276 } 1277 else 1278 { 1279 applyModificationToNonObjectclass(mod, relaxConstraints); 1280 } 1281 } 1282 1283 private void applyModificationToObjectclass(Modification mod, boolean relaxConstraints) throws DirectoryException 1284 { 1285 Attribute a = mod.getAttribute(); 1286 1287 Map<ObjectClass, String> ocs = new LinkedHashMap<>(); 1288 for (ByteString v : a) 1289 { 1290 String ocName = v.toString(); 1291 ocs.put(DirectoryServer.getSchema().getObjectClass(ocName), ocName); 1292 } 1293 1294 AttributeDescription attrDesc = a.getAttributeDescription(); 1295 switch (mod.getModificationType().asEnum()) 1296 { 1297 case ADD: 1298 for (ObjectClass oc : ocs.keySet()) 1299 { 1300 if (objectClasses.containsKey(oc)) 1301 { 1302 if (!relaxConstraints) 1303 { 1304 LocalizableMessage message = ERR_ENTRY_DUPLICATE_VALUES.get(attrDesc); 1305 throw new DirectoryException(ATTRIBUTE_OR_VALUE_EXISTS, message); 1306 } 1307 } 1308 else 1309 { 1310 objectClasses.put(oc, ocs.get(oc)); 1311 } 1312 } 1313 objectClassAttribute = null; 1314 break; 1315 1316 case DELETE: 1317 for (ObjectClass oc : ocs.keySet()) 1318 { 1319 if (objectClasses.remove(oc) == null && !relaxConstraints) 1320 { 1321 LocalizableMessage message = ERR_ENTRY_NO_SUCH_VALUE.get(attrDesc); 1322 throw new DirectoryException(NO_SUCH_ATTRIBUTE, message); 1323 } 1324 } 1325 objectClassAttribute = null; 1326 break; 1327 1328 case REPLACE: 1329 objectClasses = ocs; 1330 objectClassAttribute = null; 1331 break; 1332 1333 case INCREMENT: 1334 LocalizableMessage message = ERR_ENTRY_OC_INCREMENT_NOT_SUPPORTED.get(); 1335 throw new DirectoryException(CONSTRAINT_VIOLATION, message); 1336 1337 default: 1338 message = ERR_ENTRY_UNKNOWN_MODIFICATION_TYPE.get(mod.getModificationType()); 1339 throw new DirectoryException(UNWILLING_TO_PERFORM, message); 1340 } 1341 } 1342 1343 private void applyModificationToNonObjectclass(Modification mod, boolean relaxConstraints) throws DirectoryException 1344 { 1345 Attribute a = mod.getAttribute(); 1346 switch (mod.getModificationType().asEnum()) 1347 { 1348 case ADD: 1349 List<ByteString> duplicateValues = new LinkedList<>(); 1350 addAttribute(a, duplicateValues); 1351 if (!duplicateValues.isEmpty() && !relaxConstraints) 1352 { 1353 LocalizableMessage message = ERR_ENTRY_DUPLICATE_VALUES.get(a.getAttributeDescription()); 1354 throw new DirectoryException(ATTRIBUTE_OR_VALUE_EXISTS, message); 1355 } 1356 break; 1357 1358 case DELETE: 1359 List<ByteString> missingValues = new LinkedList<>(); 1360 removeAttribute(a, missingValues); 1361 if (!missingValues.isEmpty() && !relaxConstraints) 1362 { 1363 LocalizableMessage message = ERR_ENTRY_NO_SUCH_VALUE.get(a.getAttributeDescription()); 1364 throw new DirectoryException(NO_SUCH_ATTRIBUTE, message); 1365 } 1366 break; 1367 1368 case REPLACE: 1369 replaceAttribute(a); 1370 break; 1371 1372 case INCREMENT: 1373 incrementAttribute(a); 1374 break; 1375 1376 default: 1377 LocalizableMessage message = ERR_ENTRY_UNKNOWN_MODIFICATION_TYPE.get(mod.getModificationType()); 1378 throw new DirectoryException(UNWILLING_TO_PERFORM, message); 1379 } 1380 } 1381 1382 /** 1383 * Applies the provided modification to this entry. No schema 1384 * checking will be performed. 1385 * 1386 * @param mod The modification to apply to this entry. 1387 * 1388 * @throws DirectoryException If a problem occurs while attempting 1389 * to apply the modification. Note 1390 * that even if a problem occurs, then 1391 * the entry may have been altered in some way. 1392 */ 1393 public void applyModification(Modification mod) throws DirectoryException 1394 { 1395 applyModification(mod, false); 1396 } 1397 1398 /** 1399 * Applies all of the provided modifications to this entry. 1400 * 1401 * @param mods The modifications to apply to this entry. 1402 * 1403 * @throws DirectoryException If a problem occurs while attempting 1404 * to apply the modifications. Note 1405 * that even if a problem occurs, then 1406 * the entry may have been altered in some way. 1407 */ 1408 public void applyModifications(List<Modification> mods) 1409 throws DirectoryException 1410 { 1411 for (Modification m : mods) 1412 { 1413 applyModification(m, true); 1414 } 1415 } 1416 1417 1418 1419 /** 1420 * Indicates whether this entry conforms to the server's schema 1421 * requirements. The checks performed by this method include: 1422 * 1423 * <UL> 1424 * <LI>Make sure that all required attributes are present, either 1425 * in the list of user or operational attributes.</LI> 1426 * <LI>Make sure that all user attributes are allowed by at least 1427 * one of the objectclasses. The operational attributes will 1428 * not be checked in this manner.</LI> 1429 * <LI>Make sure that all single-valued attributes contained in 1430 * the entry have only a single value.</LI> 1431 * <LI>Make sure that the entry contains a single structural 1432 * objectclass.</LI> 1433 * <LI>Make sure that the entry complies with any defined name 1434 * forms, DIT content rules, and DIT structure rules.</LI> 1435 * </UL> 1436 * 1437 * @param parentEntry The entry that is the immediate 1438 * parent of this entry, which may 1439 * be checked for DIT structure rule 1440 * conformance. This may be 1441 * {@code null} if there is no 1442 * parent or if it is unavailable 1443 * to the caller. 1444 * @param parentProvided Indicates whether the caller 1445 * attempted to provide the parent. 1446 * If not, then the parent entry 1447 * will be loaded on demand if it is 1448 * required. 1449 * @param validateNameForms Indicates whether to validate the 1450 * entry against name form 1451 * definitions. This should only be 1452 * {@code true} for add and modify 1453 * DN operations, as well as for 1454 * for imports. 1455 * @param validateStructureRules Indicates whether to validate the 1456 * entry against DIT structure rule 1457 * definitions. This should only 1458 * be {@code true} for add and 1459 * modify DN operations. 1460 * @param invalidReason The buffer to which an 1461 * explanation will be appended if 1462 * this entry does not conform to 1463 * the server's schema 1464 * configuration. 1465 * 1466 * @return {@code true} if this entry conforms to the server's 1467 * schema requirements, or {@code false} if it does not. 1468 */ 1469 public boolean conformsToSchema(Entry parentEntry, 1470 boolean parentProvided, 1471 boolean validateNameForms, 1472 boolean validateStructureRules, 1473 LocalizableMessageBuilder invalidReason) 1474 { 1475 // Get the structural objectclass for the entry. If there isn't 1476 // one, or if there's more than one, then see if that's OK. 1477 AcceptRejectWarn structuralPolicy = 1478 DirectoryServer.getSingleStructuralObjectClassPolicy(); 1479 ObjectClass structuralClass = null; 1480 boolean multipleOCErrorLogged = false; 1481 for (ObjectClass oc : objectClasses.keySet()) 1482 { 1483 if (oc.getObjectClassType() == ObjectClassType.STRUCTURAL) 1484 { 1485 if (structuralClass == null || oc.isDescendantOf(structuralClass)) 1486 { 1487 structuralClass = oc; 1488 } 1489 else if (! structuralClass.isDescendantOf(oc)) 1490 { 1491 LocalizableMessage message = 1492 ERR_ENTRY_SCHEMA_MULTIPLE_STRUCTURAL_CLASSES.get( 1493 dn, 1494 structuralClass.getNameOrOID(), 1495 oc.getNameOrOID()); 1496 1497 if (structuralPolicy == AcceptRejectWarn.REJECT) 1498 { 1499 invalidReason.append(message); 1500 return false; 1501 } 1502 else if (structuralPolicy == AcceptRejectWarn.WARN 1503 && !multipleOCErrorLogged) 1504 { 1505 logger.error(message); 1506 multipleOCErrorLogged = true; 1507 } 1508 } 1509 } 1510 } 1511 1512 NameForm nameForm = null; 1513 DITContentRule ditContentRule = null; 1514 DITStructureRule ditStructureRule = null; 1515 if (structuralClass == null) 1516 { 1517 LocalizableMessage message = ERR_ENTRY_SCHEMA_NO_STRUCTURAL_CLASS.get(dn); 1518 if (structuralPolicy == AcceptRejectWarn.REJECT) 1519 { 1520 invalidReason.append(message); 1521 return false; 1522 } 1523 else if (structuralPolicy == AcceptRejectWarn.WARN) 1524 { 1525 logger.error(message); 1526 } 1527 1528 if (! checkAttributesAndObjectClasses(null, 1529 structuralPolicy, invalidReason)) 1530 { 1531 return false; 1532 } 1533 1534 } 1535 else 1536 { 1537 ditContentRule = DirectoryServer.getSchema().getDITContentRule(structuralClass); 1538 if (ditContentRule != null && ditContentRule.isObsolete()) 1539 { 1540 ditContentRule = null; 1541 } 1542 1543 if (! checkAttributesAndObjectClasses(ditContentRule, 1544 structuralPolicy, invalidReason)) 1545 { 1546 return false; 1547 } 1548 1549 if (validateNameForms) 1550 { 1551 /** 1552 * There may be multiple nameforms registered with this 1553 * structural objectclass.However, we need to select only one 1554 * of the nameforms and its corresponding DITstructure rule. 1555 * We will iterate over all the nameforms and see if atleast 1556 * one is acceptable before rejecting the entry. 1557 * DITStructureRules corresponding to other non-acceptable 1558 * nameforms are not applied. 1559 */ 1560 Collection<NameForm> forms = DirectoryServer.getSchema().getNameForm(structuralClass); 1561 if (forms != null) 1562 { 1563 List<NameForm> listForms = new ArrayList<NameForm>(forms); 1564 boolean matchFound = false; 1565 boolean obsolete = true; 1566 for(int index=0; index <listForms.size(); index++) 1567 { 1568 NameForm nf = listForms.get(index); 1569 if(!nf.isObsolete()) 1570 { 1571 obsolete = false; 1572 matchFound = checkNameForm(nf, structuralPolicy, invalidReason); 1573 1574 if(matchFound) 1575 { 1576 nameForm = nf; 1577 break; 1578 } 1579 1580 if(index != listForms.size()-1) 1581 { 1582 invalidReason.append(","); 1583 } 1584 } 1585 } 1586 if(! obsolete && !matchFound) 1587 { 1588 // We couldn't match this entry against any of the nameforms. 1589 return false; 1590 } 1591 } 1592 1593 1594 if (validateStructureRules && nameForm != null) 1595 { 1596 for (DITStructureRule ditRule : DirectoryServer.getSchema().getDITStructureRules(nameForm)) 1597 { 1598 if (!ditRule.isObsolete()) 1599 { 1600 ditStructureRule = ditRule; 1601 break; 1602 } 1603 } 1604 } 1605 } 1606 } 1607 1608 1609 // If there is a DIT content rule for this entry, then make sure 1610 // that the entry is in compliance with it. 1611 if (ditContentRule != null 1612 && !checkDITContentRule(ditContentRule, structuralPolicy, invalidReason)) 1613 { 1614 return false; 1615 } 1616 1617 return checkDITStructureRule(ditStructureRule, structuralClass, 1618 parentEntry, parentProvided, validateStructureRules, structuralPolicy, 1619 invalidReason); 1620 } 1621 1622 1623 1624 /** 1625 * Checks the attributes and object classes contained in this entry 1626 * to determine whether they conform to the server schema 1627 * requirements. 1628 * 1629 * @param ditContentRule The DIT content rule for this entry, if 1630 * any. 1631 * @param structuralPolicy The policy that should be used for 1632 * structural object class compliance. 1633 * @param invalidReason A buffer into which an invalid reason 1634 * may be added. 1635 * 1636 * @return {@code true} if this entry passes all of the checks, or 1637 * {@code false} if there are any failures. 1638 */ 1639 private boolean checkAttributesAndObjectClasses( 1640 DITContentRule ditContentRule, 1641 AcceptRejectWarn structuralPolicy, 1642 LocalizableMessageBuilder invalidReason) 1643 { 1644 // Make sure that we recognize all of the objectclasses, that all 1645 // auxiliary classes are allowed by the DIT content rule, and that 1646 // all attributes required by the object classes are present. 1647 for (ObjectClass o : objectClasses.keySet()) 1648 { 1649 if (DirectoryServer.getSchema().getObjectClass(o.getOID()).isPlaceHolder()) 1650 { 1651 invalidReason.append(ERR_ENTRY_SCHEMA_UNKNOWN_OC.get(dn, o.getNameOrOID())); 1652 return false; 1653 } 1654 1655 if (o.getObjectClassType() == ObjectClassType.AUXILIARY 1656 && ditContentRule != null && !ditContentRule.getAuxiliaryClasses().contains(o)) 1657 { 1658 LocalizableMessage message = 1659 ERR_ENTRY_SCHEMA_DISALLOWED_AUXILIARY_CLASS.get( 1660 dn, 1661 o.getNameOrOID(), 1662 ditContentRule.getNameOrOID()); 1663 if (structuralPolicy == AcceptRejectWarn.REJECT) 1664 { 1665 invalidReason.append(message); 1666 return false; 1667 } 1668 else if (structuralPolicy == AcceptRejectWarn.WARN) 1669 { 1670 logger.error(message); 1671 } 1672 } 1673 1674 for (AttributeType t : o.getDeclaredRequiredAttributes()) 1675 { 1676 if (!userAttributes.containsKey(t) 1677 && !operationalAttributes.containsKey(t) 1678 && !t.isObjectClass()) 1679 { 1680 LocalizableMessage message = 1681 ERR_ENTRY_SCHEMA_MISSING_REQUIRED_ATTR_FOR_OC.get( 1682 dn, 1683 t.getNameOrOID(), 1684 o.getNameOrOID()); 1685 invalidReason.append(message); 1686 return false; 1687 } 1688 } 1689 } 1690 1691 1692 // Make sure all the user attributes are allowed, have at least 1693 // one value, and if they are single-valued that they have exactly 1694 // one value. 1695 for (AttributeType t : userAttributes.keySet()) 1696 { 1697 boolean found = false; 1698 for (ObjectClass o : objectClasses.keySet()) 1699 { 1700 if (o.isRequiredOrOptional(t)) 1701 { 1702 found = true; 1703 break; 1704 } 1705 } 1706 1707 if (!found && ditContentRule != null 1708 && ditContentRule.isRequiredOrOptional(t)) 1709 { 1710 found = true; 1711 } 1712 1713 if (! found) 1714 { 1715 LocalizableMessage message = 1716 ERR_ENTRY_SCHEMA_DISALLOWED_USER_ATTR_FOR_OC.get( dn, t.getNameOrOID()); 1717 invalidReason.append(message); 1718 return false; 1719 } 1720 1721 List<Attribute> attrList = userAttributes.get(t); 1722 if (attrList != null) 1723 { 1724 for (Attribute a : attrList) 1725 { 1726 if (a.isEmpty()) 1727 { 1728 invalidReason.append(ERR_ENTRY_SCHEMA_ATTR_NO_VALUES.get(dn, t.getNameOrOID())); 1729 return false; 1730 } 1731 else if (t.isSingleValue() && a.size() != 1) 1732 { 1733 invalidReason.append(ERR_ENTRY_SCHEMA_ATTR_SINGLE_VALUED.get(dn, t.getNameOrOID())); 1734 return false; 1735 } 1736 } 1737 } 1738 } 1739 1740 1741 // Iterate through all of the operational attributes and make sure 1742 // that all of the single-valued attributes only have one value. 1743 for (AttributeType t : operationalAttributes.keySet()) 1744 { 1745 if (t.isSingleValue()) 1746 { 1747 List<Attribute> attrList = operationalAttributes.get(t); 1748 if (attrList != null) 1749 { 1750 for (Attribute a : attrList) 1751 { 1752 if (a.size() > 1) 1753 { 1754 invalidReason.append(ERR_ENTRY_SCHEMA_ATTR_SINGLE_VALUED.get(dn, t.getNameOrOID())); 1755 return false; 1756 } 1757 } 1758 } 1759 } 1760 } 1761 1762 1763 // If we've gotten here, then things are OK. 1764 return true; 1765 } 1766 1767 1768 1769 /** 1770 * Performs any processing needed for name form validation. 1771 * 1772 * @param nameForm The name form to validate against this 1773 * entry. 1774 * @param structuralPolicy The policy that should be used for 1775 * structural object class compliance. 1776 * @param invalidReason A buffer into which an invalid reason 1777 * may be added. 1778 * 1779 * @return {@code true} if this entry passes all of the checks, or 1780 * {@code false} if there are any failures. 1781 */ 1782 private boolean checkNameForm(NameForm nameForm, 1783 AcceptRejectWarn structuralPolicy, 1784 LocalizableMessageBuilder invalidReason) 1785 { 1786 RDN rdn = dn.rdn(); 1787 if (rdn != null) 1788 { 1789 // Make sure that all the required attributes are present. 1790 for (AttributeType t : nameForm.getRequiredAttributes()) 1791 { 1792 if (! rdn.hasAttributeType(t)) 1793 { 1794 LocalizableMessage message = 1795 ERR_ENTRY_SCHEMA_RDN_MISSING_REQUIRED_ATTR.get( 1796 dn, 1797 t.getNameOrOID(), 1798 nameForm.getNameOrOID()); 1799 1800 if (structuralPolicy == AcceptRejectWarn.REJECT) 1801 { 1802 invalidReason.append(message); 1803 return false; 1804 } 1805 else if (structuralPolicy == AcceptRejectWarn.WARN) 1806 { 1807 logger.error(message); 1808 } 1809 } 1810 } 1811 1812 // Make sure that all attributes in the RDN are allowed. 1813 for (AVA ava : rdn) 1814 { 1815 AttributeType t = ava.getAttributeType(); 1816 if (! nameForm.isRequiredOrOptional(t)) 1817 { 1818 LocalizableMessage message = 1819 ERR_ENTRY_SCHEMA_RDN_DISALLOWED_ATTR.get( 1820 dn, 1821 t.getNameOrOID(), 1822 nameForm.getNameOrOID()); 1823 1824 if (structuralPolicy == AcceptRejectWarn.REJECT) 1825 { 1826 invalidReason.append(message); 1827 return false; 1828 } 1829 else if (structuralPolicy == AcceptRejectWarn.WARN) 1830 { 1831 logger.error(message); 1832 } 1833 } 1834 } 1835 } 1836 1837 // If we've gotten here, then things are OK. 1838 return true; 1839 } 1840 1841 1842 1843 /** 1844 * Performs any processing needed for DIT content rule validation. 1845 * 1846 * @param ditContentRule The DIT content rule to validate 1847 * against this entry. 1848 * @param structuralPolicy The policy that should be used for 1849 * structural object class compliance. 1850 * @param invalidReason A buffer into which an invalid reason 1851 * may be added. 1852 * 1853 * @return {@code true} if this entry passes all of the checks, or 1854 * {@code false} if there are any failures. 1855 */ 1856 private boolean checkDITContentRule(DITContentRule ditContentRule, 1857 AcceptRejectWarn structuralPolicy, 1858 LocalizableMessageBuilder invalidReason) 1859 { 1860 // Make sure that all of the required attributes are present. 1861 for (AttributeType t : ditContentRule.getRequiredAttributes()) 1862 { 1863 if (!userAttributes.containsKey(t) 1864 && !operationalAttributes.containsKey(t) 1865 && !t.isObjectClass()) 1866 { 1867 LocalizableMessage message = 1868 ERR_ENTRY_SCHEMA_MISSING_REQUIRED_ATTR_FOR_DCR.get( 1869 dn, 1870 t.getNameOrOID(), 1871 ditContentRule.getNameOrOID()); 1872 1873 if (structuralPolicy == AcceptRejectWarn.REJECT) 1874 { 1875 invalidReason.append(message); 1876 return false; 1877 } 1878 else if (structuralPolicy == AcceptRejectWarn.WARN) 1879 { 1880 logger.error(message); 1881 } 1882 } 1883 } 1884 1885 // Make sure that none of the prohibited attributes are present. 1886 for (AttributeType t : ditContentRule.getProhibitedAttributes()) 1887 { 1888 if (userAttributes.containsKey(t) || 1889 operationalAttributes.containsKey(t)) 1890 { 1891 LocalizableMessage message = 1892 ERR_ENTRY_SCHEMA_PROHIBITED_ATTR_FOR_DCR.get( 1893 dn, 1894 t.getNameOrOID(), 1895 ditContentRule.getNameOrOID()); 1896 1897 if (structuralPolicy == AcceptRejectWarn.REJECT) 1898 { 1899 invalidReason.append(message); 1900 return false; 1901 } 1902 else if (structuralPolicy == AcceptRejectWarn.WARN) 1903 { 1904 logger.error(message); 1905 } 1906 } 1907 } 1908 1909 // If we've gotten here, then things are OK. 1910 return true; 1911 } 1912 1913 1914 1915 /** 1916 * Performs any processing needed for DIT structure rule validation. 1917 * 1918 * @param ditStructureRule The DIT structure rule for this 1919 * entry. 1920 * @param structuralClass The structural object class for 1921 * this entry. 1922 * @param parentEntry The parent entry, if available 1923 * and applicable. 1924 * @param parentProvided Indicates whether the parent 1925 * entry was provided. 1926 * @param validateStructureRules Indicates whether to check to see 1927 * if this entry violates a DIT 1928 * structure rule for its parent. 1929 * @param structuralPolicy The policy that should be used 1930 * for structural object class 1931 * compliance. 1932 * @param invalidReason A buffer into which an invalid 1933 * reason may be added. 1934 * 1935 * @return {@code true} if this entry passes all of the checks, or 1936 * {@code false} if there are any failures. 1937 */ 1938 private boolean checkDITStructureRule( 1939 DITStructureRule ditStructureRule, 1940 ObjectClass structuralClass, 1941 Entry parentEntry, boolean parentProvided, 1942 boolean validateStructureRules, 1943 AcceptRejectWarn structuralPolicy, 1944 LocalizableMessageBuilder invalidReason) 1945 { 1946 // If there is a DIT structure rule for this entry, then make sure 1947 // that the entry is in compliance with it. 1948 if (ditStructureRule != null && !ditStructureRule.getSuperiorRules().isEmpty()) 1949 { 1950 if (parentProvided) 1951 { 1952 if (parentEntry != null) 1953 { 1954 boolean dsrValid = 1955 validateDITStructureRule(ditStructureRule, 1956 structuralClass, parentEntry, 1957 structuralPolicy, 1958 invalidReason); 1959 if (! dsrValid) 1960 { 1961 return false; 1962 } 1963 } 1964 } 1965 else 1966 { 1967 // Get the DN of the parent entry if possible. 1968 DN parentDN = DirectoryServer.getParentDNInSuffix(dn); 1969 if (parentDN != null) 1970 { 1971 try 1972 { 1973 parentEntry = DirectoryServer.getEntry(parentDN); 1974 if (parentEntry == null) 1975 { 1976 LocalizableMessage message = ERR_ENTRY_SCHEMA_DSR_NO_PARENT_ENTRY.get(dn, parentDN); 1977 1978 if (structuralPolicy == AcceptRejectWarn.REJECT) 1979 { 1980 invalidReason.append(message); 1981 return false; 1982 } 1983 else if (structuralPolicy == AcceptRejectWarn.WARN) 1984 { 1985 logger.error(message); 1986 } 1987 } 1988 else 1989 { 1990 boolean dsrValid = 1991 validateDITStructureRule(ditStructureRule, 1992 structuralClass, 1993 parentEntry, 1994 structuralPolicy, 1995 invalidReason); 1996 if (! dsrValid) 1997 { 1998 return false; 1999 } 2000 } 2001 } 2002 catch (Exception e) 2003 { 2004 logger.traceException(e); 2005 2006 LocalizableMessage message = 2007 ERR_ENTRY_SCHEMA_COULD_NOT_CHECK_DSR.get( 2008 dn, 2009 ditStructureRule.getNameOrRuleID(), 2010 getExceptionMessage(e)); 2011 2012 if (structuralPolicy == AcceptRejectWarn.REJECT) 2013 { 2014 invalidReason.append(message); 2015 return false; 2016 } 2017 else if (structuralPolicy == AcceptRejectWarn.WARN) 2018 { 2019 logger.error(message); 2020 } 2021 } 2022 } 2023 } 2024 } 2025 else if (validateStructureRules) 2026 { 2027 // There is no DIT structure rule for this entry, but there may 2028 // be one for the parent entry. If there is such a rule for the 2029 // parent entry, then this entry will not be valid. 2030 boolean parentExists = false; 2031 ObjectClass parentStructuralClass = null; 2032 if (parentEntry != null) 2033 { 2034 parentExists = true; 2035 parentStructuralClass = parentEntry.getStructuralObjectClass(); 2036 } 2037 else if (! parentProvided) 2038 { 2039 DN parentDN = DirectoryServer.getParentDNInSuffix(getName()); 2040 if (parentDN != null) 2041 { 2042 try 2043 { 2044 parentEntry = DirectoryServer.getEntry(parentDN); 2045 if (parentEntry == null) 2046 { 2047 LocalizableMessage message = 2048 ERR_ENTRY_SCHEMA_DSR_NO_PARENT_ENTRY.get( 2049 dn, parentDN); 2050 2051 if (structuralPolicy == AcceptRejectWarn.REJECT) 2052 { 2053 invalidReason.append(message); 2054 return false; 2055 } 2056 else if (structuralPolicy == AcceptRejectWarn.WARN) 2057 { 2058 logger.error(message); 2059 } 2060 } 2061 else 2062 { 2063 parentExists = true; 2064 parentStructuralClass = parentEntry.getStructuralObjectClass(); 2065 } 2066 } 2067 catch (Exception e) 2068 { 2069 logger.traceException(e); 2070 2071 LocalizableMessage message = 2072 ERR_ENTRY_SCHEMA_COULD_NOT_CHECK_PARENT_DSR.get( 2073 dn, getExceptionMessage(e)); 2074 2075 if (structuralPolicy == AcceptRejectWarn.REJECT) 2076 { 2077 invalidReason.append(message); 2078 return false; 2079 } 2080 else if (structuralPolicy == AcceptRejectWarn.WARN) 2081 { 2082 logger.error(message); 2083 } 2084 } 2085 } 2086 } 2087 2088 if (parentExists) 2089 { 2090 if (parentStructuralClass == null) 2091 { 2092 LocalizableMessage message = ERR_ENTRY_SCHEMA_DSR_NO_PARENT_OC.get(dn, parentEntry.getName()); 2093 if (structuralPolicy == AcceptRejectWarn.REJECT) 2094 { 2095 invalidReason.append(message); 2096 return false; 2097 } 2098 else if (structuralPolicy == AcceptRejectWarn.WARN) 2099 { 2100 logger.error(message); 2101 } 2102 } 2103 else 2104 { 2105 Collection<NameForm> allNFs = DirectoryServer.getSchema().getNameForm(parentStructuralClass); 2106 if(allNFs != null) 2107 { 2108 for(NameForm parentNF : allNFs) 2109 { 2110 if (!parentNF.isObsolete()) 2111 { 2112 for (DITStructureRule parentDSR : DirectoryServer.getSchema().getDITStructureRules(parentNF)) 2113 { 2114 if (!parentDSR.isObsolete()) 2115 { 2116 LocalizableMessage message = ERR_ENTRY_SCHEMA_VIOLATES_PARENT_DSR.get(dn, parentEntry.getName()); 2117 if (structuralPolicy == AcceptRejectWarn.REJECT) 2118 { 2119 invalidReason.append(message); 2120 return false; 2121 } 2122 else if (structuralPolicy == AcceptRejectWarn.WARN) 2123 { 2124 logger.error(message); 2125 } 2126 } 2127 } 2128 } 2129 } 2130 } 2131 } 2132 } 2133 } 2134 2135 // If we've gotten here, then things are OK. 2136 return true; 2137 } 2138 2139 2140 2141 /** 2142 * Determines whether this entry is in conformance to the provided 2143 * DIT structure rule. 2144 * 2145 * @param dsr The DIT structure rule to use in the 2146 * determination. 2147 * @param structuralClass The structural objectclass for this 2148 * entry to use in the determination. 2149 * @param parentEntry The reference to the parent entry to 2150 * check. 2151 * @param structuralPolicy The policy that should be used around 2152 * enforcement of DIT structure rules. 2153 * @param invalidReason The buffer to which the invalid reason 2154 * should be appended if a problem is 2155 * found. 2156 * 2157 * @return <CODE>true</CODE> if this entry conforms to the provided 2158 * DIT structure rule, or <CODE>false</CODE> if not. 2159 */ 2160 private boolean validateDITStructureRule(DITStructureRule dsr, 2161 ObjectClass structuralClass, Entry parentEntry, 2162 AcceptRejectWarn structuralPolicy, 2163 LocalizableMessageBuilder invalidReason) 2164 { 2165 ObjectClass oc = parentEntry.getStructuralObjectClass(); 2166 if (oc == null) 2167 { 2168 LocalizableMessage message = ERR_ENTRY_SCHEMA_DSR_NO_PARENT_OC.get( 2169 dn, parentEntry.getName()); 2170 2171 if (structuralPolicy == AcceptRejectWarn.REJECT) 2172 { 2173 invalidReason.append(message); 2174 return false; 2175 } 2176 else if (structuralPolicy == AcceptRejectWarn.WARN) 2177 { 2178 logger.error(message); 2179 } 2180 } 2181 2182 boolean matchFound = false; 2183 for (DITStructureRule dsr2 : dsr.getSuperiorRules()) 2184 { 2185 if (dsr2.getNameForm().getStructuralClass().equals(oc)) 2186 { 2187 matchFound = true; 2188 } 2189 } 2190 2191 if (! matchFound) 2192 { 2193 LocalizableMessage message = 2194 ERR_ENTRY_SCHEMA_DSR_DISALLOWED_SUPERIOR_OC.get( 2195 dn, 2196 dsr.getNameOrRuleID(), 2197 structuralClass.getNameOrOID(), 2198 oc.getNameOrOID()); 2199 2200 if (structuralPolicy == AcceptRejectWarn.REJECT) 2201 { 2202 invalidReason.append(message); 2203 return false; 2204 } 2205 else if (structuralPolicy == AcceptRejectWarn.WARN) 2206 { 2207 logger.error(message); 2208 } 2209 } 2210 2211 return true; 2212 } 2213 2214 2215 2216 /** 2217 * Retrieves the attachment for this entry. 2218 * 2219 * @return The attachment for this entry, or <CODE>null</CODE> if 2220 * there is none. 2221 */ 2222 public Object getAttachment() 2223 { 2224 return attachment; 2225 } 2226 2227 2228 2229 /** 2230 * Specifies the attachment for this entry. This will replace any 2231 * existing attachment that might be defined. 2232 * 2233 * @param attachment The attachment for this entry, or 2234 * <CODE>null</CODE> if there should not be an 2235 * attachment. 2236 */ 2237 public void setAttachment(Object attachment) 2238 { 2239 this.attachment = attachment; 2240 } 2241 2242 2243 2244 /** 2245 * Creates a duplicate of this entry that may be altered without 2246 * impacting the information in this entry. 2247 * 2248 * @param processVirtual Indicates whether virtual attribute 2249 * processing should be performed for the 2250 * entry. 2251 * 2252 * @return A duplicate of this entry that may be altered without 2253 * impacting the information in this entry. 2254 */ 2255 public Entry duplicate(boolean processVirtual) 2256 { 2257 Map<ObjectClass, String> objectClassesCopy = new HashMap<>(objectClasses); 2258 2259 Map<AttributeType, List<Attribute>> userAttrsCopy = new HashMap<>(userAttributes.size()); 2260 deepCopy(userAttributes, userAttrsCopy, false, false, false, 2261 true, false); 2262 2263 Map<AttributeType, List<Attribute>> operationalAttrsCopy = 2264 new HashMap<>(operationalAttributes.size()); 2265 deepCopy(operationalAttributes, operationalAttrsCopy, false, 2266 false, false, true, false); 2267 2268 // Put back all the suppressed attributes where they belonged to. 2269 // Then hopefully processVirtualAttributes() will rebuild the suppressed 2270 // attribute list correctly. 2271 for (AttributeType t : suppressedAttributes.keySet()) 2272 { 2273 List<Attribute> attrList = suppressedAttributes.get(t); 2274 if (t.isOperational()) 2275 { 2276 operationalAttrsCopy.put(t, attrList); 2277 } 2278 else 2279 { 2280 userAttrsCopy.put(t, attrList); 2281 } 2282 } 2283 2284 Entry e = new Entry(dn, objectClassesCopy, userAttrsCopy, 2285 operationalAttrsCopy); 2286 if (processVirtual) 2287 { 2288 e.processVirtualAttributes(); 2289 } 2290 return e; 2291 } 2292 2293 2294 2295 /** 2296 * Performs a deep copy from the source map to the target map. 2297 * In this case, the attributes in the list will be duplicates 2298 * rather than re-using the same reference. 2299 * 2300 * @param source 2301 * The source map from which to obtain the information. 2302 * @param target 2303 * The target map into which to place the copied 2304 * information. 2305 * @param omitValues 2306 * Indicates whether to omit attribute values when 2307 * processing. 2308 * @param omitEmpty 2309 * Indicates whether to omit empty attributes when 2310 * processing. 2311 * @param omitReal 2312 * Indicates whether to exclude real attributes. 2313 * @param omitVirtual 2314 * Indicates whether to exclude virtual attributes. 2315 * @param mergeDuplicates 2316 * Indicates whether duplicate attributes should be merged. 2317 */ 2318 private void deepCopy(Map<AttributeType,List<Attribute>> source, 2319 Map<AttributeType,List<Attribute>> target, 2320 boolean omitValues, 2321 boolean omitEmpty, 2322 boolean omitReal, 2323 boolean omitVirtual, 2324 boolean mergeDuplicates) 2325 { 2326 for (Map.Entry<AttributeType, List<Attribute>> mapEntry : 2327 source.entrySet()) 2328 { 2329 AttributeType t = mapEntry.getKey(); 2330 List<Attribute> sourceList = mapEntry.getValue(); 2331 List<Attribute> targetList = new ArrayList<>(sourceList.size()); 2332 2333 for (Attribute a : sourceList) 2334 { 2335 if ((omitReal && a.isReal()) 2336 || (omitVirtual && a.isVirtual()) 2337 || (omitEmpty && a.isEmpty())) 2338 { 2339 continue; 2340 } 2341 2342 if (omitValues) 2343 { 2344 a = Attributes.empty(a); 2345 } 2346 2347 if (!targetList.isEmpty() && mergeDuplicates) 2348 { 2349 // Ensure that there is only one attribute with the same type and options. 2350 // This is not very efficient but will occur very rarely. 2351 boolean found = false; 2352 for (int i = 0; i < targetList.size(); i++) 2353 { 2354 Attribute otherAttribute = targetList.get(i); 2355 if (otherAttribute.getAttributeDescription().equals(a.getAttributeDescription())) 2356 { 2357 targetList.set(i, Attributes.merge(a, otherAttribute)); 2358 found = true; 2359 } 2360 } 2361 2362 if (!found) 2363 { 2364 targetList.add(a); 2365 } 2366 } 2367 else 2368 { 2369 targetList.add(a); 2370 } 2371 } 2372 2373 if (!targetList.isEmpty()) 2374 { 2375 target.put(t, targetList); 2376 } 2377 } 2378 } 2379 2380 2381 2382 /** 2383 * Indicates whether this entry meets the criteria to consider it a referral 2384 * (e.g., it contains the "referral" objectclass and a "ref" attribute). 2385 * 2386 * @return <CODE>true</CODE> if this entry meets the criteria to 2387 * consider it a referral, or <CODE>false</CODE> if not. 2388 */ 2389 public boolean isReferral() 2390 { 2391 return hasObjectClassOrAttribute(OC_REFERRAL, ATTR_REFERRAL_URL); 2392 } 2393 2394 /** 2395 * Returns whether the current entry has a specific object class or attribute. 2396 * 2397 * @param objectClassName 2398 * the name of the object class to look for 2399 * @param attrTypeName 2400 * the attribute type name of the object class to look for 2401 * @return true if the current entry has the object class or the attribute, 2402 * false otherwise 2403 */ 2404 private boolean hasObjectClassOrAttribute(String objectClassName, String attrTypeName) 2405 { 2406 ObjectClass oc = DirectoryServer.getSchema().getObjectClass(objectClassName); 2407 if (oc.isPlaceHolder()) 2408 { 2409 // This should not happen 2410 // The server doesn't have this objectclass defined. 2411 logger.trace("No %s objectclass is defined in the server schema.", objectClassName); 2412 return containsObjectClassByName(objectClassName); 2413 } 2414 if (!objectClasses.containsKey(oc)) 2415 { 2416 return false; 2417 } 2418 2419 AttributeType attrType = DirectoryServer.getSchema().getAttributeType(attrTypeName); 2420 if (attrType.isPlaceHolder()) 2421 { 2422 // This should not happen 2423 // The server doesn't have this attribute type defined. 2424 logger.trace("No %s attribute type is defined in the server schema.", attrTypeName); 2425 return false; 2426 } 2427 return userAttributes.containsKey(attrType) 2428 || operationalAttributes.containsKey(attrType); 2429 } 2430 2431 /** 2432 * Whether the object class name exists in the objectClass of this entry. 2433 * 2434 * @param objectClassName 2435 * the name of the object class to look for 2436 * @return true if the object class name exists in the objectClass of this 2437 * entry, false otherwise 2438 */ 2439 private boolean containsObjectClassByName(String objectClassName) 2440 { 2441 for (String ocName : objectClasses.values()) 2442 { 2443 if (objectClassName.equalsIgnoreCase(ocName)) 2444 { 2445 return true; 2446 } 2447 } 2448 return false; 2449 } 2450 2451 /** 2452 * Retrieves the set of referral URLs that are included in this 2453 * referral entry. This should only be called if 2454 * <CODE>isReferral()</CODE> returns <CODE>true</CODE>. 2455 * 2456 * @return The set of referral URLs that are included in this entry 2457 * if it is a referral, or <CODE>null</CODE> if it is not a 2458 * referral. 2459 */ 2460 public Set<String> getReferralURLs() 2461 { 2462 AttributeType referralType = DirectoryServer.getSchema().getAttributeType(ATTR_REFERRAL_URL); 2463 if (referralType.isPlaceHolder()) 2464 { 2465 // This should not happen -- The server doesn't have a ref attribute type defined. 2466 logger.trace("No %s attribute type is defined in the server schema.", ATTR_REFERRAL_URL); 2467 return null; 2468 } 2469 2470 List<Attribute> refAttrs = userAttributes.get(referralType); 2471 if (refAttrs == null) 2472 { 2473 refAttrs = operationalAttributes.get(referralType); 2474 if (refAttrs == null) 2475 { 2476 return null; 2477 } 2478 } 2479 2480 Set<String> referralURLs = new LinkedHashSet<>(); 2481 for (Attribute a : refAttrs) 2482 { 2483 for (ByteString v : a) 2484 { 2485 referralURLs.add(v.toString()); 2486 } 2487 } 2488 2489 return referralURLs; 2490 } 2491 2492 2493 2494 /** 2495 * Indicates whether this entry meets the criteria to consider it an 2496 * alias (e.g., it contains the "aliasObject" objectclass and a 2497 * "alias" attribute). 2498 * 2499 * @return <CODE>true</CODE> if this entry meets the criteria to 2500 * consider it an alias, or <CODE>false</CODE> if not. 2501 */ 2502 public boolean isAlias() 2503 { 2504 return hasObjectClassOrAttribute(OC_ALIAS, ATTR_ALIAS_DN); 2505 } 2506 2507 2508 2509 /** 2510 * Retrieves the DN of the entry referenced by this alias entry. 2511 * This should only be called if <CODE>isAlias()</CODE> returns 2512 * <CODE>true</CODE>. 2513 * 2514 * @return The DN of the entry referenced by this alias entry, or 2515 * <CODE>null</CODE> if it is not an alias. 2516 * 2517 * @throws DirectoryException If there is an aliasedObjectName 2518 * attribute but its value cannot be 2519 * parsed as a DN. 2520 */ 2521 public DN getAliasedDN() throws DirectoryException 2522 { 2523 AttributeType aliasType = DirectoryServer.getSchema().getAttributeType(ATTR_REFERRAL_URL); 2524 if (aliasType.isPlaceHolder()) 2525 { 2526 // This should not happen -- The server doesn't have an aliasedObjectName attribute type defined. 2527 logger.trace("No %s attribute type is defined in the server schema.", ATTR_ALIAS_DN); 2528 return null; 2529 } 2530 2531 List<Attribute> aliasAttrs = userAttributes.get(aliasType); 2532 if (aliasAttrs == null) 2533 { 2534 aliasAttrs = operationalAttributes.get(aliasType); 2535 if (aliasAttrs == null) 2536 { 2537 return null; 2538 } 2539 } 2540 2541 if (!aliasAttrs.isEmpty()) 2542 { 2543 // There should only be a single alias attribute in an entry, 2544 // and we'll skip the check for others for performance reasons. 2545 // We would just end up taking the first one anyway. The same 2546 // is true with the set of values, since it should be a 2547 // single-valued attribute. 2548 Attribute aliasAttr = aliasAttrs.get(0); 2549 if (!aliasAttr.isEmpty()) 2550 { 2551 return DN.valueOf(aliasAttr.iterator().next().toString()); 2552 } 2553 } 2554 return null; 2555 } 2556 2557 2558 2559 /** 2560 * Indicates whether this entry meets the criteria to consider it an 2561 * LDAP subentry (i.e., it contains the "ldapSubentry" objectclass). 2562 * 2563 * @return <CODE>true</CODE> if this entry meets the criteria to 2564 * consider it an LDAP subentry, or <CODE>false</CODE> if 2565 * not. 2566 */ 2567 public boolean isLDAPSubentry() 2568 { 2569 return hasObjectClass(OC_LDAP_SUBENTRY_LC); 2570 } 2571 2572 /** 2573 * Returns whether the current entry has a specific object class. 2574 * 2575 * @param objectClassLowerCase 2576 * the lowercase name of the object class to look for 2577 * @return true if the current entry has the object class, false otherwise 2578 */ 2579 private boolean hasObjectClass(String objectClassLowerCase) 2580 { 2581 ObjectClass oc = DirectoryServer.getSchema().getObjectClass(objectClassLowerCase); 2582 if (oc.isPlaceHolder()) 2583 { 2584 // This should not happen 2585 // The server doesn't have this object class defined. 2586 logger.trace("No %s objectclass is defined in the server schema.", objectClassLowerCase); 2587 return containsObjectClassByName(objectClassLowerCase); 2588 } 2589 2590 // Make the determination based on whether this entry has this objectclass. 2591 return objectClasses.containsKey(oc); 2592 } 2593 2594 2595 2596 /** 2597 * Indicates whether this entry meets the criteria to consider it 2598 * an RFC 3672 LDAP subentry (i.e., it contains the "subentry" 2599 * objectclass). 2600 * 2601 * @return <CODE>true</CODE> if this entry meets the criteria to 2602 * consider it an RFC 3672 LDAP subentry, or <CODE>false 2603 * </CODE> if not. 2604 */ 2605 public boolean isSubentry() 2606 { 2607 return hasObjectClass(OC_SUBENTRY); 2608 } 2609 2610 2611 2612 /** 2613 * Indicates whether the entry meets the criteria to consider it an 2614 * RFC 3671 LDAP collective attributes subentry (i.e., it contains 2615 * the "collectiveAttributeSubentry" objectclass). 2616 * 2617 * @return <CODE>true</CODE> if this entry meets the criteria to 2618 * consider it an RFC 3671 LDAP collective attributes 2619 * subentry, or <CODE>false</CODE> if not. 2620 */ 2621 public boolean isCollectiveAttributeSubentry() 2622 { 2623 return hasObjectClass(OC_COLLECTIVE_ATTR_SUBENTRY_LC); 2624 } 2625 2626 2627 2628 /** 2629 * Indicates whether the entry meets the criteria to consider it an 2630 * inherited collective attributes subentry (i.e., it contains 2631 * the "inheritedCollectiveAttributeSubentry" objectclass). 2632 * 2633 * @return <CODE>true</CODE> if this entry meets the criteria to 2634 * consider it an inherited collective attributes 2635 * subentry, or <CODE>false</CODE> if not. 2636 */ 2637 public boolean isInheritedCollectiveAttributeSubentry() 2638 { 2639 return hasObjectClass(OC_INHERITED_COLLECTIVE_ATTR_SUBENTRY_LC); 2640 } 2641 2642 2643 2644 /** 2645 * Indicates whether the entry meets the criteria to consider it an inherited 2646 * from DN collective attributes subentry (i.e., it contains the 2647 * "inheritedFromDNCollectiveAttributeSubentry" objectclass). 2648 * 2649 * @return <CODE>true</CODE> if this entry meets the criteria to consider it 2650 * an inherited from DN collective attributes subentry, or 2651 * <CODE>false</CODE> if not. 2652 */ 2653 public boolean isInheritedFromDNCollectiveAttributeSubentry() 2654 { 2655 return hasObjectClass(OC_INHERITED_FROM_DN_COLLECTIVE_ATTR_SUBENTRY_LC); 2656 } 2657 2658 2659 2660 /** 2661 * Indicates whether the entry meets the criteria to consider it 2662 * an inherited from RDN collective attributes subentry (i.e., 2663 * it contains the "inheritedFromRDNCollectiveAttributeSubentry" 2664 * objectclass). 2665 * 2666 * @return <CODE>true</CODE> if this entry meets the criteria to 2667 * consider it an inherited from RDN collective attributes 2668 * subentry, or <CODE>false</CODE> if not. 2669 */ 2670 public boolean isInheritedFromRDNCollectiveAttributeSubentry() 2671 { 2672 return hasObjectClass(OC_INHERITED_FROM_RDN_COLLECTIVE_ATTR_SUBENTRY_LC); 2673 } 2674 2675 2676 2677 /** 2678 * Indicates whether the entry meets the criteria to consider it a 2679 * LDAP password policy subentry (i.e., it contains the "pwdPolicy" 2680 * objectclass of LDAP Password Policy Internet-Draft). 2681 * 2682 * @return <CODE>true</CODE> if this entry meets the criteria to 2683 * consider it a LDAP Password Policy Internet-Draft 2684 * subentry, or <CODE>false</CODE> if not. 2685 */ 2686 public boolean isPasswordPolicySubentry() 2687 { 2688 return hasObjectClass(OC_PWD_POLICY_SUBENTRY_LC); 2689 } 2690 2691 2692 2693 /** 2694 * Indicates whether this entry falls within the range of the 2695 * provided search base DN and scope. 2696 * 2697 * @param baseDN The base DN for which to make the determination. 2698 * @param scope The search scope for which to make the 2699 * determination. 2700 * 2701 * @return <CODE>true</CODE> if this entry is within the given 2702 * base and scope, or <CODE>false</CODE> if it is not. 2703 */ 2704 public boolean matchesBaseAndScope(DN baseDN, SearchScope scope) 2705 { 2706 return dn.isInScopeOf(baseDN, scope); 2707 } 2708 2709 2710 2711 /** 2712 * Performs any necessary collective attribute processing for this 2713 * entry. This should only be called at the time the entry is 2714 * decoded or created within the backend. 2715 */ 2716 private void processCollectiveAttributes() 2717 { 2718 if (isSubentry() || isLDAPSubentry()) 2719 { 2720 return; 2721 } 2722 2723 SubentryManager manager = 2724 DirectoryServer.getSubentryManager(); 2725 if(manager == null) 2726 { 2727 //Subentry manager may not have been initialized by 2728 //a component that doesn't require it. 2729 return; 2730 } 2731 // Get applicable collective subentries. 2732 List<SubEntry> collectiveAttrSubentries = 2733 manager.getCollectiveSubentries(this); 2734 2735 if (collectiveAttrSubentries == null || collectiveAttrSubentries.isEmpty()) 2736 { 2737 // Nothing to see here, move along. 2738 return; 2739 } 2740 2741 // Get collective attribute exclusions. 2742 AttributeType exclusionsType = DirectoryServer.getSchema().getAttributeType(ATTR_COLLECTIVE_EXCLUSIONS_LC); 2743 List<Attribute> exclusionsAttrList = operationalAttributes.get(exclusionsType); 2744 List<String> excludedAttrNames = new ArrayList<>(); 2745 if (exclusionsAttrList != null && !exclusionsAttrList.isEmpty()) 2746 { 2747 for (Attribute attr : exclusionsAttrList) 2748 { 2749 for (ByteString attrValue : attr) 2750 { 2751 String excludedAttrName = attrValue.toString().toLowerCase(); 2752 if (VALUE_COLLECTIVE_EXCLUSIONS_EXCLUDE_ALL_LC.equals(excludedAttrName) 2753 || OID_COLLECTIVE_EXCLUSIONS_EXCLUDE_ALL.equals(excludedAttrName)) 2754 { 2755 return; 2756 } 2757 excludedAttrNames.add(excludedAttrName); 2758 } 2759 } 2760 } 2761 2762 // Process collective attributes. 2763 for (SubEntry subEntry : collectiveAttrSubentries) 2764 { 2765 if (subEntry.isCollective() || subEntry.isInheritedCollective()) 2766 { 2767 Entry inheritFromEntry = null; 2768 if (subEntry.isInheritedCollective()) 2769 { 2770 if (subEntry.isInheritedFromDNCollective() && 2771 hasAttribute(subEntry.getInheritFromDNType())) 2772 { 2773 try 2774 { 2775 DN inheritFromDN = null; 2776 for (Attribute attr : getAttribute(subEntry.getInheritFromDNType())) 2777 { 2778 for (ByteString value : attr) 2779 { 2780 inheritFromDN = DN.valueOf(value); 2781 // Respect subentry root scope. 2782 if (!inheritFromDN.isSubordinateOrEqualTo( 2783 subEntry.getDN().parent())) 2784 { 2785 inheritFromDN = null; 2786 } 2787 break; 2788 } 2789 } 2790 if (inheritFromDN == null) 2791 { 2792 continue; 2793 } 2794 2795 // TODO : ACI check; needs re-factoring to happen. 2796 inheritFromEntry = DirectoryServer.getEntry(inheritFromDN); 2797 } 2798 catch (DirectoryException de) 2799 { 2800 logger.traceException(de); 2801 } 2802 } 2803 else if (subEntry.isInheritedFromRDNCollective() && 2804 hasAttribute(subEntry.getInheritFromRDNAttrType())) 2805 { 2806 DN inheritFromDN = subEntry.getInheritFromBaseDN(); 2807 if (inheritFromDN != null) 2808 { 2809 try 2810 { 2811 for (Attribute attr : getAttribute(subEntry.getInheritFromRDNAttrType())) 2812 { 2813 inheritFromDN = subEntry.getInheritFromBaseDN(); 2814 for (ByteString value : attr) 2815 { 2816 inheritFromDN = inheritFromDN.child( 2817 new RDN(subEntry.getInheritFromRDNType(), value)); 2818 break; 2819 } 2820 } 2821 2822 // TODO : ACI check; needs re-factoring to happen. 2823 inheritFromEntry = DirectoryServer.getEntry(inheritFromDN); 2824 } 2825 catch (DirectoryException de) 2826 { 2827 logger.traceException(de); 2828 } 2829 } 2830 else 2831 { 2832 continue; 2833 } 2834 } 2835 } 2836 List<Attribute> collectiveAttrList = subEntry.getCollectiveAttributes(); 2837 for (Attribute collectiveAttr : collectiveAttrList) 2838 { 2839 AttributeType attributeType = collectiveAttr.getAttributeDescription().getAttributeType(); 2840 if (hasAnyNameOrOID(attributeType, excludedAttrNames)) 2841 { 2842 continue; 2843 } 2844 if (subEntry.isInheritedCollective()) 2845 { 2846 if (inheritFromEntry != null) 2847 { 2848 collectiveAttr = inheritFromEntry.getExactAttribute(collectiveAttr.getAttributeDescription()); 2849 if (collectiveAttr == null || collectiveAttr.isEmpty()) 2850 { 2851 continue; 2852 } 2853 collectiveAttr = new CollectiveVirtualAttribute(collectiveAttr); 2854 } 2855 else 2856 { 2857 continue; 2858 } 2859 } 2860 List<Attribute> attrList = userAttributes.get(attributeType); 2861 if (attrList == null || attrList.isEmpty()) 2862 { 2863 attrList = operationalAttributes.get(attributeType); 2864 if (attrList == null || attrList.isEmpty()) 2865 { 2866 // There aren't any conflicts, so we can just add the attribute to the entry. 2867 putAttributes(attributeType, newLinkedList(collectiveAttr)); 2868 } 2869 else 2870 { 2871 // There is a conflict with an existing operational attribute. 2872 resolveCollectiveConflict(subEntry.getConflictBehavior(), 2873 collectiveAttr, attrList, operationalAttributes, attributeType); 2874 } 2875 } 2876 else 2877 { 2878 // There is a conflict with an existing user attribute. 2879 resolveCollectiveConflict(subEntry.getConflictBehavior(), 2880 collectiveAttr, attrList, userAttributes, attributeType); 2881 } 2882 } 2883 } 2884 } 2885 } 2886 2887 private boolean hasAnyNameOrOID(AttributeType attributeType, Collection<String> attrNames) 2888 { 2889 for (String attrName : attrNames) 2890 { 2891 if (attributeType.hasNameOrOID(attrName)) 2892 { 2893 return true; 2894 } 2895 } 2896 return false; 2897 } 2898 2899 private ByteString normalize(MatchingRule matchingRule, ByteString value) 2900 throws DirectoryException 2901 { 2902 try 2903 { 2904 return matchingRule.normalizeAttributeValue(value); 2905 } 2906 catch (DecodeException e) 2907 { 2908 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 2909 e.getMessageObject(), e); 2910 } 2911 } 2912 2913 /** 2914 * Resolves a conflict arising with a collective attribute. 2915 * 2916 * @param conflictBehavior 2917 * the behavior of the conflict 2918 * @param collectiveAttr 2919 * the attribute in conflict 2920 * @param attrList 2921 * the List of attribute where to resolve the conflict 2922 * @param attributes 2923 * the Map of attributes where to solve the conflict 2924 * @param attributeType 2925 * the attribute type used with the Map 2926 */ 2927 private void resolveCollectiveConflict( 2928 CollectiveConflictBehavior conflictBehavior, Attribute collectiveAttr, 2929 List<Attribute> attrList, Map<AttributeType, List<Attribute>> attributes, 2930 AttributeType attributeType) 2931 { 2932 if (attrList.get(0).isVirtual()) 2933 { 2934 // The existing attribute is already virtual, 2935 // so we've got a different conflict, but we'll let the first win. 2936 // FIXME -- Should we handle this differently? 2937 return; 2938 } 2939 2940 // The conflict is with a real attribute. See what the 2941 // conflict behavior is and figure out how to handle it. 2942 switch (conflictBehavior) 2943 { 2944 case REAL_OVERRIDES_VIRTUAL: 2945 // We don't need to update the entry because the real attribute will take 2946 // precedence. 2947 break; 2948 2949 case VIRTUAL_OVERRIDES_REAL: 2950 // We need to move the real attribute to the suppressed list 2951 // and replace it with the virtual attribute. 2952 suppressedAttributes.put(attributeType, attrList); 2953 attributes.put(attributeType, newLinkedList(collectiveAttr)); 2954 break; 2955 2956 case MERGE_REAL_AND_VIRTUAL: 2957 // We need to add the virtual attribute to the 2958 // list and keep the existing real attribute(s). 2959 attrList.add(collectiveAttr); 2960 break; 2961 } 2962 } 2963 2964 2965 2966 /** 2967 * Performs any necessary virtual attribute processing for this 2968 * entry. This should only be called at the time the entry is 2969 * decoded or created within the backend. 2970 */ 2971 public void processVirtualAttributes() 2972 { 2973 for (VirtualAttributeRule rule : DirectoryServer.getVirtualAttributes(this)) 2974 { 2975 AttributeType attributeType = rule.getAttributeType(); 2976 List<Attribute> attrList = userAttributes.get(attributeType); 2977 if (attrList == null || attrList.isEmpty()) 2978 { 2979 attrList = operationalAttributes.get(attributeType); 2980 if (attrList == null || attrList.isEmpty()) 2981 { 2982 // There aren't any conflicts, so we can just add the attribute to the entry. 2983 Attribute attr = new VirtualAttribute(attributeType, this, rule); 2984 putAttributes(attributeType, newLinkedList(attr)); 2985 } 2986 else 2987 { 2988 // There is a conflict with an existing operational attribute. 2989 resolveVirtualConflict(rule, attrList, operationalAttributes, attributeType); 2990 } 2991 } 2992 else 2993 { 2994 // There is a conflict with an existing user attribute. 2995 resolveVirtualConflict(rule, attrList, userAttributes, attributeType); 2996 } 2997 } 2998 2999 // Collective attributes. 3000 processCollectiveAttributes(); 3001 } 3002 3003 /** 3004 * Resolves a conflict arising with a virtual attribute. 3005 * 3006 * @param rule 3007 * the VirtualAttributeRule in conflict 3008 * @param attrList 3009 * the List of attribute where to resolve the conflict 3010 * @param attributes 3011 * the Map of attribute where to resolve the conflict 3012 * @param attributeType 3013 * the attribute type used with the Map 3014 */ 3015 private void resolveVirtualConflict(VirtualAttributeRule rule, 3016 List<Attribute> attrList, Map<AttributeType, List<Attribute>> attributes, 3017 AttributeType attributeType) 3018 { 3019 if (attrList.get(0).isVirtual()) 3020 { 3021 // The existing attribute is already virtual, so we've got 3022 // a different conflict, but we'll let the first win. 3023 // FIXME -- Should we handle this differently? 3024 return; 3025 } 3026 3027 // The conflict is with a real attribute. See what the 3028 // conflict behavior is and figure out how to handle it. 3029 switch (rule.getConflictBehavior()) 3030 { 3031 case REAL_OVERRIDES_VIRTUAL: 3032 // We don't need to update the entry because the real 3033 // attribute will take precedence. 3034 break; 3035 3036 case VIRTUAL_OVERRIDES_REAL: 3037 // We need to move the real attribute to the suppressed 3038 // list and replace it with the virtual attribute. 3039 suppressedAttributes.put(attributeType, attrList); 3040 Attribute attr = new VirtualAttribute(attributeType, this, rule); 3041 attributes.put(attributeType, newLinkedList(attr)); 3042 break; 3043 3044 case MERGE_REAL_AND_VIRTUAL: 3045 // We need to add the virtual attribute to the list and 3046 // keep the existing real attribute(s). 3047 attrList.add(new VirtualAttribute(attributeType, this, rule)); 3048 break; 3049 } 3050 } 3051 3052 3053 /** 3054 * Encodes this entry into a form that is suitable for long-term 3055 * persistent storage. The encoding will have a version number so 3056 * that if the way we store entries changes in the future we will 3057 * still be able to read entries encoded in an older format. 3058 * 3059 * @param buffer The buffer to encode into. 3060 * @param config The configuration that may be used to control how 3061 * the entry is encoded. 3062 * 3063 * @throws DirectoryException If a problem occurs while attempting 3064 * to encode the entry. 3065 */ 3066 public void encode(ByteStringBuilder buffer, 3067 EntryEncodeConfig config) 3068 throws DirectoryException 3069 { 3070 encodeV3(buffer, config); 3071 } 3072 3073 /** 3074 * Encodes this entry using the V3 encoding. 3075 * 3076 * @param buffer The buffer to encode into. 3077 * @param config The configuration that should be used to encode 3078 * the entry. 3079 * 3080 * @throws DirectoryException If a problem occurs while attempting 3081 * to encode the entry. 3082 */ 3083 private void encodeV3(ByteStringBuilder buffer, 3084 EntryEncodeConfig config) 3085 throws DirectoryException 3086 { 3087 // The version number will be one byte. 3088 buffer.appendByte(0x03); 3089 3090 // Get the encoded representation of the config. 3091 config.encode(buffer); 3092 3093 // If we should include the DN, then it will be encoded as a 3094 // one-to-five byte length followed by the UTF-8 byte 3095 // representation. 3096 if (! config.excludeDN()) 3097 { 3098 // TODO: Can we encode the DN directly into buffer? 3099 byte[] dnBytes = getBytes(dn.toString()); 3100 buffer.appendBERLength(dnBytes.length); 3101 buffer.appendBytes(dnBytes); 3102 } 3103 3104 3105 // Encode the object classes in the appropriate manner. 3106 if (config.compressObjectClassSets()) 3107 { 3108 config.getCompressedSchema().encodeObjectClasses(buffer, objectClasses); 3109 } 3110 else 3111 { 3112 // Encode number of OCs and 0 terminated names. 3113 buffer.appendBERLength(objectClasses.size()); 3114 for (String ocName : objectClasses.values()) 3115 { 3116 buffer.appendUtf8(ocName); 3117 buffer.appendByte(0x00); 3118 } 3119 } 3120 3121 3122 // Encode the user attributes in the appropriate manner. 3123 encodeAttributes(buffer, userAttributes, config); 3124 3125 3126 // The operational attributes will be encoded in the same way as 3127 // the user attributes. 3128 encodeAttributes(buffer, operationalAttributes, config); 3129 } 3130 3131 /** 3132 * Encode the given attributes of an entry. 3133 * 3134 * @param buffer The buffer to encode into. 3135 * @param attributes The attributes to encode. 3136 * @param config The configuration that may be used to control how 3137 * the entry is encoded. 3138 * 3139 * @throws DirectoryException If a problem occurs while attempting 3140 * to encode the entry. 3141 */ 3142 private void encodeAttributes(ByteStringBuilder buffer, 3143 Map<AttributeType,List<Attribute>> attributes, 3144 EntryEncodeConfig config) 3145 throws DirectoryException 3146 { 3147 int numAttributes = 0; 3148 3149 // First count how many attributes are there to encode. 3150 for (List<Attribute> attrList : attributes.values()) 3151 { 3152 Attribute a; 3153 for (int i = 0; i < attrList.size(); i++) 3154 { 3155 a = attrList.get(i); 3156 if (a.isVirtual() || a.isEmpty()) 3157 { 3158 continue; 3159 } 3160 3161 numAttributes++; 3162 } 3163 } 3164 3165 // Encoded one-to-five byte number of attributes 3166 buffer.appendBERLength(numAttributes); 3167 3168 if (config.compressAttributeDescriptions()) 3169 { 3170 for (List<Attribute> attrList : attributes.values()) 3171 { 3172 for (Attribute a : attrList) 3173 { 3174 if (a.isVirtual() || a.isEmpty()) 3175 { 3176 continue; 3177 } 3178 3179 config.getCompressedSchema().encodeAttribute(buffer, a); 3180 } 3181 } 3182 } 3183 else 3184 { 3185 // The attributes will be encoded as a sequence of: 3186 // - A UTF-8 byte representation of the attribute name. 3187 // - A zero delimiter 3188 // - A one-to-five byte number of values for the attribute 3189 // - A sequence of: 3190 // - A one-to-five byte length for the value 3191 // - A UTF-8 byte representation for the value 3192 for (List<Attribute> attrList : attributes.values()) 3193 { 3194 for (Attribute a : attrList) 3195 { 3196 buffer.appendBytes(getBytes(a.getAttributeDescription().toString())); 3197 buffer.appendByte(0x00); 3198 3199 buffer.appendBERLength(a.size()); 3200 for(ByteString v : a) 3201 { 3202 buffer.appendBERLength(v.length()); 3203 buffer.appendBytes(v); 3204 } 3205 } 3206 } 3207 } 3208 } 3209 3210 3211 /** 3212 * Decodes the provided byte array as an entry. 3213 * 3214 * @param entryBuffer The byte array containing the data to be 3215 * decoded. 3216 * 3217 * @return The decoded entry. 3218 * 3219 * @throws DirectoryException If the provided byte array cannot be 3220 * decoded as an entry. 3221 */ 3222 public static Entry decode(ByteSequenceReader entryBuffer) 3223 throws DirectoryException 3224 { 3225 return decode(entryBuffer, 3226 DirectoryServer.getDefaultCompressedSchema()); 3227 } 3228 3229 3230 3231 /** 3232 * Decodes the provided byte array as an entry using the V3 3233 * encoding. 3234 * 3235 * @param entryBuffer The byte buffer containing the data to 3236 * be decoded. 3237 * @param compressedSchema The compressed schema manager to use 3238 * when decoding tokenized schema 3239 * elements. 3240 * 3241 * @return The decoded entry. 3242 * 3243 * @throws DirectoryException If the provided byte array cannot be 3244 * decoded as an entry. 3245 */ 3246 public static Entry decode(ByteSequenceReader entryBuffer, 3247 CompressedSchema compressedSchema) 3248 throws DirectoryException 3249 { 3250 try 3251 { 3252 // The first byte must be the entry version. If it's not one 3253 // we recognize, then that's an error. 3254 Byte version = entryBuffer.readByte(); 3255 if (version != 0x03 && version != 0x02 && version != 0x01) 3256 { 3257 LocalizableMessage message = ERR_ENTRY_DECODE_UNRECOGNIZED_VERSION.get( 3258 byteToHex(version)); 3259 throw new DirectoryException( 3260 DirectoryServer.getServerErrorResultCode(), 3261 message); 3262 } 3263 3264 EntryEncodeConfig config; 3265 if(version != 0x01) 3266 { 3267 // Next is the length of the encoded configuration. 3268 int configLength = entryBuffer.readBERLength(); 3269 3270 // Next is the encoded configuration itself. 3271 config = 3272 EntryEncodeConfig.decode(entryBuffer, configLength, 3273 compressedSchema); 3274 } 3275 else 3276 { 3277 config = EntryEncodeConfig.DEFAULT_CONFIG; 3278 } 3279 3280 // If we should have included the DN in the entry, then it's 3281 // next. 3282 DN dn; 3283 if (config.excludeDN()) 3284 { 3285 dn = DN.rootDN(); 3286 } 3287 else 3288 { 3289 // Next is the length of the DN. It may be a single byte or 3290 // multiple bytes. 3291 int dnLength = entryBuffer.readBERLength(); 3292 3293 3294 // Next is the DN itself. 3295 ByteSequence dnBytes = entryBuffer.readByteSequence(dnLength); 3296 dn = DN.valueOf(dnBytes.toByteString()); 3297 } 3298 3299 3300 // Next is the set of encoded object classes. The encoding will 3301 // depend on the configuration. 3302 Map<ObjectClass,String> objectClasses = 3303 decodeObjectClasses(version, entryBuffer, config); 3304 3305 3306 // Now, we should iterate through the user and operational attributes and 3307 // decode each one. 3308 Map<AttributeType, List<Attribute>> userAttributes = 3309 decodeAttributes(version, entryBuffer, config); 3310 Map<AttributeType, List<Attribute>> operationalAttributes = 3311 decodeAttributes(version, entryBuffer, config); 3312 3313 3314 // We've got everything that we need, so create and return the entry. 3315 return new Entry(dn, objectClasses, userAttributes, 3316 operationalAttributes); 3317 } 3318 catch (DirectoryException de) 3319 { 3320 throw de; 3321 } 3322 catch (Exception e) 3323 { 3324 logger.traceException(e); 3325 3326 LocalizableMessage message = 3327 ERR_ENTRY_DECODE_EXCEPTION.get(getExceptionMessage(e)); 3328 throw new DirectoryException( 3329 DirectoryServer.getServerErrorResultCode(), 3330 message, e); 3331 } 3332 } 3333 3334 3335 /** 3336 * Decode the object classes of an encoded entry. 3337 * 3338 * @param ver The version of the entry encoding. 3339 * @param entryBuffer The byte sequence containing the encoded 3340 * entry. 3341 * @param config The configuration that may be used to control how 3342 * the entry is encoded. 3343 * 3344 * @return A map of the decoded object classes. 3345 * @throws DirectoryException If a problem occurs while attempting 3346 * to encode the entry. 3347 */ 3348 private static Map<ObjectClass,String> decodeObjectClasses( 3349 byte ver, ByteSequenceReader entryBuffer, 3350 EntryEncodeConfig config) throws DirectoryException 3351 { 3352 // Next is the set of encoded object classes. The encoding will 3353 // depend on the configuration. 3354 if (config.compressObjectClassSets()) 3355 { 3356 return config.getCompressedSchema().decodeObjectClasses(entryBuffer); 3357 } 3358 3359 Map<ObjectClass, String> objectClasses; 3360 { 3361 if(ver < 0x03) 3362 { 3363 // Next is the length of the object classes. It may be a 3364 // single byte or multiple bytes. 3365 int ocLength = entryBuffer.readBERLength(); 3366 3367 // The set of object classes will be encoded as a single 3368 // string with the object class names separated by zeros. 3369 objectClasses = new LinkedHashMap<>(); 3370 int startPos = entryBuffer.position(); 3371 for (int i=0; i < ocLength; i++) 3372 { 3373 if (entryBuffer.readByte() == 0x00) 3374 { 3375 int endPos = entryBuffer.position() - 1; 3376 addObjectClass(objectClasses, entryBuffer, startPos, endPos); 3377 3378 entryBuffer.skip(1); 3379 startPos = entryBuffer.position(); 3380 } 3381 } 3382 int endPos = entryBuffer.position(); 3383 addObjectClass(objectClasses, entryBuffer, startPos, endPos); 3384 } 3385 else 3386 { 3387 // Next is the number of zero terminated object classes. 3388 int numOC = entryBuffer.readBERLength(); 3389 objectClasses = new LinkedHashMap<>(numOC); 3390 for(int i = 0; i < numOC; i++) 3391 { 3392 int startPos = entryBuffer.position(); 3393 while(entryBuffer.readByte() != 0x00) 3394 {} 3395 int endPos = entryBuffer.position() - 1; 3396 addObjectClass(objectClasses, entryBuffer, startPos, endPos); 3397 entryBuffer.skip(1); 3398 } 3399 } 3400 } 3401 3402 return objectClasses; 3403 } 3404 3405 /** 3406 * Adds the objectClass contained in the buffer to the map of object class. 3407 * 3408 * @param objectClasses 3409 * the Map where to add the objectClass 3410 * @param entryBuffer 3411 * the buffer containing the objectClass name 3412 * @param startPos 3413 * the starting position in the buffer 3414 * @param endPos 3415 * the ending position in the buffer 3416 */ 3417 private static void addObjectClass(Map<ObjectClass, String> objectClasses, 3418 ByteSequenceReader entryBuffer, int startPos, int endPos) 3419 { 3420 entryBuffer.position(startPos); 3421 final String ocName = entryBuffer.readStringUtf8(endPos - startPos); 3422 objectClasses.put(DirectoryServer.getSchema().getObjectClass(ocName), ocName); 3423 } 3424 3425 /** 3426 * Decode the attributes of an encoded entry. 3427 * 3428 * @param ver The version of the entry encoding. 3429 * @param entryBuffer The byte sequence containing the encoded 3430 * entry. 3431 * @param config The configuration that may be used to control how 3432 * the entry is encoded. 3433 * 3434 * @return A map of the decoded object classes. 3435 * @throws DirectoryException If a problem occurs while attempting 3436 * to encode the entry. 3437 */ 3438 private static Map<AttributeType, List<Attribute>> 3439 decodeAttributes(Byte ver, ByteSequenceReader entryBuffer, 3440 EntryEncodeConfig config) throws DirectoryException 3441 { 3442 // Next is the total number of attributes. It may be a 3443 // single byte or multiple bytes. 3444 int attrs = entryBuffer.readBERLength(); 3445 3446 3447 // Now, we should iterate through the attributes and decode each one. 3448 Map<AttributeType, List<Attribute>> attributes = new LinkedHashMap<>(attrs); 3449 if (config.compressAttributeDescriptions()) 3450 { 3451 for (int i=0; i < attrs; i++) 3452 { 3453 if(ver < 0x03) 3454 { 3455 // Version 2 includes a total attribute length 3456 entryBuffer.readBERLength(); 3457 } 3458 // Decode the attribute. 3459 Attribute a = config.getCompressedSchema().decodeAttribute(entryBuffer); 3460 AttributeType attrType = a.getAttributeDescription().getAttributeType(); 3461 List<Attribute> attrList = attributes.get(attrType); 3462 if (attrList == null) 3463 { 3464 attrList = new ArrayList<>(1); 3465 attributes.put(attrType, attrList); 3466 } 3467 attrList.add(a); 3468 } 3469 } 3470 else 3471 { 3472 AttributeBuilder builder = new AttributeBuilder(); 3473 int startPos; 3474 int endPos; 3475 for (int i=0; i < attrs; i++) 3476 { 3477 // First, we have the zero-terminated attribute name. 3478 startPos = entryBuffer.position(); 3479 while (entryBuffer.readByte() != 0x00) 3480 {} 3481 endPos = entryBuffer.position()-1; 3482 entryBuffer.position(startPos); 3483 String name = entryBuffer.readStringUtf8(endPos - startPos); 3484 entryBuffer.skip(1); 3485 3486 AttributeDescription attrDesc = AttributeDescription.valueOf(name); 3487 builder.setAttributeDescription(attrDesc); 3488 3489 // Next, we have the number of values. 3490 int numValues = entryBuffer.readBERLength(); 3491 3492 // Next, we have the sequence of length-value pairs. 3493 for (int j=0; j < numValues; j++) 3494 { 3495 int valueLength = entryBuffer.readBERLength(); 3496 builder.add(entryBuffer.readByteSequence(valueLength).toByteString()); 3497 } 3498 3499 3500 // Create the attribute and add it to the set of attributes. 3501 Attribute a = builder.toAttribute(); 3502 AttributeType attributeType = a.getAttributeDescription().getAttributeType(); 3503 List<Attribute> attrList = attributes.get(attributeType); 3504 if (attrList == null) 3505 { 3506 attrList = new ArrayList<>(1); 3507 attributes.put(attributeType, attrList); 3508 } 3509 attrList.add(a); 3510 } 3511 } 3512 3513 return attributes; 3514 } 3515 3516 /** 3517 * Retrieves a list of the lines for this entry in LDIF form. Long 3518 * lines will not be wrapped automatically. 3519 * 3520 * @return A list of the lines for this entry in LDIF form. 3521 */ 3522 public List<StringBuilder> toLDIF() 3523 { 3524 List<StringBuilder> ldifLines = new LinkedList<>(); 3525 3526 // First, append the DN. 3527 StringBuilder dnLine = new StringBuilder("dn"); 3528 appendLDIFSeparatorAndValue(dnLine, ByteString.valueOfUtf8(dn.toString())); 3529 ldifLines.add(dnLine); 3530 3531 // Next, add the set of objectclasses. 3532 for (String s : objectClasses.values()) 3533 { 3534 StringBuilder ocLine = new StringBuilder("objectClass: ").append(s); 3535 ldifLines.add(ocLine); 3536 } 3537 3538 // Finally, add the set of user and operational attributes. 3539 addLinesForAttributes(ldifLines, userAttributes); 3540 addLinesForAttributes(ldifLines, operationalAttributes); 3541 3542 return ldifLines; 3543 } 3544 3545 3546 /** 3547 * Add LDIF lines for each passed in attributes. 3548 * 3549 * @param ldifLines 3550 * the List where to add the LDIF lines 3551 * @param attributes 3552 * the List of attributes to convert into LDIf lines 3553 */ 3554 private void addLinesForAttributes(List<StringBuilder> ldifLines, 3555 Map<AttributeType, List<Attribute>> attributes) 3556 { 3557 for (List<Attribute> attrList : attributes.values()) 3558 { 3559 for (Attribute a : attrList) 3560 { 3561 String attrName = a.getAttributeDescription().toString(); 3562 for (ByteString v : a) 3563 { 3564 StringBuilder attrLine = new StringBuilder(attrName); 3565 appendLDIFSeparatorAndValue(attrLine, v); 3566 ldifLines.add(attrLine); 3567 } 3568 } 3569 } 3570 } 3571 3572 3573 /** 3574 * Writes this entry in LDIF form according to the provided 3575 * configuration. 3576 * 3577 * @param exportConfig The configuration that specifies how the 3578 * entry should be written. 3579 * 3580 * @return <CODE>true</CODE> if the entry is actually written, or 3581 * <CODE>false</CODE> if it is not for some reason. 3582 * 3583 * @throws IOException If a problem occurs while writing the 3584 * information. 3585 * 3586 * @throws LDIFException If a problem occurs while trying to 3587 * determine whether to write the entry. 3588 */ 3589 public boolean toLDIF(LDIFExportConfig exportConfig) 3590 throws IOException, LDIFException 3591 { 3592 // See if this entry should be included in the export at all. 3593 try 3594 { 3595 if (! exportConfig.includeEntry(this)) 3596 { 3597 if (logger.isTraceEnabled()) 3598 { 3599 logger.trace("Skipping entry %s because of the export configuration.", dn); 3600 } 3601 return false; 3602 } 3603 } 3604 catch (Exception e) 3605 { 3606 logger.traceException(e); 3607 throw new LDIFException(ERR_LDIF_COULD_NOT_EVALUATE_FILTERS_FOR_EXPORT.get(dn, e), e); 3608 } 3609 3610 3611 // Invoke LDIF export plugins on the entry if appropriate. 3612 if (exportConfig.invokeExportPlugins()) 3613 { 3614 PluginConfigManager pluginConfigManager = 3615 DirectoryServer.getPluginConfigManager(); 3616 PluginResult.ImportLDIF pluginResult = 3617 pluginConfigManager.invokeLDIFExportPlugins(exportConfig, 3618 this); 3619 if (! pluginResult.continueProcessing()) 3620 { 3621 return false; 3622 } 3623 } 3624 3625 3626 // Get the information necessary to write the LDIF. 3627 BufferedWriter writer = exportConfig.getWriter(); 3628 int wrapColumn = exportConfig.getWrapColumn(); 3629 boolean wrapLines = wrapColumn > 1; 3630 3631 3632 // First, write the DN. It will always be included. 3633 StringBuilder dnLine = new StringBuilder("dn"); 3634 appendLDIFSeparatorAndValue(dnLine, ByteString.valueOfUtf8(dn.toString())); 3635 LDIFWriter.writeLDIFLine(dnLine, writer, wrapLines, wrapColumn); 3636 3637 3638 // Next, the set of objectclasses. 3639 final boolean typesOnly = exportConfig.typesOnly(); 3640 if (exportConfig.includeObjectClasses()) 3641 { 3642 if (typesOnly) 3643 { 3644 StringBuilder ocLine = new StringBuilder("objectClass:"); 3645 LDIFWriter.writeLDIFLine(ocLine, writer, wrapLines, wrapColumn); 3646 } 3647 else 3648 { 3649 for (String s : objectClasses.values()) 3650 { 3651 StringBuilder ocLine = new StringBuilder("objectClass: ").append(s); 3652 LDIFWriter.writeLDIFLine(ocLine, writer, wrapLines, wrapColumn); 3653 } 3654 } 3655 } 3656 else 3657 { 3658 if (logger.isTraceEnabled()) 3659 { 3660 logger.trace("Skipping objectclasses for entry %s because of the export configuration.", dn); 3661 } 3662 } 3663 3664 3665 // Now the set of user attributes. 3666 writeLDIFLines(userAttributes, typesOnly, "user", exportConfig, writer, 3667 wrapColumn, wrapLines); 3668 3669 3670 // Next, the set of operational attributes. 3671 if (exportConfig.includeOperationalAttributes()) 3672 { 3673 writeLDIFLines(operationalAttributes, typesOnly, "operational", 3674 exportConfig, writer, wrapColumn, wrapLines); 3675 } 3676 else 3677 { 3678 if (logger.isTraceEnabled()) 3679 { 3680 logger.trace( 3681 "Skipping all operational attributes for entry %s " + 3682 "because of the export configuration.", dn); 3683 } 3684 } 3685 3686 3687 // If we are not supposed to include virtual attributes, then 3688 // write any attributes that may normally be suppressed by a 3689 // virtual attribute. 3690 if (! exportConfig.includeVirtualAttributes()) 3691 { 3692 for (AttributeType t : suppressedAttributes.keySet()) 3693 { 3694 if (exportConfig.includeAttribute(t)) 3695 { 3696 for (Attribute a : suppressedAttributes.get(t)) 3697 { 3698 writeLDIFLine(a, typesOnly, writer, wrapLines, wrapColumn); 3699 } 3700 } 3701 } 3702 } 3703 3704 3705 // Make sure there is a blank line after the entry. 3706 writer.newLine(); 3707 3708 3709 return true; 3710 } 3711 3712 3713 /** 3714 * Writes the provided List of attributes to LDIF using the provided 3715 * information. 3716 * 3717 * @param attributes 3718 * the List of attributes to write as LDIF 3719 * @param typesOnly 3720 * if true, only writes the type information, else writes the type 3721 * information and values for the attribute. 3722 * @param attributeType 3723 * the type of attribute being written to LDIF 3724 * @param exportConfig 3725 * configures the export to LDIF 3726 * @param writer 3727 * The writer to which the data should be written. It must not be 3728 * <CODE>null</CODE>. 3729 * @param wrapLines 3730 * Indicates whether to wrap long lines. 3731 * @param wrapColumn 3732 * The column at which long lines should be wrapped. 3733 * @throws IOException 3734 * If a problem occurs while writing the information. 3735 */ 3736 private void writeLDIFLines(Map<AttributeType, List<Attribute>> attributes, 3737 final boolean typesOnly, String attributeType, 3738 LDIFExportConfig exportConfig, BufferedWriter writer, int wrapColumn, 3739 boolean wrapLines) throws IOException 3740 { 3741 for (AttributeType attrType : attributes.keySet()) 3742 { 3743 if (exportConfig.includeAttribute(attrType)) 3744 { 3745 List<Attribute> attrList = attributes.get(attrType); 3746 for (Attribute a : attrList) 3747 { 3748 if (a.isVirtual() && !exportConfig.includeVirtualAttributes()) 3749 { 3750 continue; 3751 } 3752 3753 writeLDIFLine(a, typesOnly, writer, wrapLines, wrapColumn); 3754 } 3755 } 3756 else 3757 { 3758 if (logger.isTraceEnabled()) 3759 { 3760 logger.trace("Skipping %s attribute %s for entry %s " 3761 + "because of the export configuration.", attributeType, attrType.getNameOrOID(), dn); 3762 } 3763 } 3764 } 3765 } 3766 3767 3768 /** 3769 * Writes the provided attribute to LDIF using the provided information. 3770 * 3771 * @param attribute 3772 * the attribute to write to LDIF 3773 * @param typesOnly 3774 * if true, only writes the type information, else writes the type 3775 * information and values for the attribute. 3776 * @param writer 3777 * The writer to which the data should be written. It must not be 3778 * <CODE>null</CODE>. 3779 * @param wrapLines 3780 * Indicates whether to wrap long lines. 3781 * @param wrapColumn 3782 * The column at which long lines should be wrapped. 3783 * @throws IOException 3784 * If a problem occurs while writing the information. 3785 */ 3786 private void writeLDIFLine(Attribute attribute, final boolean typesOnly, 3787 BufferedWriter writer, boolean wrapLines, int wrapColumn) 3788 throws IOException 3789 { 3790 String attrName = attribute.getAttributeDescription().toString(); 3791 if (typesOnly) 3792 { 3793 StringBuilder attrLine = new StringBuilder(attrName); 3794 attrLine.append(":"); 3795 3796 LDIFWriter.writeLDIFLine(attrLine, writer, wrapLines, wrapColumn); 3797 } 3798 else 3799 { 3800 for (ByteString v : attribute) 3801 { 3802 StringBuilder attrLine = new StringBuilder(attrName); 3803 appendLDIFSeparatorAndValue(attrLine, v); 3804 LDIFWriter.writeLDIFLine(attrLine, writer, wrapLines, wrapColumn); 3805 } 3806 } 3807 } 3808 3809 3810 3811 /** 3812 * Retrieves the name of the protocol associated with this protocol 3813 * element. 3814 * 3815 * @return The name of the protocol associated with this protocol 3816 * element. 3817 */ 3818 @Override 3819 public String getProtocolElementName() 3820 { 3821 return "Entry"; 3822 } 3823 3824 3825 3826 /** 3827 * Retrieves a hash code for this entry. 3828 * 3829 * @return The hash code for this entry. 3830 */ 3831 @Override 3832 public int hashCode() 3833 { 3834 int hashCode = dn.hashCode(); 3835 for (ObjectClass oc : objectClasses.keySet()) 3836 { 3837 hashCode += oc.hashCode(); 3838 } 3839 3840 hashCode += hashCode(userAttributes.values()); 3841 hashCode += hashCode(operationalAttributes.values()); 3842 return hashCode; 3843 } 3844 3845 /** 3846 * Computes the hashCode for the list of attributes list. 3847 * 3848 * @param attributesLists 3849 * the attributes for which to commpute the hashCode 3850 * @return the hashCode for the list of attributes list. 3851 */ 3852 private int hashCode(Collection<List<Attribute>> attributesLists) 3853 { 3854 int result = 0; 3855 for (List<Attribute> attributes : attributesLists) 3856 { 3857 for (Attribute a : attributes) 3858 { 3859 result += a.hashCode(); 3860 } 3861 } 3862 return result; 3863 } 3864 3865 3866 3867 /** 3868 * Indicates whether the provided object is equal to this entry. In 3869 * order for the object to be considered equal, it must be an entry 3870 * with the same DN, set of object classes, and set of user and 3871 * operational attributes. 3872 * 3873 * @param o The object for which to make the determination. 3874 * 3875 * @return {@code true} if the provided object may be considered 3876 * equal to this entry, or {@code false} if not. 3877 */ 3878 @Override 3879 public boolean equals(Object o) 3880 { 3881 if (this == o) 3882 { 3883 return true; 3884 } 3885 if (o == null) 3886 { 3887 return false; 3888 } 3889 if (! (o instanceof Entry)) 3890 { 3891 return false; 3892 } 3893 3894 Entry e = (Entry) o; 3895 return dn.equals(e.dn) 3896 && objectClasses.keySet().equals(e.objectClasses.keySet()) 3897 && equals(userAttributes, e.userAttributes) 3898 && equals(operationalAttributes, e.operationalAttributes); 3899 } 3900 3901 /** 3902 * Returns whether the 2 Maps are equal. 3903 * 3904 * @param attributes1 3905 * the first Map of attributes 3906 * @param attributes2 3907 * the second Map of attributes 3908 * @return true if the 2 Maps are equal, false otherwise 3909 */ 3910 private boolean equals(Map<AttributeType, List<Attribute>> attributes1, 3911 Map<AttributeType, List<Attribute>> attributes2) 3912 { 3913 for (AttributeType at : attributes1.keySet()) 3914 { 3915 List<Attribute> list1 = attributes1.get(at); 3916 List<Attribute> list2 = attributes2.get(at); 3917 if (list2 == null || list1.size() != list2.size()) 3918 { 3919 return false; 3920 } 3921 for (Attribute a : list1) 3922 { 3923 if (!list2.contains(a)) 3924 { 3925 return false; 3926 } 3927 } 3928 } 3929 return true; 3930 } 3931 3932 3933 3934 /** 3935 * Retrieves a string representation of this protocol element. 3936 * 3937 * @return A string representation of this protocol element. 3938 */ 3939 @Override 3940 public String toString() 3941 { 3942 return toLDIFString(); 3943 } 3944 3945 3946 3947 /** 3948 * Appends a string representation of this protocol element to the 3949 * provided buffer. 3950 * 3951 * @param buffer The buffer into which the string representation 3952 * should be written. 3953 */ 3954 @Override 3955 public void toString(StringBuilder buffer) 3956 { 3957 buffer.append(this); 3958 } 3959 3960 3961 3962 /** 3963 * Appends a string representation of this protocol element to the 3964 * provided buffer. 3965 * 3966 * @param buffer The buffer into which the string representation 3967 * should be written. 3968 * @param indent The number of spaces that should be used to 3969 * indent the resulting string representation. 3970 */ 3971 @Override 3972 public void toString(StringBuilder buffer, int indent) 3973 { 3974 StringBuilder indentBuf = new StringBuilder(indent); 3975 for (int i=0 ; i < indent; i++) 3976 { 3977 indentBuf.append(' '); 3978 } 3979 3980 for (StringBuilder b : toLDIF()) 3981 { 3982 buffer.append(indentBuf); 3983 buffer.append(b); 3984 buffer.append(EOL); 3985 } 3986 } 3987 3988 3989 3990 /** 3991 * Retrieves a string representation of this entry in LDIF form. 3992 * 3993 * @return A string representation of this entry in LDIF form. 3994 */ 3995 public String toLDIFString() 3996 { 3997 StringBuilder buffer = new StringBuilder(); 3998 3999 for (StringBuilder ldifLine : toLDIF()) 4000 { 4001 buffer.append(ldifLine); 4002 buffer.append(EOL); 4003 } 4004 4005 return buffer.toString(); 4006 } 4007 4008 4009 4010 /** 4011 * Appends a single-line representation of this entry to the 4012 * provided buffer. 4013 * 4014 * @param buffer The buffer to which the information should be 4015 * written. 4016 */ 4017 public void toSingleLineString(StringBuilder buffer) 4018 { 4019 buffer.append("Entry(dn=\""); 4020 buffer.append(dn); 4021 buffer.append("\",objectClasses={"); 4022 4023 Iterator<String> iterator = objectClasses.values().iterator(); 4024 if (iterator.hasNext()) 4025 { 4026 buffer.append(iterator.next()); 4027 4028 while (iterator.hasNext()) 4029 { 4030 buffer.append(","); 4031 buffer.append(iterator.next()); 4032 } 4033 } 4034 4035 buffer.append("},userAttrs={"); 4036 appendAttributes(buffer, userAttributes.values()); 4037 buffer.append("},operationalAttrs={"); 4038 appendAttributes(buffer, operationalAttributes.values()); 4039 buffer.append("})"); 4040 } 4041 4042 /** 4043 * Appends the attributes to the StringBuilder. 4044 * 4045 * @param buffer 4046 * the StringBuilder where to append 4047 * @param attributesLists 4048 * the attributesLists to append 4049 */ 4050 private void appendAttributes(StringBuilder buffer, 4051 Collection<List<Attribute>> attributesLists) 4052 { 4053 boolean firstAttr = true; 4054 for (List<Attribute> attributes : attributesLists) 4055 { 4056 for (Attribute a : attributes) 4057 { 4058 if (firstAttr) 4059 { 4060 firstAttr = false; 4061 } 4062 else 4063 { 4064 buffer.append(","); 4065 } 4066 4067 buffer.append(a.getAttributeDescription()); 4068 4069 buffer.append("={"); 4070 Iterator<ByteString> valueIterator = a.iterator(); 4071 if (valueIterator.hasNext()) 4072 { 4073 buffer.append(valueIterator.next()); 4074 4075 while (valueIterator.hasNext()) 4076 { 4077 buffer.append(","); 4078 buffer.append(valueIterator.next()); 4079 } 4080 } 4081 4082 buffer.append("}"); 4083 } 4084 } 4085 } 4086 4087 4088 4089 /** 4090 * Retrieves the requested attribute element for the specified 4091 * attribute type and options or <code>null</code> if this entry 4092 * does not contain an attribute with the specified attribute type 4093 * and options. 4094 * 4095 * @param attributeDescription 4096 * The attribute description to retrieve. 4097 * @return The requested attribute element for the specified 4098 * attribute type and options, or <code>null</code> if the 4099 * specified attribute type is not present in this entry 4100 * with the provided set of options. 4101 */ 4102 public Attribute getExactAttribute(AttributeDescription attributeDescription) 4103 { 4104 List<Attribute> attributes = getAttributes(attributeDescription.getAttributeType()); 4105 if (attributes != null) 4106 { 4107 for (Attribute attribute : attributes) 4108 { 4109 if (attribute.getAttributeDescription().equals(attributeDescription)) 4110 { 4111 return attribute; 4112 } 4113 } 4114 } 4115 else if (attributeDescription.getAttributeType().equals(CoreSchema.getObjectClassAttributeType())) 4116 { 4117 return getObjectClassAttribute(); 4118 } 4119 return null; 4120 } 4121 4122 4123 4124 /** 4125 * Adds the provided attribute to this entry. If an attribute with 4126 * the provided type and options already exists, then it will be 4127 * either merged or replaced depending on the value of 4128 * <code>replace</code>. 4129 * 4130 * @param attribute 4131 * The attribute to add/replace in this entry. 4132 * @param duplicateValues 4133 * A list to which any duplicate values will be added. 4134 * @param replace 4135 * <code>true</code> if the attribute should replace any 4136 * existing attribute. 4137 */ 4138 private void setAttribute(Attribute attribute, 4139 List<ByteString> duplicateValues, boolean replace) 4140 { 4141 attachment = null; 4142 4143 AttributeDescription attrDesc = attribute.getAttributeDescription(); 4144 AttributeType attrType = attrDesc.getAttributeType(); 4145 if (attrType.isObjectClass()) 4146 { 4147 // We will not do any validation of the object classes - this is 4148 // left to the caller. 4149 if (replace) 4150 { 4151 objectClasses.clear(); 4152 } 4153 4154 MatchingRule rule = attrType.getEqualityMatchingRule(); 4155 for (ByteString v : attribute) 4156 { 4157 String name = v.toString(); 4158 String lowerName = toLowerName(rule, v); 4159 4160 // Create a default object class if necessary. 4161 ObjectClass oc = DirectoryServer.getSchema().getObjectClass(lowerName); 4162 4163 if (replace) 4164 { 4165 objectClasses.put(oc, name); 4166 } 4167 else 4168 { 4169 if (objectClasses.containsKey(oc)) 4170 { 4171 duplicateValues.add(v); 4172 } 4173 else 4174 { 4175 objectClasses.put(oc, name); 4176 } 4177 } 4178 } 4179 4180 return; 4181 } 4182 4183 List<Attribute> attributes = getAttributes(attrType); 4184 if (attributes == null) 4185 { 4186 // Do nothing if we are deleting a non-existing attribute. 4187 if (replace && attribute.isEmpty()) 4188 { 4189 return; 4190 } 4191 4192 // We are adding the first attribute with this attribute type. 4193 putAttributes(attrType, newArrayList(attribute)); 4194 return; 4195 } 4196 4197 // There are already attributes with the same attribute type. 4198 for (int i = 0; i < attributes.size(); i++) 4199 { 4200 Attribute a = attributes.get(i); 4201 if (a.getAttributeDescription().equals(attrDesc)) 4202 { 4203 if (replace) 4204 { 4205 if (!attribute.isEmpty()) 4206 { 4207 attributes.set(i, attribute); 4208 } 4209 else 4210 { 4211 attributes.remove(i); 4212 4213 if (attributes.isEmpty()) 4214 { 4215 removeAttributes(attrType); 4216 } 4217 } 4218 } 4219 else 4220 { 4221 AttributeBuilder builder = new AttributeBuilder(a); 4222 for (ByteString v : attribute) 4223 { 4224 if (!builder.add(v)) 4225 { 4226 duplicateValues.add(v); 4227 } 4228 } 4229 attributes.set(i, builder.toAttribute()); 4230 } 4231 return; 4232 } 4233 } 4234 4235 // There were no attributes with the same options. 4236 if (replace && attribute.isEmpty()) 4237 { 4238 // Do nothing. 4239 return; 4240 } 4241 4242 attributes.add(attribute); 4243 } 4244 4245 4246 4247 /** 4248 * Returns an entry containing only those attributes of this entry 4249 * which match the provided criteria. 4250 * 4251 * @param attrNameList 4252 * The list of attributes to include, may include wild 4253 * cards. 4254 * @param omitValues 4255 * Indicates whether to omit attribute values when 4256 * processing. 4257 * @param omitReal 4258 * Indicates whether to exclude real attributes. 4259 * @param omitVirtual 4260 * Indicates whether to exclude virtual attributes. 4261 * @return An entry containing only those attributes of this entry 4262 * which match the provided criteria. 4263 */ 4264 public Entry filterEntry(Set<String> attrNameList, 4265 boolean omitValues, boolean omitReal, boolean omitVirtual) 4266 { 4267 final AttributeType ocType = CoreSchema.getObjectClassAttributeType(); 4268 4269 Map<ObjectClass, String> objectClassesCopy; 4270 Map<AttributeType, List<Attribute>> userAttrsCopy; 4271 Map<AttributeType, List<Attribute>> operationalAttrsCopy; 4272 4273 if (attrNameList == null || attrNameList.isEmpty()) 4274 { 4275 // Common case: return filtered user attributes. 4276 userAttrsCopy = new LinkedHashMap<>(userAttributes.size()); 4277 operationalAttrsCopy = new LinkedHashMap<>(0); 4278 4279 if (omitReal) 4280 { 4281 objectClassesCopy = new LinkedHashMap<>(0); 4282 } 4283 else if (omitValues) 4284 { 4285 objectClassesCopy = new LinkedHashMap<>(0); 4286 4287 // Add empty object class attribute. 4288 userAttrsCopy.put(ocType, newArrayList(Attributes.empty(ocType))); 4289 } 4290 else 4291 { 4292 objectClassesCopy = new LinkedHashMap<>(objectClasses); 4293 4294 // First, add the objectclass attribute. 4295 Attribute ocAttr = getObjectClassAttribute(); 4296 if (ocAttr != null) 4297 { 4298 userAttrsCopy.put(ocType, newArrayList(ocAttr)); 4299 } 4300 } 4301 4302 // Copy all user attributes. 4303 deepCopy(userAttributes, userAttrsCopy, omitValues, true, 4304 omitReal, omitVirtual, true); 4305 } 4306 else 4307 { 4308 // Incrementally build table of attributes. 4309 if (omitReal || omitValues) 4310 { 4311 objectClassesCopy = new LinkedHashMap<>(0); 4312 } 4313 else 4314 { 4315 objectClassesCopy = new LinkedHashMap<>(objectClasses.size()); 4316 } 4317 4318 userAttrsCopy = new LinkedHashMap<>(userAttributes.size()); 4319 operationalAttrsCopy = new LinkedHashMap<>(operationalAttributes.size()); 4320 4321 for (String attrName : attrNameList) 4322 { 4323 if ("*".equals(attrName)) 4324 { 4325 // This is a special placeholder indicating that all user 4326 // attributes should be returned. 4327 if (!omitReal) 4328 { 4329 if (omitValues) 4330 { 4331 // Add empty object class attribute. 4332 userAttrsCopy.put(ocType, newArrayList(Attributes.empty(ocType))); 4333 } 4334 else 4335 { 4336 // Add the objectclass attribute. 4337 objectClassesCopy.putAll(objectClasses); 4338 Attribute ocAttr = getObjectClassAttribute(); 4339 if (ocAttr != null) 4340 { 4341 userAttrsCopy.put(ocType, newArrayList(ocAttr)); 4342 } 4343 } 4344 } 4345 4346 // Copy all user attributes. 4347 deepCopy(userAttributes, userAttrsCopy, omitValues, true, 4348 omitReal, omitVirtual, true); 4349 continue; 4350 } 4351 else if ("+".equals(attrName)) 4352 { 4353 // This is a special placeholder indicating that all 4354 // operational attributes should be returned. 4355 deepCopy(operationalAttributes, operationalAttrsCopy, 4356 omitValues, true, omitReal, omitVirtual, true); 4357 continue; 4358 } 4359 4360 final AttributeDescription attrDesc; 4361 try 4362 { 4363 attrDesc = AttributeDescription.valueOf(attrName); 4364 } 4365 catch (LocalizedIllegalArgumentException e) 4366 { 4367 // For compatibility tolerate and ignore illegal attribute types, instead of 4368 // aborting with a ProtocolError (2) as per the RFC. See OPENDJ-2813. 4369 logger.traceException(e); 4370 continue; 4371 } 4372 attrName = attrDesc.getNameOrOID(); 4373 final AttributeType attrType = attrDesc.getAttributeType(); 4374 if (attrType.isPlaceHolder()) 4375 { 4376 // Unrecognized attribute type - do best effort search. 4377 for (Map.Entry<AttributeType, List<Attribute>> e : userAttributes.entrySet()) 4378 { 4379 AttributeType t = e.getKey(); 4380 if (t.hasNameOrOID(attrType.getNameOrOID())) 4381 { 4382 mergeAttributeLists(e.getValue(), userAttrsCopy, attrDesc, 4383 omitValues, omitReal, omitVirtual); 4384 continue; 4385 } 4386 } 4387 4388 for (Map.Entry<AttributeType, List<Attribute>> e : operationalAttributes.entrySet()) 4389 { 4390 AttributeType t = e.getKey(); 4391 if (t.hasNameOrOID(attrType.getNameOrOID())) 4392 { 4393 mergeAttributeLists(e.getValue(), operationalAttrsCopy, attrDesc, 4394 omitValues, omitReal, omitVirtual); 4395 continue; 4396 } 4397 } 4398 } 4399 else 4400 { 4401 // Recognized attribute type. 4402 if (attrType.isObjectClass()) { 4403 if (!omitReal) 4404 { 4405 if (omitValues) 4406 { 4407 userAttrsCopy.put(ocType, newArrayList(Attributes.empty(ocType, attrName))); 4408 } 4409 else 4410 { 4411 Attribute ocAttr = getObjectClassAttribute(); 4412 if (ocAttr != null) 4413 { 4414 if (!attrName.equals(ocAttr.getAttributeDescription().getNameOrOID())) 4415 { 4416 // User requested non-default object class type name. 4417 AttributeBuilder builder = new AttributeBuilder(ocAttr); 4418 builder.setAttributeDescription(AttributeDescription.create(attrName, ocType)); 4419 ocAttr = builder.toAttribute(); 4420 } 4421 4422 userAttrsCopy.put(ocType, newArrayList(ocAttr)); 4423 } 4424 } 4425 } 4426 } 4427 else 4428 { 4429 List<Attribute> attrList = getUserAttribute(attrType); 4430 if (!attrList.isEmpty()) 4431 { 4432 mergeAttributeLists(attrList, userAttrsCopy, attrDesc, 4433 omitValues, omitReal, omitVirtual); 4434 } 4435 else 4436 { 4437 attrList = getOperationalAttribute(attrType); 4438 if (!attrList.isEmpty()) 4439 { 4440 mergeAttributeLists(attrList, operationalAttrsCopy, attrDesc, 4441 omitValues, omitReal, omitVirtual); 4442 } 4443 } 4444 } 4445 } 4446 } 4447 } 4448 4449 return new Entry(dn, objectClassesCopy, userAttrsCopy, 4450 operationalAttrsCopy); 4451 } 4452 4453 private void mergeAttributeLists(List<Attribute> sourceList, 4454 Map<AttributeType, List<Attribute>> destMap, AttributeDescription attrDesc, 4455 boolean omitValues, boolean omitReal, boolean omitVirtual) 4456 { 4457 if (sourceList == null) 4458 { 4459 return; 4460 } 4461 4462 final String attrName = attrDesc.getNameOrOID(); 4463 for (Attribute attribute : sourceList) 4464 { 4465 AttributeDescription subAttrDesc = attribute.getAttributeDescription(); 4466 if (attribute.isEmpty() 4467 || (omitReal && attribute.isReal()) 4468 || (omitVirtual && attribute.isVirtual()) 4469 || !subAttrDesc.isSubTypeOf(attrDesc)) 4470 { 4471 continue; 4472 } 4473 4474 // If a non-default attribute name was provided or if the 4475 // attribute has options then we will need to rebuild the 4476 // attribute so that it contains the user-requested names and options. 4477 final AttributeType subAttrType = subAttrDesc.getAttributeType(); 4478 4479 if ((attrName != null && !attrName.equals(subAttrDesc.getNameOrOID())) 4480 || attrDesc.hasOptions()) 4481 { 4482 // We want to use the user-provided name only if this attribute has 4483 // the same type as the requested type. This might not be the case for 4484 // sub-types e.g. requesting "name" and getting back "cn" - we don't 4485 // want to rename "name" to "cn". 4486 AttributeType attrType = attrDesc.getAttributeType(); 4487 AttributeDescription newAttrDesc; 4488 if (attrName == null || !subAttrType.equals(attrType)) 4489 { 4490 newAttrDesc = AttributeDescription.create(subAttrDesc.getNameOrOID(), subAttrDesc.getAttributeType()); 4491 } 4492 else 4493 { 4494 newAttrDesc = AttributeDescription.create(attrName, subAttrDesc.getAttributeType()); 4495 } 4496 4497 AttributeBuilder builder = new AttributeBuilder(newAttrDesc); 4498 builder.setOptions(attrDesc.getOptions()); 4499 // Now add in remaining options from original attribute 4500 // (this will not overwrite options already present). 4501 builder.setOptions(subAttrDesc.getOptions()); 4502 if (!omitValues) 4503 { 4504 builder.addAll(attribute); 4505 } 4506 attribute = builder.toAttribute(); 4507 } 4508 else if (omitValues) 4509 { 4510 attribute = Attributes.empty(attribute); 4511 } 4512 4513 // Now put the attribute into the destination map. 4514 // Be careful of duplicates. 4515 List<Attribute> attrList = destMap.get(subAttrType); 4516 4517 if (attrList == null) 4518 { 4519 // Assume that they'll all go in the one list. This isn't 4520 // always the case, for example if the list contains sub-types. 4521 attrList = new ArrayList<>(sourceList.size()); 4522 attrList.add(attribute); 4523 destMap.put(subAttrType, attrList); 4524 } 4525 else 4526 { 4527 // The attribute may have already been put in the list. 4528 // 4529 // This may occur in two cases: 4530 // 4531 // 1) The attribute is identified by more than one attribute 4532 // type description in the attribute list (e.g. in a wildcard). 4533 // 4534 // 2) The attribute has both a real and virtual component. 4535 // 4536 boolean found = false; 4537 for (int i = 0; i < attrList.size(); i++) 4538 { 4539 Attribute otherAttribute = attrList.get(i); 4540 if (otherAttribute.getAttributeDescription().equals(subAttrDesc)) 4541 { 4542 // Assume that wildcards appear first in an attribute 4543 // list with more specific attribute names afterwards: 4544 // let the attribute name and options from the later 4545 // attribute take preference. 4546 attrList.set(i, Attributes.merge(attribute, otherAttribute)); 4547 found = true; 4548 } 4549 } 4550 4551 if (!found) 4552 { 4553 attrList.add(attribute); 4554 } 4555 } 4556 } 4557 } 4558}