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.getAttributeTypeOrNull(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("No %s attribute type is defined in the server schema.", attrTypeName); 2651 } 2652 return false; 2653 } 2654 return userAttributes.containsKey(attrType) 2655 || operationalAttributes.containsKey(attrType); 2656 } 2657 2658 /** 2659 * Whether the object class name exists in the objectClass of this entry. 2660 * 2661 * @param objectClassName 2662 * the name of the object class to look for 2663 * @return true if the object class name exists in the objectClass of this 2664 * entry, false otherwise 2665 */ 2666 private boolean containsObjectClassByName(String objectClassName) 2667 { 2668 for (String ocName : objectClasses.values()) 2669 { 2670 if (objectClassName.equalsIgnoreCase(ocName)) 2671 { 2672 return true; 2673 } 2674 } 2675 return false; 2676 } 2677 2678 /** 2679 * Retrieves the set of referral URLs that are included in this 2680 * referral entry. This should only be called if 2681 * <CODE>isReferral()</CODE> returns <CODE>true</CODE>. 2682 * 2683 * @return The set of referral URLs that are included in this entry 2684 * if it is a referral, or <CODE>null</CODE> if it is not a 2685 * referral. 2686 */ 2687 public Set<String> getReferralURLs() 2688 { 2689 AttributeType referralType = DirectoryServer.getAttributeTypeOrNull(ATTR_REFERRAL_URL); 2690 if (referralType == null) 2691 { 2692 // This should not happen -- The server doesn't have a ref attribute type defined. 2693 logger.trace("No %s attribute type is defined in the server schema.", ATTR_REFERRAL_URL); 2694 return null; 2695 } 2696 2697 List<Attribute> refAttrs = userAttributes.get(referralType); 2698 if (refAttrs == null) 2699 { 2700 refAttrs = operationalAttributes.get(referralType); 2701 if (refAttrs == null) 2702 { 2703 return null; 2704 } 2705 } 2706 2707 Set<String> referralURLs = new LinkedHashSet<>(); 2708 for (Attribute a : refAttrs) 2709 { 2710 for (ByteString v : a) 2711 { 2712 referralURLs.add(v.toString()); 2713 } 2714 } 2715 2716 return referralURLs; 2717 } 2718 2719 2720 2721 /** 2722 * Indicates whether this entry meets the criteria to consider it an 2723 * alias (e.g., it contains the "aliasObject" objectclass and a 2724 * "alias" attribute). 2725 * 2726 * @return <CODE>true</CODE> if this entry meets the criteria to 2727 * consider it an alias, or <CODE>false</CODE> if not. 2728 */ 2729 public boolean isAlias() 2730 { 2731 return hasObjectClassOrAttribute(OC_ALIAS, ATTR_ALIAS_DN); 2732 } 2733 2734 2735 2736 /** 2737 * Retrieves the DN of the entry referenced by this alias entry. 2738 * This should only be called if <CODE>isAlias()</CODE> returns 2739 * <CODE>true</CODE>. 2740 * 2741 * @return The DN of the entry referenced by this alias entry, or 2742 * <CODE>null</CODE> if it is not an alias. 2743 * 2744 * @throws DirectoryException If there is an aliasedObjectName 2745 * attribute but its value cannot be 2746 * parsed as a DN. 2747 */ 2748 public DN getAliasedDN() throws DirectoryException 2749 { 2750 AttributeType aliasType = DirectoryServer.getAttributeTypeOrNull(ATTR_REFERRAL_URL); 2751 if (aliasType == null) 2752 { 2753 // This should not happen -- The server doesn't have an aliasedObjectName attribute type defined. 2754 logger.trace("No %s attribute type is defined in the server schema.", ATTR_ALIAS_DN); 2755 return null; 2756 } 2757 2758 List<Attribute> aliasAttrs = userAttributes.get(aliasType); 2759 if (aliasAttrs == null) 2760 { 2761 aliasAttrs = operationalAttributes.get(aliasType); 2762 if (aliasAttrs == null) 2763 { 2764 return null; 2765 } 2766 } 2767 2768 if (!aliasAttrs.isEmpty()) 2769 { 2770 // There should only be a single alias attribute in an entry, 2771 // and we'll skip the check for others for performance reasons. 2772 // We would just end up taking the first one anyway. The same 2773 // is true with the set of values, since it should be a 2774 // single-valued attribute. 2775 Attribute aliasAttr = aliasAttrs.get(0); 2776 if (!aliasAttr.isEmpty()) 2777 { 2778 return DN.valueOf(aliasAttr.iterator().next().toString()); 2779 } 2780 } 2781 return null; 2782 } 2783 2784 2785 2786 /** 2787 * Indicates whether this entry meets the criteria to consider it an 2788 * LDAP subentry (i.e., it contains the "ldapSubentry" objectclass). 2789 * 2790 * @return <CODE>true</CODE> if this entry meets the criteria to 2791 * consider it an LDAP subentry, or <CODE>false</CODE> if 2792 * not. 2793 */ 2794 public boolean isLDAPSubentry() 2795 { 2796 return hasObjectClass(OC_LDAP_SUBENTRY_LC); 2797 } 2798 2799 /** 2800 * Returns whether the current entry has a specific object class. 2801 * 2802 * @param objectClassLowerCase 2803 * the lowercase name of the object class to look for 2804 * @return true if the current entry has the object class, false otherwise 2805 */ 2806 private boolean hasObjectClass(String objectClassLowerCase) 2807 { 2808 ObjectClass oc = DirectoryServer.getObjectClass(objectClassLowerCase); 2809 if (oc == null) 2810 { 2811 // This should not happen 2812 // The server doesn't have this object class defined. 2813 if (logger.isTraceEnabled()) 2814 { 2815 logger.trace( 2816 "No %s objectclass is defined in the server schema.", 2817 objectClassLowerCase); 2818 } 2819 return containsObjectClassByName(objectClassLowerCase); 2820 } 2821 2822 // Make the determination based on whether this entry has this objectclass. 2823 return objectClasses.containsKey(oc); 2824 } 2825 2826 2827 2828 /** 2829 * Indicates whether this entry meets the criteria to consider it 2830 * an RFC 3672 LDAP subentry (i.e., it contains the "subentry" 2831 * objectclass). 2832 * 2833 * @return <CODE>true</CODE> if this entry meets the criteria to 2834 * consider it an RFC 3672 LDAP subentry, or <CODE>false 2835 * </CODE> if not. 2836 */ 2837 public boolean isSubentry() 2838 { 2839 return hasObjectClass(OC_SUBENTRY); 2840 } 2841 2842 2843 2844 /** 2845 * Indicates whether the entry meets the criteria to consider it an 2846 * RFC 3671 LDAP collective attributes subentry (i.e., it contains 2847 * the "collectiveAttributeSubentry" objectclass). 2848 * 2849 * @return <CODE>true</CODE> if this entry meets the criteria to 2850 * consider it an RFC 3671 LDAP collective attributes 2851 * subentry, or <CODE>false</CODE> if not. 2852 */ 2853 public boolean isCollectiveAttributeSubentry() 2854 { 2855 return hasObjectClass(OC_COLLECTIVE_ATTR_SUBENTRY_LC); 2856 } 2857 2858 2859 2860 /** 2861 * Indicates whether the entry meets the criteria to consider it an 2862 * inherited collective attributes subentry (i.e., it contains 2863 * the "inheritedCollectiveAttributeSubentry" objectclass). 2864 * 2865 * @return <CODE>true</CODE> if this entry meets the criteria to 2866 * consider it an inherited collective attributes 2867 * subentry, or <CODE>false</CODE> if not. 2868 */ 2869 public boolean isInheritedCollectiveAttributeSubentry() 2870 { 2871 return hasObjectClass(OC_INHERITED_COLLECTIVE_ATTR_SUBENTRY_LC); 2872 } 2873 2874 2875 2876 /** 2877 * Indicates whether the entry meets the criteria to consider it an inherited 2878 * from DN collective attributes subentry (i.e., it contains the 2879 * "inheritedFromDNCollectiveAttributeSubentry" objectclass). 2880 * 2881 * @return <CODE>true</CODE> if this entry meets the criteria to consider it 2882 * an inherited from DN collective attributes subentry, or 2883 * <CODE>false</CODE> if not. 2884 */ 2885 public boolean isInheritedFromDNCollectiveAttributeSubentry() 2886 { 2887 return hasObjectClass(OC_INHERITED_FROM_DN_COLLECTIVE_ATTR_SUBENTRY_LC); 2888 } 2889 2890 2891 2892 /** 2893 * Indicates whether the entry meets the criteria to consider it 2894 * an inherited from RDN collective attributes subentry (i.e., 2895 * it contains the "inheritedFromRDNCollectiveAttributeSubentry" 2896 * objectclass). 2897 * 2898 * @return <CODE>true</CODE> if this entry meets the criteria to 2899 * consider it an inherited from RDN collective attributes 2900 * subentry, or <CODE>false</CODE> if not. 2901 */ 2902 public boolean isInheritedFromRDNCollectiveAttributeSubentry() 2903 { 2904 return hasObjectClass(OC_INHERITED_FROM_RDN_COLLECTIVE_ATTR_SUBENTRY_LC); 2905 } 2906 2907 2908 2909 /** 2910 * Indicates whether the entry meets the criteria to consider it a 2911 * LDAP password policy subentry (i.e., it contains the "pwdPolicy" 2912 * objectclass of LDAP Password Policy Internet-Draft). 2913 * 2914 * @return <CODE>true</CODE> if this entry meets the criteria to 2915 * consider it a LDAP Password Policy Internet-Draft 2916 * subentry, or <CODE>false</CODE> if not. 2917 */ 2918 public boolean isPasswordPolicySubentry() 2919 { 2920 return hasObjectClass(OC_PWD_POLICY_SUBENTRY_LC); 2921 } 2922 2923 2924 2925 /** 2926 * Indicates whether this entry falls within the range of the 2927 * provided search base DN and scope. 2928 * 2929 * @param baseDN The base DN for which to make the determination. 2930 * @param scope The search scope for which to make the 2931 * determination. 2932 * 2933 * @return <CODE>true</CODE> if this entry is within the given 2934 * base and scope, or <CODE>false</CODE> if it is not. 2935 */ 2936 public boolean matchesBaseAndScope(DN baseDN, SearchScope scope) 2937 { 2938 return dn.matchesBaseAndScope(baseDN, scope); 2939 } 2940 2941 2942 2943 /** 2944 * Performs any necessary collective attribute processing for this 2945 * entry. This should only be called at the time the entry is 2946 * decoded or created within the backend. 2947 */ 2948 private void processCollectiveAttributes() 2949 { 2950 if (isSubentry() || isLDAPSubentry()) 2951 { 2952 return; 2953 } 2954 2955 SubentryManager manager = 2956 DirectoryServer.getSubentryManager(); 2957 if(manager == null) 2958 { 2959 //Subentry manager may not have been initialized by 2960 //a component that doesn't require it. 2961 return; 2962 } 2963 // Get applicable collective subentries. 2964 List<SubEntry> collectiveAttrSubentries = 2965 manager.getCollectiveSubentries(this); 2966 2967 if (collectiveAttrSubentries == null || collectiveAttrSubentries.isEmpty()) 2968 { 2969 // Nothing to see here, move along. 2970 return; 2971 } 2972 2973 // Get collective attribute exclusions. 2974 AttributeType exclusionsType = DirectoryServer.getAttributeTypeOrNull(ATTR_COLLECTIVE_EXCLUSIONS_LC); 2975 List<Attribute> exclusionsAttrList = operationalAttributes.get(exclusionsType); 2976 Set<String> exclusionsNameSet = new HashSet<>(); 2977 if (exclusionsAttrList != null && !exclusionsAttrList.isEmpty()) 2978 { 2979 for (Attribute attr : exclusionsAttrList) 2980 { 2981 for (ByteString attrValue : attr) 2982 { 2983 String exclusionsName = attrValue.toString().toLowerCase(); 2984 if (VALUE_COLLECTIVE_EXCLUSIONS_EXCLUDE_ALL_LC.equals(exclusionsName) 2985 || OID_COLLECTIVE_EXCLUSIONS_EXCLUDE_ALL.equals(exclusionsName)) 2986 { 2987 return; 2988 } 2989 exclusionsNameSet.add(exclusionsName); 2990 } 2991 } 2992 } 2993 2994 // Process collective attributes. 2995 for (SubEntry subEntry : collectiveAttrSubentries) 2996 { 2997 if (subEntry.isCollective() || subEntry.isInheritedCollective()) 2998 { 2999 Entry inheritFromEntry = null; 3000 if (subEntry.isInheritedCollective()) 3001 { 3002 if (subEntry.isInheritedFromDNCollective() && 3003 hasAttribute(subEntry.getInheritFromDNType())) 3004 { 3005 try 3006 { 3007 DN inheritFromDN = null; 3008 for (Attribute attr : getAttribute( 3009 subEntry.getInheritFromDNType())) 3010 { 3011 for (ByteString value : attr) 3012 { 3013 inheritFromDN = DN.decode(value); 3014 // Respect subentry root scope. 3015 if (!inheritFromDN.isDescendantOf( 3016 subEntry.getDN().parent())) 3017 { 3018 inheritFromDN = null; 3019 } 3020 break; 3021 } 3022 } 3023 if (inheritFromDN == null) 3024 { 3025 continue; 3026 } 3027 3028 // TODO : ACI check; needs re-factoring to happen. 3029 inheritFromEntry = DirectoryServer.getEntry(inheritFromDN); 3030 } 3031 catch (DirectoryException de) 3032 { 3033 logger.traceException(de); 3034 } 3035 } 3036 else if (subEntry.isInheritedFromRDNCollective() && 3037 hasAttribute(subEntry.getInheritFromRDNAttrType())) 3038 { 3039 DN inheritFromDN = subEntry.getInheritFromBaseDN(); 3040 if (inheritFromDN != null) 3041 { 3042 try 3043 { 3044 for (Attribute attr : getAttribute( 3045 subEntry.getInheritFromRDNAttrType())) 3046 { 3047 inheritFromDN = subEntry.getInheritFromBaseDN(); 3048 for (ByteString value : attr) 3049 { 3050 inheritFromDN = inheritFromDN.child( 3051 RDN.create(subEntry.getInheritFromRDNType(), 3052 value)); 3053 break; 3054 } 3055 } 3056 3057 // TODO : ACI check; needs re-factoring to happen. 3058 inheritFromEntry = DirectoryServer.getEntry(inheritFromDN); 3059 } 3060 catch (DirectoryException de) 3061 { 3062 logger.traceException(de); 3063 } 3064 } 3065 else 3066 { 3067 continue; 3068 } 3069 } 3070 } 3071 List<Attribute> collectiveAttrList = subEntry.getCollectiveAttributes(); 3072 for (Attribute collectiveAttr : collectiveAttrList) 3073 { 3074 AttributeType attributeType = collectiveAttr.getAttributeType(); 3075 if (exclusionsNameSet.contains( 3076 attributeType.getNormalizedPrimaryNameOrOID())) 3077 { 3078 continue; 3079 } 3080 if (subEntry.isInheritedCollective()) 3081 { 3082 if (inheritFromEntry != null) 3083 { 3084 collectiveAttr = inheritFromEntry.getExactAttribute( 3085 collectiveAttr.getAttributeType(), 3086 collectiveAttr.getOptions()); 3087 if (collectiveAttr == null || collectiveAttr.isEmpty()) 3088 { 3089 continue; 3090 } 3091 collectiveAttr = new CollectiveVirtualAttribute(collectiveAttr); 3092 } 3093 else 3094 { 3095 continue; 3096 } 3097 } 3098 List<Attribute> attrList = userAttributes.get(attributeType); 3099 if (attrList == null || attrList.isEmpty()) 3100 { 3101 attrList = operationalAttributes.get(attributeType); 3102 if (attrList == null || attrList.isEmpty()) 3103 { 3104 // There aren't any conflicts, so we can just add the attribute to the entry. 3105 putAttributes(attributeType, newLinkedList(collectiveAttr)); 3106 } 3107 else 3108 { 3109 // There is a conflict with an existing operational attribute. 3110 resolveCollectiveConflict(subEntry.getConflictBehavior(), 3111 collectiveAttr, attrList, operationalAttributes, attributeType); 3112 } 3113 } 3114 else 3115 { 3116 // There is a conflict with an existing user attribute. 3117 resolveCollectiveConflict(subEntry.getConflictBehavior(), 3118 collectiveAttr, attrList, userAttributes, attributeType); 3119 } 3120 } 3121 } 3122 } 3123 } 3124 3125 private ByteString normalize(MatchingRule matchingRule, ByteString value) 3126 throws DirectoryException 3127 { 3128 try 3129 { 3130 return matchingRule.normalizeAttributeValue(value); 3131 } 3132 catch (DecodeException e) 3133 { 3134 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 3135 e.getMessageObject(), e); 3136 } 3137 } 3138 3139 /** 3140 * Resolves a conflict arising with a collective attribute. 3141 * 3142 * @param conflictBehavior 3143 * the behavior of the conflict 3144 * @param collectiveAttr 3145 * the attribute in conflict 3146 * @param attrList 3147 * the List of attribute where to resolve the conflict 3148 * @param attributes 3149 * the Map of attributes where to solve the conflict 3150 * @param attributeType 3151 * the attribute type used with the Map 3152 */ 3153 private void resolveCollectiveConflict( 3154 CollectiveConflictBehavior conflictBehavior, Attribute collectiveAttr, 3155 List<Attribute> attrList, Map<AttributeType, List<Attribute>> attributes, 3156 AttributeType attributeType) 3157 { 3158 if (attrList.get(0).isVirtual()) 3159 { 3160 // The existing attribute is already virtual, 3161 // so we've got a different conflict, but we'll let the first win. 3162 // FIXME -- Should we handle this differently? 3163 return; 3164 } 3165 3166 // The conflict is with a real attribute. See what the 3167 // conflict behavior is and figure out how to handle it. 3168 switch (conflictBehavior) 3169 { 3170 case REAL_OVERRIDES_VIRTUAL: 3171 // We don't need to update the entry because the real attribute will take 3172 // precedence. 3173 break; 3174 3175 case VIRTUAL_OVERRIDES_REAL: 3176 // We need to move the real attribute to the suppressed list 3177 // and replace it with the virtual attribute. 3178 suppressedAttributes.put(attributeType, attrList); 3179 attributes.put(attributeType, newLinkedList(collectiveAttr)); 3180 break; 3181 3182 case MERGE_REAL_AND_VIRTUAL: 3183 // We need to add the virtual attribute to the 3184 // list and keep the existing real attribute(s). 3185 attrList.add(collectiveAttr); 3186 break; 3187 } 3188 } 3189 3190 3191 3192 /** 3193 * Performs any necessary virtual attribute processing for this 3194 * entry. This should only be called at the time the entry is 3195 * decoded or created within the backend. 3196 */ 3197 public void processVirtualAttributes() 3198 { 3199 for (VirtualAttributeRule rule : DirectoryServer.getVirtualAttributes(this)) 3200 { 3201 AttributeType attributeType = rule.getAttributeType(); 3202 List<Attribute> attrList = userAttributes.get(attributeType); 3203 if (attrList == null || attrList.isEmpty()) 3204 { 3205 attrList = operationalAttributes.get(attributeType); 3206 if (attrList == null || attrList.isEmpty()) 3207 { 3208 // There aren't any conflicts, so we can just add the attribute to the entry. 3209 Attribute attr = new VirtualAttribute(attributeType, this, rule); 3210 putAttributes(attributeType, newLinkedList(attr)); 3211 } 3212 else 3213 { 3214 // There is a conflict with an existing operational attribute. 3215 resolveVirtualConflict(rule, attrList, operationalAttributes, attributeType); 3216 } 3217 } 3218 else 3219 { 3220 // There is a conflict with an existing user attribute. 3221 resolveVirtualConflict(rule, attrList, userAttributes, attributeType); 3222 } 3223 } 3224 3225 // Collective attributes. 3226 processCollectiveAttributes(); 3227 } 3228 3229 /** 3230 * Resolves a conflict arising with a virtual attribute. 3231 * 3232 * @param rule 3233 * the VirtualAttributeRule in conflict 3234 * @param attrList 3235 * the List of attribute where to resolve the conflict 3236 * @param attributes 3237 * the Map of attribute where to resolve the conflict 3238 * @param attributeType 3239 * the attribute type used with the Map 3240 */ 3241 private void resolveVirtualConflict(VirtualAttributeRule rule, 3242 List<Attribute> attrList, Map<AttributeType, List<Attribute>> attributes, 3243 AttributeType attributeType) 3244 { 3245 if (attrList.get(0).isVirtual()) 3246 { 3247 // The existing attribute is already virtual, so we've got 3248 // a different conflict, but we'll let the first win. 3249 // FIXME -- Should we handle this differently? 3250 return; 3251 } 3252 3253 // The conflict is with a real attribute. See what the 3254 // conflict behavior is and figure out how to handle it. 3255 switch (rule.getConflictBehavior()) 3256 { 3257 case REAL_OVERRIDES_VIRTUAL: 3258 // We don't need to update the entry because the real 3259 // attribute will take precedence. 3260 break; 3261 3262 case VIRTUAL_OVERRIDES_REAL: 3263 // We need to move the real attribute to the suppressed 3264 // list and replace it with the virtual attribute. 3265 suppressedAttributes.put(attributeType, attrList); 3266 Attribute attr = new VirtualAttribute(attributeType, this, rule); 3267 attributes.put(attributeType, newLinkedList(attr)); 3268 break; 3269 3270 case MERGE_REAL_AND_VIRTUAL: 3271 // We need to add the virtual attribute to the list and 3272 // keep the existing real attribute(s). 3273 attrList.add(new VirtualAttribute(attributeType, this, rule)); 3274 break; 3275 } 3276 } 3277 3278 3279 /** 3280 * Encodes this entry into a form that is suitable for long-term 3281 * persistent storage. The encoding will have a version number so 3282 * that if the way we store entries changes in the future we will 3283 * still be able to read entries encoded in an older format. 3284 * 3285 * @param buffer The buffer to encode into. 3286 * @param config The configuration that may be used to control how 3287 * the entry is encoded. 3288 * 3289 * @throws DirectoryException If a problem occurs while attempting 3290 * to encode the entry. 3291 */ 3292 public void encode(ByteStringBuilder buffer, 3293 EntryEncodeConfig config) 3294 throws DirectoryException 3295 { 3296 encodeV3(buffer, config); 3297 } 3298 3299 /** 3300 * Encodes this entry using the V3 encoding. 3301 * 3302 * @param buffer The buffer to encode into. 3303 * @param config The configuration that should be used to encode 3304 * the entry. 3305 * 3306 * @throws DirectoryException If a problem occurs while attempting 3307 * to encode the entry. 3308 */ 3309 private void encodeV3(ByteStringBuilder buffer, 3310 EntryEncodeConfig config) 3311 throws DirectoryException 3312 { 3313 // The version number will be one byte. 3314 buffer.appendByte(0x03); 3315 3316 // Get the encoded representation of the config. 3317 config.encode(buffer); 3318 3319 // If we should include the DN, then it will be encoded as a 3320 // one-to-five byte length followed by the UTF-8 byte 3321 // representation. 3322 if (! config.excludeDN()) 3323 { 3324 // TODO: Can we encode the DN directly into buffer? 3325 byte[] dnBytes = getBytes(dn.toString()); 3326 buffer.appendBERLength(dnBytes.length); 3327 buffer.appendBytes(dnBytes); 3328 } 3329 3330 3331 // Encode the object classes in the appropriate manner. 3332 if (config.compressObjectClassSets()) 3333 { 3334 config.getCompressedSchema().encodeObjectClasses(buffer, objectClasses); 3335 } 3336 else 3337 { 3338 // Encode number of OCs and 0 terminated names. 3339 buffer.appendBERLength(objectClasses.size()); 3340 for (String ocName : objectClasses.values()) 3341 { 3342 buffer.appendUtf8(ocName); 3343 buffer.appendByte(0x00); 3344 } 3345 } 3346 3347 3348 // Encode the user attributes in the appropriate manner. 3349 encodeAttributes(buffer, userAttributes, config); 3350 3351 3352 // The operational attributes will be encoded in the same way as 3353 // the user attributes. 3354 encodeAttributes(buffer, operationalAttributes, config); 3355 } 3356 3357 /** 3358 * Encode the given attributes of an entry. 3359 * 3360 * @param buffer The buffer to encode into. 3361 * @param attributes The attributes to encode. 3362 * @param config The configuration that may be used to control how 3363 * the entry is encoded. 3364 * 3365 * @throws DirectoryException If a problem occurs while attempting 3366 * to encode the entry. 3367 */ 3368 private void encodeAttributes(ByteStringBuilder buffer, 3369 Map<AttributeType,List<Attribute>> attributes, 3370 EntryEncodeConfig config) 3371 throws DirectoryException 3372 { 3373 int numAttributes = 0; 3374 3375 // First count how many attributes are there to encode. 3376 for (List<Attribute> attrList : attributes.values()) 3377 { 3378 Attribute a; 3379 for (int i = 0; i < attrList.size(); i++) 3380 { 3381 a = attrList.get(i); 3382 if (a.isVirtual() || a.isEmpty()) 3383 { 3384 continue; 3385 } 3386 3387 numAttributes++; 3388 } 3389 } 3390 3391 // Encoded one-to-five byte number of attributes 3392 buffer.appendBERLength(numAttributes); 3393 3394 if (config.compressAttributeDescriptions()) 3395 { 3396 for (List<Attribute> attrList : attributes.values()) 3397 { 3398 for (Attribute a : attrList) 3399 { 3400 if (a.isVirtual() || a.isEmpty()) 3401 { 3402 continue; 3403 } 3404 3405 config.getCompressedSchema().encodeAttribute(buffer, a); 3406 } 3407 } 3408 } 3409 else 3410 { 3411 // The attributes will be encoded as a sequence of: 3412 // - A UTF-8 byte representation of the attribute name. 3413 // - A zero delimiter 3414 // - A one-to-five byte number of values for the attribute 3415 // - A sequence of: 3416 // - A one-to-five byte length for the value 3417 // - A UTF-8 byte representation for the value 3418 for (List<Attribute> attrList : attributes.values()) 3419 { 3420 for (Attribute a : attrList) 3421 { 3422 byte[] nameBytes = getBytes(a.getNameWithOptions()); 3423 buffer.appendBytes(nameBytes); 3424 buffer.appendByte(0x00); 3425 3426 buffer.appendBERLength(a.size()); 3427 for(ByteString v : a) 3428 { 3429 buffer.appendBERLength(v.length()); 3430 buffer.appendBytes(v); 3431 } 3432 } 3433 } 3434 } 3435 } 3436 3437 3438 /** 3439 * Decodes the provided byte array as an entry. 3440 * 3441 * @param entryBuffer The byte array containing the data to be 3442 * decoded. 3443 * 3444 * @return The decoded entry. 3445 * 3446 * @throws DirectoryException If the provided byte array cannot be 3447 * decoded as an entry. 3448 */ 3449 public static Entry decode(ByteSequenceReader entryBuffer) 3450 throws DirectoryException 3451 { 3452 return decode(entryBuffer, 3453 DirectoryServer.getDefaultCompressedSchema()); 3454 } 3455 3456 3457 3458 /** 3459 * Decodes the provided byte array as an entry using the V3 3460 * encoding. 3461 * 3462 * @param entryBuffer The byte buffer containing the data to 3463 * be decoded. 3464 * @param compressedSchema The compressed schema manager to use 3465 * when decoding tokenized schema 3466 * elements. 3467 * 3468 * @return The decoded entry. 3469 * 3470 * @throws DirectoryException If the provided byte array cannot be 3471 * decoded as an entry. 3472 */ 3473 public static Entry decode(ByteSequenceReader entryBuffer, 3474 CompressedSchema compressedSchema) 3475 throws DirectoryException 3476 { 3477 try 3478 { 3479 // The first byte must be the entry version. If it's not one 3480 // we recognize, then that's an error. 3481 Byte version = entryBuffer.readByte(); 3482 if (version != 0x03 && version != 0x02 && version != 0x01) 3483 { 3484 LocalizableMessage message = ERR_ENTRY_DECODE_UNRECOGNIZED_VERSION.get( 3485 byteToHex(version)); 3486 throw new DirectoryException( 3487 DirectoryServer.getServerErrorResultCode(), 3488 message); 3489 } 3490 3491 EntryEncodeConfig config; 3492 if(version != 0x01) 3493 { 3494 // Next is the length of the encoded configuration. 3495 int configLength = entryBuffer.readBERLength(); 3496 3497 // Next is the encoded configuration itself. 3498 config = 3499 EntryEncodeConfig.decode(entryBuffer, configLength, 3500 compressedSchema); 3501 } 3502 else 3503 { 3504 config = EntryEncodeConfig.DEFAULT_CONFIG; 3505 } 3506 3507 // If we should have included the DN in the entry, then it's 3508 // next. 3509 DN dn; 3510 if (config.excludeDN()) 3511 { 3512 dn = DN.NULL_DN; 3513 } 3514 else 3515 { 3516 // Next is the length of the DN. It may be a single byte or 3517 // multiple bytes. 3518 int dnLength = entryBuffer.readBERLength(); 3519 3520 3521 // Next is the DN itself. 3522 ByteSequence dnBytes = entryBuffer.readByteSequence(dnLength); 3523 dn = DN.decode(dnBytes.toByteString()); 3524 } 3525 3526 3527 // Next is the set of encoded object classes. The encoding will 3528 // depend on the configuration. 3529 Map<ObjectClass,String> objectClasses = 3530 decodeObjectClasses(version, entryBuffer, config); 3531 3532 3533 // Now, we should iterate through the user and operational attributes and 3534 // decode each one. 3535 Map<AttributeType, List<Attribute>> userAttributes = 3536 decodeAttributes(version, entryBuffer, config); 3537 Map<AttributeType, List<Attribute>> operationalAttributes = 3538 decodeAttributes(version, entryBuffer, config); 3539 3540 3541 // We've got everything that we need, so create and return the entry. 3542 return new Entry(dn, objectClasses, userAttributes, 3543 operationalAttributes); 3544 } 3545 catch (DirectoryException de) 3546 { 3547 throw de; 3548 } 3549 catch (Exception e) 3550 { 3551 logger.traceException(e); 3552 3553 LocalizableMessage message = 3554 ERR_ENTRY_DECODE_EXCEPTION.get(getExceptionMessage(e)); 3555 throw new DirectoryException( 3556 DirectoryServer.getServerErrorResultCode(), 3557 message, e); 3558 } 3559 } 3560 3561 3562 /** 3563 * Decode the object classes of an encoded entry. 3564 * 3565 * @param ver The version of the entry encoding. 3566 * @param entryBuffer The byte sequence containing the encoded 3567 * entry. 3568 * @param config The configuration that may be used to control how 3569 * the entry is encoded. 3570 * 3571 * @return A map of the decoded object classes. 3572 * @throws DirectoryException If a problem occurs while attempting 3573 * to encode the entry. 3574 */ 3575 private static Map<ObjectClass,String> decodeObjectClasses( 3576 byte ver, ByteSequenceReader entryBuffer, 3577 EntryEncodeConfig config) throws DirectoryException 3578 { 3579 // Next is the set of encoded object classes. The encoding will 3580 // depend on the configuration. 3581 if (config.compressObjectClassSets()) 3582 { 3583 return config.getCompressedSchema().decodeObjectClasses(entryBuffer); 3584 } 3585 3586 Map<ObjectClass, String> objectClasses; 3587 { 3588 if(ver < 0x03) 3589 { 3590 // Next is the length of the object classes. It may be a 3591 // single byte or multiple bytes. 3592 int ocLength = entryBuffer.readBERLength(); 3593 3594 // The set of object classes will be encoded as a single 3595 // string with the object class names separated by zeros. 3596 objectClasses = new LinkedHashMap<>(); 3597 int startPos = entryBuffer.position(); 3598 for (int i=0; i < ocLength; i++) 3599 { 3600 if (entryBuffer.readByte() == 0x00) 3601 { 3602 int endPos = entryBuffer.position() - 1; 3603 addObjectClass(objectClasses, entryBuffer, startPos, endPos); 3604 3605 entryBuffer.skip(1); 3606 startPos = entryBuffer.position(); 3607 } 3608 } 3609 int endPos = entryBuffer.position(); 3610 addObjectClass(objectClasses, entryBuffer, startPos, endPos); 3611 } 3612 else 3613 { 3614 // Next is the number of zero terminated object classes. 3615 int numOC = entryBuffer.readBERLength(); 3616 objectClasses = new LinkedHashMap<>(numOC); 3617 for(int i = 0; i < numOC; i++) 3618 { 3619 int startPos = entryBuffer.position(); 3620 while(entryBuffer.readByte() != 0x00) 3621 {} 3622 int endPos = entryBuffer.position() - 1; 3623 addObjectClass(objectClasses, entryBuffer, startPos, endPos); 3624 entryBuffer.skip(1); 3625 } 3626 } 3627 } 3628 3629 return objectClasses; 3630 } 3631 3632 /** 3633 * Adds the objectClass contained in the buffer to the map of object class. 3634 * 3635 * @param objectClasses 3636 * the Map where to add the objectClass 3637 * @param entryBuffer 3638 * the buffer containing the objectClass name 3639 * @param startPos 3640 * the starting position in the buffer 3641 * @param endPos 3642 * the ending position in the buffer 3643 */ 3644 private static void addObjectClass(Map<ObjectClass, String> objectClasses, 3645 ByteSequenceReader entryBuffer, int startPos, int endPos) 3646 { 3647 entryBuffer.position(startPos); 3648 final String ocName = entryBuffer.readStringUtf8(endPos - startPos); 3649 final String lowerName = toLowerCase(ocName); 3650 final ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true); 3651 objectClasses.put(oc, ocName); 3652 } 3653 3654 /** 3655 * Decode the attributes of an encoded entry. 3656 * 3657 * @param ver The version of the entry encoding. 3658 * @param entryBuffer The byte sequence containing the encoded 3659 * entry. 3660 * @param config The configuration that may be used to control how 3661 * the entry is encoded. 3662 * 3663 * @return A map of the decoded object classes. 3664 * @throws DirectoryException If a problem occurs while attempting 3665 * to encode the entry. 3666 */ 3667 private static Map<AttributeType, List<Attribute>> 3668 decodeAttributes(Byte ver, ByteSequenceReader entryBuffer, 3669 EntryEncodeConfig config) throws DirectoryException 3670 { 3671 // Next is the total number of attributes. It may be a 3672 // single byte or multiple bytes. 3673 int attrs = entryBuffer.readBERLength(); 3674 3675 3676 // Now, we should iterate through the attributes and decode each one. 3677 Map<AttributeType, List<Attribute>> attributes = new LinkedHashMap<>(attrs); 3678 if (config.compressAttributeDescriptions()) 3679 { 3680 for (int i=0; i < attrs; i++) 3681 { 3682 if(ver < 0x03) 3683 { 3684 // Version 2 includes a total attribute length 3685 entryBuffer.readBERLength(); 3686 } 3687 // Decode the attribute. 3688 Attribute a = config.getCompressedSchema().decodeAttribute(entryBuffer); 3689 List<Attribute> attrList = attributes.get(a.getAttributeType()); 3690 if (attrList == null) 3691 { 3692 attrList = new ArrayList<>(1); 3693 attributes.put(a.getAttributeType(), attrList); 3694 } 3695 attrList.add(a); 3696 } 3697 } 3698 else 3699 { 3700 AttributeBuilder builder = new AttributeBuilder(); 3701 int startPos; 3702 int endPos; 3703 for (int i=0; i < attrs; i++) 3704 { 3705 3706 // First, we have the zero-terminated attribute name. 3707 startPos = entryBuffer.position(); 3708 while (entryBuffer.readByte() != 0x00) 3709 {} 3710 endPos = entryBuffer.position()-1; 3711 entryBuffer.position(startPos); 3712 String name = entryBuffer.readStringUtf8(endPos - startPos); 3713 entryBuffer.skip(1); 3714 3715 AttributeType attributeType; 3716 int semicolonPos = name.indexOf(';'); 3717 if (semicolonPos > 0) 3718 { 3719 builder.setAttributeType(name.substring(0, semicolonPos)); 3720 attributeType = builder.getAttributeType(); 3721 3722 int nextPos = name.indexOf(';', semicolonPos+1); 3723 while (nextPos > 0) 3724 { 3725 String option = name.substring(semicolonPos+1, nextPos); 3726 if (option.length() > 0) 3727 { 3728 builder.setOption(option); 3729 } 3730 3731 semicolonPos = nextPos; 3732 nextPos = name.indexOf(';', semicolonPos+1); 3733 } 3734 3735 String option = name.substring(semicolonPos+1); 3736 if (option.length() > 0) 3737 { 3738 builder.setOption(option); 3739 } 3740 } 3741 else 3742 { 3743 builder.setAttributeType(name); 3744 attributeType = builder.getAttributeType(); 3745 } 3746 3747 3748 // Next, we have the number of values. 3749 int numValues = entryBuffer.readBERLength(); 3750 3751 // Next, we have the sequence of length-value pairs. 3752 for (int j=0; j < numValues; j++) 3753 { 3754 int valueLength = entryBuffer.readBERLength(); 3755 3756 ByteString valueBytes = 3757 entryBuffer.readByteSequence(valueLength).toByteString(); 3758 builder.add(valueBytes); 3759 } 3760 3761 3762 // Create the attribute and add it to the set of attributes. 3763 Attribute a = builder.toAttribute(); 3764 List<Attribute> attrList = attributes.get(attributeType); 3765 if (attrList == null) 3766 { 3767 attrList = new ArrayList<>(1); 3768 attributes.put(attributeType, attrList); 3769 } 3770 attrList.add(a); 3771 } 3772 } 3773 3774 return attributes; 3775 } 3776 3777 3778 3779 /** 3780 * Retrieves a list of the lines for this entry in LDIF form. Long 3781 * lines will not be wrapped automatically. 3782 * 3783 * @return A list of the lines for this entry in LDIF form. 3784 */ 3785 public List<StringBuilder> toLDIF() 3786 { 3787 List<StringBuilder> ldifLines = new LinkedList<>(); 3788 3789 // First, append the DN. 3790 StringBuilder dnLine = new StringBuilder("dn"); 3791 appendLDIFSeparatorAndValue(dnLine, ByteString.valueOfUtf8(dn.toString())); 3792 ldifLines.add(dnLine); 3793 3794 // Next, add the set of objectclasses. 3795 for (String s : objectClasses.values()) 3796 { 3797 StringBuilder ocLine = new StringBuilder("objectClass: ").append(s); 3798 ldifLines.add(ocLine); 3799 } 3800 3801 // Finally, add the set of user and operational attributes. 3802 addLinesForAttributes(ldifLines, userAttributes); 3803 addLinesForAttributes(ldifLines, operationalAttributes); 3804 3805 return ldifLines; 3806 } 3807 3808 3809 /** 3810 * Add LDIF lines for each passed in attributes. 3811 * 3812 * @param ldifLines 3813 * the List where to add the LDIF lines 3814 * @param attributes 3815 * the List of attributes to convert into LDIf lines 3816 */ 3817 private void addLinesForAttributes(List<StringBuilder> ldifLines, 3818 Map<AttributeType, List<Attribute>> attributes) 3819 { 3820 for (List<Attribute> attrList : attributes.values()) 3821 { 3822 for (Attribute a : attrList) 3823 { 3824 StringBuilder attrName = new StringBuilder(a.getName()); 3825 for (String o : a.getOptions()) 3826 { 3827 attrName.append(";"); 3828 attrName.append(o); 3829 } 3830 3831 for (ByteString v : a) 3832 { 3833 StringBuilder attrLine = new StringBuilder(attrName); 3834 appendLDIFSeparatorAndValue(attrLine, v); 3835 ldifLines.add(attrLine); 3836 } 3837 } 3838 } 3839 } 3840 3841 3842 /** 3843 * Writes this entry in LDIF form according to the provided 3844 * configuration. 3845 * 3846 * @param exportConfig The configuration that specifies how the 3847 * entry should be written. 3848 * 3849 * @return <CODE>true</CODE> if the entry is actually written, or 3850 * <CODE>false</CODE> if it is not for some reason. 3851 * 3852 * @throws IOException If a problem occurs while writing the 3853 * information. 3854 * 3855 * @throws LDIFException If a problem occurs while trying to 3856 * determine whether to write the entry. 3857 */ 3858 public boolean toLDIF(LDIFExportConfig exportConfig) 3859 throws IOException, LDIFException 3860 { 3861 // See if this entry should be included in the export at all. 3862 try 3863 { 3864 if (! exportConfig.includeEntry(this)) 3865 { 3866 if (logger.isTraceEnabled()) 3867 { 3868 logger.trace("Skipping entry %s because of the export configuration.", dn); 3869 } 3870 return false; 3871 } 3872 } 3873 catch (Exception e) 3874 { 3875 logger.traceException(e); 3876 throw new LDIFException(ERR_LDIF_COULD_NOT_EVALUATE_FILTERS_FOR_EXPORT.get(dn, e), e); 3877 } 3878 3879 3880 // Invoke LDIF export plugins on the entry if appropriate. 3881 if (exportConfig.invokeExportPlugins()) 3882 { 3883 PluginConfigManager pluginConfigManager = 3884 DirectoryServer.getPluginConfigManager(); 3885 PluginResult.ImportLDIF pluginResult = 3886 pluginConfigManager.invokeLDIFExportPlugins(exportConfig, 3887 this); 3888 if (! pluginResult.continueProcessing()) 3889 { 3890 return false; 3891 } 3892 } 3893 3894 3895 // Get the information necessary to write the LDIF. 3896 BufferedWriter writer = exportConfig.getWriter(); 3897 int wrapColumn = exportConfig.getWrapColumn(); 3898 boolean wrapLines = wrapColumn > 1; 3899 3900 3901 // First, write the DN. It will always be included. 3902 StringBuilder dnLine = new StringBuilder("dn"); 3903 appendLDIFSeparatorAndValue(dnLine, ByteString.valueOfUtf8(dn.toString())); 3904 LDIFWriter.writeLDIFLine(dnLine, writer, wrapLines, wrapColumn); 3905 3906 3907 // Next, the set of objectclasses. 3908 final boolean typesOnly = exportConfig.typesOnly(); 3909 if (exportConfig.includeObjectClasses()) 3910 { 3911 if (typesOnly) 3912 { 3913 StringBuilder ocLine = new StringBuilder("objectClass:"); 3914 LDIFWriter.writeLDIFLine(ocLine, writer, wrapLines, wrapColumn); 3915 } 3916 else 3917 { 3918 for (String s : objectClasses.values()) 3919 { 3920 StringBuilder ocLine = new StringBuilder("objectClass: ").append(s); 3921 LDIFWriter.writeLDIFLine(ocLine, writer, wrapLines, wrapColumn); 3922 } 3923 } 3924 } 3925 else 3926 { 3927 if (logger.isTraceEnabled()) 3928 { 3929 logger.trace("Skipping objectclasses for entry %s because of the export configuration.", dn); 3930 } 3931 } 3932 3933 3934 // Now the set of user attributes. 3935 writeLDIFLines(userAttributes, typesOnly, "user", exportConfig, writer, 3936 wrapColumn, wrapLines); 3937 3938 3939 // Next, the set of operational attributes. 3940 if (exportConfig.includeOperationalAttributes()) 3941 { 3942 writeLDIFLines(operationalAttributes, typesOnly, "operational", 3943 exportConfig, writer, wrapColumn, wrapLines); 3944 } 3945 else 3946 { 3947 if (logger.isTraceEnabled()) 3948 { 3949 logger.trace( 3950 "Skipping all operational attributes for entry %s " + 3951 "because of the export configuration.", dn); 3952 } 3953 } 3954 3955 3956 // If we are not supposed to include virtual attributes, then 3957 // write any attributes that may normally be suppressed by a 3958 // virtual attribute. 3959 if (! exportConfig.includeVirtualAttributes()) 3960 { 3961 for (AttributeType t : suppressedAttributes.keySet()) 3962 { 3963 if (exportConfig.includeAttribute(t)) 3964 { 3965 for (Attribute a : suppressedAttributes.get(t)) 3966 { 3967 writeLDIFLine(a, typesOnly, writer, wrapLines, wrapColumn); 3968 } 3969 } 3970 } 3971 } 3972 3973 3974 // Make sure there is a blank line after the entry. 3975 writer.newLine(); 3976 3977 3978 return true; 3979 } 3980 3981 3982 /** 3983 * Writes the provided List of attributes to LDIF using the provided 3984 * information. 3985 * 3986 * @param attributes 3987 * the List of attributes to write as LDIF 3988 * @param typesOnly 3989 * if true, only writes the type information, else writes the type 3990 * information and values for the attribute. 3991 * @param attributeType 3992 * the type of attribute being written to LDIF 3993 * @param exportConfig 3994 * configures the export to LDIF 3995 * @param writer 3996 * The writer to which the data should be written. It must not be 3997 * <CODE>null</CODE>. 3998 * @param wrapLines 3999 * Indicates whether to wrap long lines. 4000 * @param wrapColumn 4001 * The column at which long lines should be wrapped. 4002 * @throws IOException 4003 * If a problem occurs while writing the information. 4004 */ 4005 private void writeLDIFLines(Map<AttributeType, List<Attribute>> attributes, 4006 final boolean typesOnly, String attributeType, 4007 LDIFExportConfig exportConfig, BufferedWriter writer, int wrapColumn, 4008 boolean wrapLines) throws IOException 4009 { 4010 for (AttributeType attrType : attributes.keySet()) 4011 { 4012 if (exportConfig.includeAttribute(attrType)) 4013 { 4014 List<Attribute> attrList = attributes.get(attrType); 4015 for (Attribute a : attrList) 4016 { 4017 if (a.isVirtual() && !exportConfig.includeVirtualAttributes()) 4018 { 4019 continue; 4020 } 4021 4022 writeLDIFLine(a, typesOnly, writer, wrapLines, wrapColumn); 4023 } 4024 } 4025 else 4026 { 4027 if (logger.isTraceEnabled()) 4028 { 4029 logger.trace("Skipping %s attribute %s for entry %s " 4030 + "because of the export configuration.", attributeType, attrType.getNameOrOID(), dn); 4031 } 4032 } 4033 } 4034 } 4035 4036 4037 /** 4038 * Writes the provided attribute to LDIF using the provided information. 4039 * 4040 * @param attribute 4041 * the attribute to write to LDIF 4042 * @param typesOnly 4043 * if true, only writes the type information, else writes the type 4044 * information and values for the attribute. 4045 * @param writer 4046 * The writer to which the data should be written. It must not be 4047 * <CODE>null</CODE>. 4048 * @param wrapLines 4049 * Indicates whether to wrap long lines. 4050 * @param wrapColumn 4051 * The column at which long lines should be wrapped. 4052 * @throws IOException 4053 * If a problem occurs while writing the information. 4054 */ 4055 private void writeLDIFLine(Attribute attribute, final boolean typesOnly, 4056 BufferedWriter writer, boolean wrapLines, int wrapColumn) 4057 throws IOException 4058 { 4059 StringBuilder attrName = new StringBuilder(attribute.getName()); 4060 for (String o : attribute.getOptions()) 4061 { 4062 attrName.append(";"); 4063 attrName.append(o); 4064 } 4065 4066 if (typesOnly) 4067 { 4068 attrName.append(":"); 4069 4070 LDIFWriter.writeLDIFLine(attrName, writer, wrapLines, wrapColumn); 4071 } 4072 else 4073 { 4074 for (ByteString v : attribute) 4075 { 4076 StringBuilder attrLine = new StringBuilder(attrName); 4077 appendLDIFSeparatorAndValue(attrLine, v); 4078 LDIFWriter.writeLDIFLine(attrLine, writer, wrapLines, wrapColumn); 4079 } 4080 } 4081 } 4082 4083 4084 4085 /** 4086 * Retrieves the name of the protocol associated with this protocol 4087 * element. 4088 * 4089 * @return The name of the protocol associated with this protocol 4090 * element. 4091 */ 4092 @Override 4093 public String getProtocolElementName() 4094 { 4095 return "Entry"; 4096 } 4097 4098 4099 4100 /** 4101 * Retrieves a hash code for this entry. 4102 * 4103 * @return The hash code for this entry. 4104 */ 4105 @Override 4106 public int hashCode() 4107 { 4108 int hashCode = dn.hashCode(); 4109 for (ObjectClass oc : objectClasses.keySet()) 4110 { 4111 hashCode += oc.hashCode(); 4112 } 4113 4114 hashCode += hashCode(userAttributes.values()); 4115 hashCode += hashCode(operationalAttributes.values()); 4116 return hashCode; 4117 } 4118 4119 /** 4120 * Computes the hashCode for the list of attributes list. 4121 * 4122 * @param attributesLists 4123 * the attributes for which to commpute the hashCode 4124 * @return the hashCode for the list of attributes list. 4125 */ 4126 private int hashCode(Collection<List<Attribute>> attributesLists) 4127 { 4128 int result = 0; 4129 for (List<Attribute> attributes : attributesLists) 4130 { 4131 for (Attribute a : attributes) 4132 { 4133 result += a.hashCode(); 4134 } 4135 } 4136 return result; 4137 } 4138 4139 4140 4141 /** 4142 * Indicates whether the provided object is equal to this entry. In 4143 * order for the object to be considered equal, it must be an entry 4144 * with the same DN, set of object classes, and set of user and 4145 * operational attributes. 4146 * 4147 * @param o The object for which to make the determination. 4148 * 4149 * @return {@code true} if the provided object may be considered 4150 * equal to this entry, or {@code false} if not. 4151 */ 4152 @Override 4153 public boolean equals(Object o) 4154 { 4155 if (this == o) 4156 { 4157 return true; 4158 } 4159 if (o == null) 4160 { 4161 return false; 4162 } 4163 if (! (o instanceof Entry)) 4164 { 4165 return false; 4166 } 4167 4168 Entry e = (Entry) o; 4169 return dn.equals(e.dn) 4170 && objectClasses.keySet().equals(e.objectClasses.keySet()) 4171 && equals(userAttributes, e.userAttributes) 4172 && equals(operationalAttributes, e.operationalAttributes); 4173 } 4174 4175 /** 4176 * Returns whether the 2 Maps are equal. 4177 * 4178 * @param attributes1 4179 * the first Map of attributes 4180 * @param attributes2 4181 * the second Map of attributes 4182 * @return true if the 2 Maps are equal, false otherwise 4183 */ 4184 private boolean equals(Map<AttributeType, List<Attribute>> attributes1, 4185 Map<AttributeType, List<Attribute>> attributes2) 4186 { 4187 for (AttributeType at : attributes1.keySet()) 4188 { 4189 List<Attribute> list1 = attributes1.get(at); 4190 List<Attribute> list2 = attributes2.get(at); 4191 if (list2 == null || list1.size() != list2.size()) 4192 { 4193 return false; 4194 } 4195 for (Attribute a : list1) 4196 { 4197 if (!list2.contains(a)) 4198 { 4199 return false; 4200 } 4201 } 4202 } 4203 return true; 4204 } 4205 4206 4207 4208 /** 4209 * Retrieves a string representation of this protocol element. 4210 * 4211 * @return A string representation of this protocol element. 4212 */ 4213 @Override 4214 public String toString() 4215 { 4216 return toLDIFString(); 4217 } 4218 4219 4220 4221 /** 4222 * Appends a string representation of this protocol element to the 4223 * provided buffer. 4224 * 4225 * @param buffer The buffer into which the string representation 4226 * should be written. 4227 */ 4228 @Override 4229 public void toString(StringBuilder buffer) 4230 { 4231 buffer.append(this); 4232 } 4233 4234 4235 4236 /** 4237 * Appends a string representation of this protocol element to the 4238 * provided buffer. 4239 * 4240 * @param buffer The buffer into which the string representation 4241 * should be written. 4242 * @param indent The number of spaces that should be used to 4243 * indent the resulting string representation. 4244 */ 4245 @Override 4246 public void toString(StringBuilder buffer, int indent) 4247 { 4248 StringBuilder indentBuf = new StringBuilder(indent); 4249 for (int i=0 ; i < indent; i++) 4250 { 4251 indentBuf.append(' '); 4252 } 4253 4254 for (StringBuilder b : toLDIF()) 4255 { 4256 buffer.append(indentBuf); 4257 buffer.append(b); 4258 buffer.append(EOL); 4259 } 4260 } 4261 4262 4263 4264 /** 4265 * Retrieves a string representation of this entry in LDIF form. 4266 * 4267 * @return A string representation of this entry in LDIF form. 4268 */ 4269 public String toLDIFString() 4270 { 4271 StringBuilder buffer = new StringBuilder(); 4272 4273 for (StringBuilder ldifLine : toLDIF()) 4274 { 4275 buffer.append(ldifLine); 4276 buffer.append(EOL); 4277 } 4278 4279 return buffer.toString(); 4280 } 4281 4282 4283 4284 /** 4285 * Appends a single-line representation of this entry to the 4286 * provided buffer. 4287 * 4288 * @param buffer The buffer to which the information should be 4289 * written. 4290 */ 4291 public void toSingleLineString(StringBuilder buffer) 4292 { 4293 buffer.append("Entry(dn=\""); 4294 dn.toString(buffer); 4295 buffer.append("\",objectClasses={"); 4296 4297 Iterator<String> iterator = objectClasses.values().iterator(); 4298 if (iterator.hasNext()) 4299 { 4300 buffer.append(iterator.next()); 4301 4302 while (iterator.hasNext()) 4303 { 4304 buffer.append(","); 4305 buffer.append(iterator.next()); 4306 } 4307 } 4308 4309 buffer.append("},userAttrs={"); 4310 appendAttributes(buffer, userAttributes.values()); 4311 buffer.append("},operationalAttrs={"); 4312 appendAttributes(buffer, operationalAttributes.values()); 4313 buffer.append("})"); 4314 } 4315 4316 /** 4317 * Appends the attributes to the StringBuilder. 4318 * 4319 * @param buffer 4320 * the StringBuilder where to append 4321 * @param attributesLists 4322 * the attributesLists to append 4323 */ 4324 private void appendAttributes(StringBuilder buffer, 4325 Collection<List<Attribute>> attributesLists) 4326 { 4327 boolean firstAttr = true; 4328 for (List<Attribute> attributes : attributesLists) 4329 { 4330 for (Attribute a : attributes) 4331 { 4332 if (firstAttr) 4333 { 4334 firstAttr = false; 4335 } 4336 else 4337 { 4338 buffer.append(","); 4339 } 4340 4341 buffer.append(a.getName()); 4342 4343 if (a.hasOptions()) 4344 { 4345 for (String optionString : a.getOptions()) 4346 { 4347 buffer.append(";"); 4348 buffer.append(optionString); 4349 } 4350 } 4351 4352 buffer.append("={"); 4353 Iterator<ByteString> valueIterator = a.iterator(); 4354 if (valueIterator.hasNext()) 4355 { 4356 buffer.append(valueIterator.next()); 4357 4358 while (valueIterator.hasNext()) 4359 { 4360 buffer.append(","); 4361 buffer.append(valueIterator.next()); 4362 } 4363 } 4364 4365 buffer.append("}"); 4366 } 4367 } 4368 } 4369 4370 4371 4372 /** 4373 * Retrieves the requested attribute element for the specified 4374 * attribute type and options or <code>null</code> if this entry 4375 * does not contain an attribute with the specified attribute type 4376 * and options. 4377 * 4378 * @param attributeType 4379 * The attribute type to retrieve. 4380 * @param options 4381 * The set of attribute options. 4382 * @return The requested attribute element for the specified 4383 * attribute type and options, or <code>null</code> if the 4384 * specified attribute type is not present in this entry 4385 * with the provided set of options. 4386 */ 4387 public Attribute getExactAttribute(AttributeType attributeType, 4388 Set<String> options) 4389 { 4390 List<Attribute> attributes = getAttributes(attributeType); 4391 if (attributes != null) 4392 { 4393 for (Attribute attribute : attributes) 4394 { 4395 if (attribute.optionsEqual(options)) 4396 { 4397 return attribute; 4398 } 4399 } 4400 } 4401 return null; 4402 } 4403 4404 4405 4406 /** 4407 * Adds the provided attribute to this entry. If an attribute with 4408 * the provided type and options already exists, then it will be 4409 * either merged or replaced depending on the value of 4410 * <code>replace</code>. 4411 * 4412 * @param attribute 4413 * The attribute to add/replace in this entry. 4414 * @param duplicateValues 4415 * A list to which any duplicate values will be added. 4416 * @param replace 4417 * <code>true</code> if the attribute should replace any 4418 * existing attribute. 4419 */ 4420 private void setAttribute(Attribute attribute, 4421 List<ByteString> duplicateValues, boolean replace) 4422 { 4423 attachment = null; 4424 4425 AttributeType attributeType = attribute.getAttributeType(); 4426 4427 if (attribute.getAttributeType().isObjectClass()) 4428 { 4429 // We will not do any validation of the object classes - this is 4430 // left to the caller. 4431 if (replace) 4432 { 4433 objectClasses.clear(); 4434 } 4435 4436 MatchingRule rule = 4437 attribute.getAttributeType().getEqualityMatchingRule(); 4438 for (ByteString v : attribute) 4439 { 4440 String name = v.toString(); 4441 String lowerName = toLowerName(rule, v); 4442 4443 // Create a default object class if necessary. 4444 ObjectClass oc = 4445 DirectoryServer.getObjectClass(lowerName, true); 4446 4447 if (replace) 4448 { 4449 objectClasses.put(oc, name); 4450 } 4451 else 4452 { 4453 if (objectClasses.containsKey(oc)) 4454 { 4455 duplicateValues.add(v); 4456 } 4457 else 4458 { 4459 objectClasses.put(oc, name); 4460 } 4461 } 4462 } 4463 4464 return; 4465 } 4466 4467 List<Attribute> attributes = getAttributes(attributeType); 4468 if (attributes == null) 4469 { 4470 // Do nothing if we are deleting a non-existing attribute. 4471 if (replace && attribute.isEmpty()) 4472 { 4473 return; 4474 } 4475 4476 // We are adding the first attribute with this attribute type. 4477 putAttributes(attributeType, newArrayList(attribute)); 4478 return; 4479 } 4480 4481 // There are already attributes with the same attribute type. 4482 Set<String> options = attribute.getOptions(); 4483 for (int i = 0; i < attributes.size(); i++) 4484 { 4485 Attribute a = attributes.get(i); 4486 if (a.optionsEqual(options)) 4487 { 4488 if (replace) 4489 { 4490 if (!attribute.isEmpty()) 4491 { 4492 attributes.set(i, attribute); 4493 } 4494 else 4495 { 4496 attributes.remove(i); 4497 4498 if (attributes.isEmpty()) 4499 { 4500 removeAttributes(attributeType); 4501 } 4502 } 4503 } 4504 else 4505 { 4506 AttributeBuilder builder = new AttributeBuilder(a); 4507 for (ByteString v : attribute) 4508 { 4509 if (!builder.add(v)) 4510 { 4511 duplicateValues.add(v); 4512 } 4513 } 4514 attributes.set(i, builder.toAttribute()); 4515 } 4516 return; 4517 } 4518 } 4519 4520 // There were no attributes with the same options. 4521 if (replace && attribute.isEmpty()) 4522 { 4523 // Do nothing. 4524 return; 4525 } 4526 4527 attributes.add(attribute); 4528 } 4529 4530 4531 4532 /** 4533 * Returns an entry containing only those attributes of this entry 4534 * which match the provided criteria. 4535 * 4536 * @param attrNameList 4537 * The list of attributes to include, may include wild 4538 * cards. 4539 * @param omitValues 4540 * Indicates whether to omit attribute values when 4541 * processing. 4542 * @param omitReal 4543 * Indicates whether to exclude real attributes. 4544 * @param omitVirtual 4545 * Indicates whether to exclude virtual attributes. 4546 * @return An entry containing only those attributes of this entry 4547 * which match the provided criteria. 4548 */ 4549 public Entry filterEntry(Set<String> attrNameList, 4550 boolean omitValues, boolean omitReal, boolean omitVirtual) 4551 { 4552 final AttributeType ocType = DirectoryServer.getObjectClassAttributeType(); 4553 4554 Map<ObjectClass, String> objectClassesCopy; 4555 Map<AttributeType, List<Attribute>> userAttrsCopy; 4556 Map<AttributeType, List<Attribute>> operationalAttrsCopy; 4557 4558 if (attrNameList == null || attrNameList.isEmpty()) 4559 { 4560 // Common case: return filtered user attributes. 4561 userAttrsCopy = new LinkedHashMap<>(userAttributes.size()); 4562 operationalAttrsCopy = new LinkedHashMap<>(0); 4563 4564 if (omitReal) 4565 { 4566 objectClassesCopy = new LinkedHashMap<>(0); 4567 } 4568 else if (omitValues) 4569 { 4570 objectClassesCopy = new LinkedHashMap<>(0); 4571 4572 // Add empty object class attribute. 4573 userAttrsCopy.put(ocType, newArrayList(Attributes.empty(ocType))); 4574 } 4575 else 4576 { 4577 objectClassesCopy = new LinkedHashMap<>(objectClasses); 4578 4579 // First, add the objectclass attribute. 4580 Attribute ocAttr = getObjectClassAttribute(); 4581 if (ocAttr != null) 4582 { 4583 userAttrsCopy.put(ocType, newArrayList(ocAttr)); 4584 } 4585 } 4586 4587 // Copy all user attributes. 4588 deepCopy(userAttributes, userAttrsCopy, omitValues, true, 4589 omitReal, omitVirtual, true); 4590 } 4591 else 4592 { 4593 // Incrementally build table of attributes. 4594 if (omitReal || omitValues) 4595 { 4596 objectClassesCopy = new LinkedHashMap<>(0); 4597 } 4598 else 4599 { 4600 objectClassesCopy = new LinkedHashMap<>(objectClasses.size()); 4601 } 4602 4603 userAttrsCopy = new LinkedHashMap<>(userAttributes.size()); 4604 operationalAttrsCopy = new LinkedHashMap<>(operationalAttributes.size()); 4605 4606 for (String attrName : attrNameList) 4607 { 4608 if ("*".equals(attrName)) 4609 { 4610 // This is a special placeholder indicating that all user 4611 // attributes should be returned. 4612 if (!omitReal) 4613 { 4614 if (omitValues) 4615 { 4616 // Add empty object class attribute. 4617 userAttrsCopy.put(ocType, newArrayList(Attributes.empty(ocType))); 4618 } 4619 else 4620 { 4621 // Add the objectclass attribute. 4622 objectClassesCopy.putAll(objectClasses); 4623 Attribute ocAttr = getObjectClassAttribute(); 4624 if (ocAttr != null) 4625 { 4626 userAttrsCopy.put(ocType, newArrayList(ocAttr)); 4627 } 4628 } 4629 } 4630 4631 // Copy all user attributes. 4632 deepCopy(userAttributes, userAttrsCopy, omitValues, true, 4633 omitReal, omitVirtual, true); 4634 continue; 4635 } 4636 else if ("+".equals(attrName)) 4637 { 4638 // This is a special placeholder indicating that all 4639 // operational attributes should be returned. 4640 deepCopy(operationalAttributes, operationalAttrsCopy, 4641 omitValues, true, omitReal, omitVirtual, true); 4642 continue; 4643 } 4644 4645 String lowerName; 4646 Set<String> options; 4647 int semicolonPos = attrName.indexOf(';'); 4648 if (semicolonPos > 0) 4649 { 4650 String tmpName = attrName.substring(0, semicolonPos); 4651 lowerName = toLowerCase(tmpName); 4652 int nextPos = attrName.indexOf(';', semicolonPos+1); 4653 options = new HashSet<>(); 4654 while (nextPos > 0) 4655 { 4656 options.add(attrName.substring(semicolonPos+1, nextPos)); 4657 4658 semicolonPos = nextPos; 4659 nextPos = attrName.indexOf(';', semicolonPos+1); 4660 } 4661 options.add(attrName.substring(semicolonPos+1)); 4662 attrName = tmpName; 4663 } 4664 else 4665 { 4666 lowerName = toLowerCase(attrName); 4667 options = null; 4668 } 4669 4670 AttributeType attrType = DirectoryServer.getAttributeTypeOrNull(lowerName); 4671 if (attrType == null) 4672 { 4673 // Unrecognized attribute type - do best effort search. 4674 for (Map.Entry<AttributeType, List<Attribute>> e : 4675 userAttributes.entrySet()) 4676 { 4677 AttributeType t = e.getKey(); 4678 if (t.hasNameOrOID(lowerName)) 4679 { 4680 mergeAttributeLists(e.getValue(), userAttrsCopy, t, 4681 attrName, options, omitValues, omitReal, omitVirtual); 4682 continue; 4683 } 4684 } 4685 4686 for (Map.Entry<AttributeType, List<Attribute>> e : 4687 operationalAttributes.entrySet()) 4688 { 4689 AttributeType t = e.getKey(); 4690 if (t.hasNameOrOID(lowerName)) 4691 { 4692 mergeAttributeLists(e.getValue(), operationalAttrsCopy, 4693 t, attrName, options, omitValues, omitReal, omitVirtual); 4694 continue; 4695 } 4696 } 4697 } 4698 else 4699 { 4700 // Recognized attribute type. 4701 if (attrType.isObjectClass()) { 4702 if (!omitReal) 4703 { 4704 if (omitValues) 4705 { 4706 userAttrsCopy.put(ocType, newArrayList(Attributes.empty(ocType, attrName))); 4707 } 4708 else 4709 { 4710 Attribute ocAttr = getObjectClassAttribute(); 4711 if (ocAttr != null) 4712 { 4713 if (!attrName.equals(ocAttr.getName())) 4714 { 4715 // User requested non-default object class type name. 4716 AttributeBuilder builder = new AttributeBuilder(ocAttr); 4717 builder.setAttributeType(ocType, attrName); 4718 ocAttr = builder.toAttribute(); 4719 } 4720 4721 userAttrsCopy.put(ocType, newArrayList(ocAttr)); 4722 } 4723 } 4724 } 4725 } 4726 else 4727 { 4728 List<Attribute> attrList = getUserAttribute(attrType); 4729 if (attrList != null) 4730 { 4731 mergeAttributeLists(attrList, userAttrsCopy, attrType, 4732 attrName, options, omitValues, omitReal, omitVirtual); 4733 } 4734 else 4735 { 4736 attrList = getOperationalAttribute(attrType); 4737 if (attrList != null) 4738 { 4739 mergeAttributeLists(attrList, operationalAttrsCopy, 4740 attrType, attrName, options, omitValues, omitReal, 4741 omitVirtual); 4742 } 4743 } 4744 } 4745 } 4746 } 4747 } 4748 4749 return new Entry(dn, objectClassesCopy, userAttrsCopy, 4750 operationalAttrsCopy); 4751 } 4752 4753 /** 4754 * Copies the provided list of attributes into the destination 4755 * attribute map according to the provided criteria. 4756 * 4757 * @param sourceList 4758 * The list containing the attributes to be copied. 4759 * @param destMap 4760 * The map where the attributes should be copied to. 4761 * @param attrType 4762 * The attribute type. 4763 * @param attrName 4764 * The user-provided attribute name. 4765 * @param options 4766 * The user-provided attribute options. 4767 * @param omitValues 4768 * Indicates whether to exclude attribute values. 4769 * @param omitReal 4770 * Indicates whether to exclude real attributes. 4771 * @param omitVirtual 4772 * Indicates whether to exclude virtual attributes. 4773 */ 4774 private void mergeAttributeLists(List<Attribute> sourceList, 4775 Map<AttributeType, List<Attribute>> destMap, 4776 AttributeType attrType, String attrName, Set<String> options, 4777 boolean omitValues, boolean omitReal, boolean omitVirtual) 4778 { 4779 if (sourceList == null) 4780 { 4781 return; 4782 } 4783 4784 for (Attribute attribute : sourceList) 4785 { 4786 if (attribute.isEmpty() 4787 || (omitReal && attribute.isReal()) 4788 || (omitVirtual && attribute.isVirtual()) 4789 || !attribute.hasAllOptions(options)) 4790 { 4791 continue; 4792 } 4793 else 4794 { 4795 // If a non-default attribute name was provided or if the 4796 // attribute has options then we will need to rebuild the 4797 // attribute so that it contains the user-requested names and options. 4798 AttributeType subAttrType = attribute.getAttributeType(); 4799 4800 if ((attrName != null && !attrName.equals(attribute.getName())) 4801 || (options != null && !options.isEmpty())) 4802 { 4803 AttributeBuilder builder = new AttributeBuilder(); 4804 4805 // We want to use the user-provided name only if this attribute has 4806 // the same type as the requested type. This might not be the case for 4807 // sub-types e.g. requesting "name" and getting back "cn" - we don't 4808 // want to rename "name" to "cn". 4809 if (attrName == null || !subAttrType.equals(attrType)) 4810 { 4811 builder.setAttributeType(subAttrType, attribute.getName()); 4812 } 4813 else 4814 { 4815 builder.setAttributeType(subAttrType, attrName); 4816 } 4817 4818 if (options != null) 4819 { 4820 builder.setOptions(options); 4821 } 4822 4823 // Now add in remaining options from original attribute 4824 // (this will not overwrite options already present). 4825 builder.setOptions(attribute.getOptions()); 4826 4827 if (!omitValues) 4828 { 4829 builder.addAll(attribute); 4830 } 4831 4832 attribute = builder.toAttribute(); 4833 } 4834 else if (omitValues) 4835 { 4836 attribute = Attributes.empty(attribute); 4837 } 4838 4839 // Now put the attribute into the destination map. 4840 // Be careful of duplicates. 4841 List<Attribute> attrList = destMap.get(subAttrType); 4842 4843 if (attrList == null) 4844 { 4845 // Assume that they'll all go in the one list. This isn't 4846 // always the case, for example if the list contains sub-types. 4847 attrList = new ArrayList<>(sourceList.size()); 4848 attrList.add(attribute); 4849 destMap.put(subAttrType, attrList); 4850 } 4851 else 4852 { 4853 // The attribute may have already been put in the list. 4854 // 4855 // This may occur in two cases: 4856 // 4857 // 1) The attribute is identified by more than one attribute 4858 // type description in the attribute list (e.g. in a wildcard). 4859 // 4860 // 2) The attribute has both a real and virtual component. 4861 // 4862 boolean found = false; 4863 for (int i = 0; i < attrList.size(); i++) 4864 { 4865 Attribute otherAttribute = attrList.get(i); 4866 if (otherAttribute.optionsEqual(attribute.getOptions())) 4867 { 4868 // Assume that wildcards appear first in an attribute 4869 // list with more specific attribute names afterwards: 4870 // let the attribute name and options from the later 4871 // attribute take preference. 4872 attrList.set(i, Attributes.merge(attribute, otherAttribute)); 4873 found = true; 4874 } 4875 } 4876 4877 if (!found) 4878 { 4879 attrList.add(attribute); 4880 } 4881 } 4882 } 4883 } 4884 } 4885}