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