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