001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 package org.apache.directory.server.core.entry; 020 021 import java.util.ArrayList; 022 import java.util.HashSet; 023 import java.util.List; 024 import java.util.NoSuchElementException; 025 import java.util.Set; 026 027 import javax.naming.NamingEnumeration; 028 import javax.naming.NamingException; 029 import javax.naming.directory.Attribute; 030 import javax.naming.directory.Attributes; 031 import javax.naming.directory.BasicAttribute; 032 import javax.naming.directory.BasicAttributes; 033 import javax.naming.directory.DirContext; 034 import javax.naming.directory.InvalidAttributeIdentifierException; 035 import javax.naming.directory.ModificationItem; 036 import javax.naming.directory.SearchResult; 037 038 import org.apache.directory.server.i18n.I18n; 039 import org.apache.directory.shared.ldap.constants.SchemaConstants; 040 import org.apache.directory.shared.ldap.entry.DefaultServerAttribute; 041 import org.apache.directory.shared.ldap.entry.DefaultServerEntry; 042 import org.apache.directory.shared.ldap.entry.EntryAttribute; 043 import org.apache.directory.shared.ldap.entry.Modification; 044 import org.apache.directory.shared.ldap.entry.ModificationOperation; 045 import org.apache.directory.shared.ldap.entry.ServerEntry; 046 import org.apache.directory.shared.ldap.entry.ServerModification; 047 import org.apache.directory.shared.ldap.entry.Value; 048 import org.apache.directory.shared.ldap.exception.LdapException; 049 import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeTypeException; 050 import org.apache.directory.shared.ldap.name.DN; 051 import org.apache.directory.shared.ldap.schema.AttributeType; 052 import org.apache.directory.shared.ldap.schema.SchemaManager; 053 import org.apache.directory.shared.ldap.schema.SchemaUtils; 054 import org.apache.directory.shared.ldap.util.EmptyEnumeration; 055 import org.apache.directory.shared.ldap.util.StringTools; 056 057 /** 058 * A helper class used to manipulate Entries, Attributes and Values. 059 * 060 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 061 * @version $Rev$, $Date$ 062 */ 063 public class ServerEntryUtils 064 { 065 /** 066 * Convert a ServerAttribute into a BasicAttribute. The DN is lost 067 * during this conversion, as the Attributes object does not store 068 * this element. 069 * 070 * @return An instance of a AttributesImpl() object 071 */ 072 public static Attribute toBasicAttribute( EntryAttribute entryAttribute ) 073 { 074 AttributeType attributeType = entryAttribute.getAttributeType(); 075 076 Attribute attribute = new BasicAttribute( attributeType.getName() ); 077 078 for ( Value<?> value: entryAttribute ) 079 { 080 attribute.add( value.get() ); 081 } 082 083 return attribute; 084 } 085 086 087 /** 088 * Convert a ServerEntry into a BasicAttributes. The DN is lost 089 * during this conversion, as the Attributes object does not store 090 * this element. 091 * 092 * @return An instance of a AttributesImpl() object 093 */ 094 public static Attributes toBasicAttributes( ServerEntry entry ) 095 { 096 if ( entry == null ) 097 { 098 return null; 099 } 100 101 Attributes attributes = new BasicAttributes( true ); 102 103 for ( AttributeType attributeType:entry.getAttributeTypes() ) 104 { 105 EntryAttribute attr = entry.get( attributeType ); 106 107 // Deal with a special case : an entry without any ObjectClass 108 if ( attributeType.getOid().equals( SchemaConstants.OBJECT_CLASS_AT_OID ) ) 109 { 110 if ( attr.size() == 0 ) 111 { 112 // We don't have any objectClass, just dismiss this element 113 continue; 114 } 115 } 116 117 attributes.put( toBasicAttribute( attr ) ); 118 } 119 120 return attributes; 121 } 122 123 124 /** 125 * Convert a BasicAttribute or a AttributeImpl to a ServerAtribute 126 * 127 * @param attribute the BasicAttributes or AttributesImpl instance to convert 128 * @param attributeType 129 * @return An instance of a ServerEntry object 130 * 131 * @throws InvalidAttributeIdentifierException If we had an incorrect attribute 132 */ 133 public static EntryAttribute toServerAttribute( Attribute attribute, AttributeType attributeType ) 134 { 135 if ( attribute == null ) 136 { 137 return null; 138 } 139 140 try 141 { 142 EntryAttribute serverAttribute = new DefaultServerAttribute( attributeType ); 143 144 for ( NamingEnumeration<?> values = attribute.getAll(); values.hasMoreElements(); ) 145 { 146 Object value = values.nextElement(); 147 148 if ( value == null ) 149 { 150 continue; 151 } 152 153 if ( serverAttribute.isHR() ) 154 { 155 if ( value instanceof String ) 156 { 157 serverAttribute.add( (String)value ); 158 } 159 else if ( value instanceof byte[] ) 160 { 161 serverAttribute.add( StringTools.utf8ToString( (byte[])value ) ); 162 } 163 else 164 { 165 return null; 166 } 167 } 168 else 169 { 170 if ( value instanceof String ) 171 { 172 serverAttribute.add( StringTools.getBytesUtf8( (String)value ) ); 173 } 174 else if ( value instanceof byte[] ) 175 { 176 serverAttribute.add( (byte[])value ); 177 } 178 else 179 { 180 return null; 181 } 182 } 183 } 184 185 return serverAttribute; 186 } 187 catch ( NamingException ne ) 188 { 189 return null; 190 } 191 } 192 193 194 /** 195 * Convert a BasicAttributes or a AttributesImpl to a ServerEntry 196 * 197 * @param attributes the BasicAttributes or AttributesImpl instance to convert 198 * @param registries The registries, needed ro build a ServerEntry 199 * @param dn The DN which is needed by the ServerEntry 200 * @return An instance of a ServerEntry object 201 * 202 * @throws LdapInvalidAttributeTypeException If we get an invalid attribute 203 */ 204 public static ServerEntry toServerEntry( Attributes attributes, DN dn, SchemaManager schemaManager ) 205 throws LdapInvalidAttributeTypeException 206 { 207 if ( attributes instanceof BasicAttributes ) 208 { 209 try 210 { 211 ServerEntry entry = new DefaultServerEntry( schemaManager, dn ); 212 213 for ( NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMoreElements(); ) 214 { 215 Attribute attr = attrs.nextElement(); 216 217 String attributeId = attr.getID(); 218 String id = SchemaUtils.stripOptions( attributeId ); 219 Set<String> options = SchemaUtils.getOptions( attributeId ); 220 // TODO : handle options. 221 AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( id ); 222 EntryAttribute serverAttribute = ServerEntryUtils.toServerAttribute( attr, attributeType ); 223 224 if ( serverAttribute != null ) 225 { 226 entry.put( serverAttribute ); 227 } 228 } 229 230 return entry; 231 } 232 catch ( LdapException ne ) 233 { 234 throw new LdapInvalidAttributeTypeException( ne.getLocalizedMessage() ); 235 } 236 } 237 else 238 { 239 return null; 240 } 241 } 242 243 244 /** 245 * Gets the target entry as it would look after a modification operation 246 * was performed on it. 247 * 248 * @param mod the modification 249 * @param entry the source entry that is modified 250 * @return the resultant entry after the modification has taken place 251 * @throws LdapException if there are problems accessing attributes 252 */ 253 public static ServerEntry getTargetEntry( Modification mod, ServerEntry entry, SchemaManager schemaManager ) throws LdapException 254 { 255 ServerEntry targetEntry = ( ServerEntry ) entry.clone(); 256 ModificationOperation modOp = mod.getOperation(); 257 String id = mod.getAttribute().getId(); 258 AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( id ); 259 260 switch ( modOp ) 261 { 262 case REPLACE_ATTRIBUTE : 263 targetEntry.put( mod.getAttribute() ); 264 break; 265 266 case REMOVE_ATTRIBUTE : 267 EntryAttribute toBeRemoved = mod.getAttribute(); 268 269 if ( toBeRemoved.size() == 0 ) 270 { 271 targetEntry.removeAttributes( id ); 272 } 273 else 274 { 275 EntryAttribute existing = targetEntry.get( id ); 276 277 if ( existing != null ) 278 { 279 for ( Value<?> value:toBeRemoved ) 280 { 281 existing.remove( value ); 282 } 283 } 284 } 285 break; 286 287 case ADD_ATTRIBUTE : 288 EntryAttribute combined = new DefaultServerAttribute( id, attributeType ); 289 EntryAttribute toBeAdded = mod.getAttribute(); 290 EntryAttribute existing = entry.get( id ); 291 292 if ( existing != null ) 293 { 294 for ( Value<?> value:existing ) 295 { 296 combined.add( value ); 297 } 298 } 299 300 for ( Value<?> value:toBeAdded ) 301 { 302 combined.add( value ); 303 } 304 305 targetEntry.put( combined ); 306 break; 307 308 default: 309 throw new IllegalStateException( I18n.err( I18n.ERR_464, modOp ) ); 310 } 311 312 return targetEntry; 313 } 314 315 316 /** 317 * Creates a new attribute which contains the values representing the union 318 * of two attributes. If one attribute is null then the resultant attribute 319 * returned is a copy of the non-null attribute. If both are null then we 320 * cannot determine the attribute ID and an {@link IllegalArgumentException} 321 * is raised. 322 * 323 * @param attr0 the first attribute 324 * @param attr1 the second attribute 325 * @return a new attribute with the union of values from both attribute 326 * arguments 327 * @throws LdapException if there are problems accessing attribute values 328 */ 329 public static EntryAttribute getUnion( EntryAttribute attr0, EntryAttribute attr1 ) 330 { 331 if ( attr0 == null && attr1 == null ) 332 { 333 throw new IllegalArgumentException( I18n.err( I18n.ERR_465 ) ); 334 } 335 else if ( attr0 == null ) 336 { 337 return attr1.clone(); 338 } 339 else if ( attr1 == null ) 340 { 341 return attr0.clone(); 342 } 343 else if ( !attr0.getAttributeType().equals( attr1.getAttributeType() ) ) 344 { 345 throw new IllegalArgumentException( I18n.err( I18n.ERR_466 ) ); 346 } 347 348 EntryAttribute attr = attr0.clone(); 349 350 for ( Value<?> value:attr1 ) 351 { 352 attr.add( value ); 353 } 354 355 return attr; 356 } 357 358 359 /** 360 * Convert a ModificationItem to an instance of a ServerModification object 361 * 362 * @param modificationImpl the modification instance to convert 363 * @param attributeType the associated attributeType 364 * @return a instance of a ServerModification object 365 */ 366 private static Modification toServerModification( ModificationItem modificationImpl, AttributeType attributeType ) 367 { 368 ModificationOperation operation; 369 370 switch ( modificationImpl.getModificationOp() ) 371 { 372 case DirContext.REMOVE_ATTRIBUTE : 373 operation = ModificationOperation.REMOVE_ATTRIBUTE; 374 break; 375 376 case DirContext.REPLACE_ATTRIBUTE : 377 operation = ModificationOperation.REPLACE_ATTRIBUTE; 378 break; 379 380 case DirContext.ADD_ATTRIBUTE : 381 default : 382 operation = ModificationOperation.ADD_ATTRIBUTE; 383 break; 384 385 } 386 387 Modification modification = new ServerModification( 388 operation, 389 ServerEntryUtils.toServerAttribute( modificationImpl.getAttribute(), attributeType ) ); 390 391 return modification; 392 393 } 394 395 396 /** 397 * 398 * Convert a list of ModificationItemImpl to a list of 399 * 400 * @param modificationImpls 401 * @param atRegistry 402 * @return 403 * @throws LdapException 404 */ 405 public static List<Modification> convertToServerModification( List<ModificationItem> modificationItems, 406 SchemaManager schemaManager ) throws LdapException 407 { 408 if ( modificationItems != null ) 409 { 410 List<Modification> modifications = new ArrayList<Modification>( modificationItems.size() ); 411 412 for ( ModificationItem modificationItem: modificationItems ) 413 { 414 AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( modificationItem.getAttribute().getID() ); 415 modifications.add( toServerModification( modificationItem, attributeType ) ); 416 } 417 418 return modifications; 419 } 420 else 421 { 422 return null; 423 } 424 } 425 426 427 /** 428 * Convert a Modification to an instance of a ServerModification object. 429 * 430 * @param modificationImpl the modification instance to convert 431 * @param attributeType the associated attributeType 432 * @return a instance of a ServerModification object 433 */ 434 private static Modification toServerModification( Modification modification, AttributeType attributeType ) 435 { 436 if ( modification instanceof ServerModification ) 437 { 438 return modification; 439 } 440 441 Modification serverModification = new ServerModification( 442 modification.getOperation(), 443 new DefaultServerAttribute( attributeType, modification.getAttribute() ) ); 444 445 return serverModification; 446 447 } 448 449 450 public static List<Modification> toServerModification( Modification[] modifications, 451 SchemaManager schemaManager ) throws LdapException 452 { 453 if ( modifications != null ) 454 { 455 List<Modification> modificationsList = new ArrayList<Modification>(); 456 457 for ( Modification modification: modifications ) 458 { 459 String attributeId = modification.getAttribute().getId(); 460 String id = stripOptions( attributeId ); 461 modification.getAttribute().setId( id ); 462 Set<String> options = getOptions( attributeId ); 463 464 // ------------------------------------------------------------------- 465 // DIRSERVER-646 Fix: Replacing an unknown attribute with no values 466 // (deletion) causes an error 467 // ------------------------------------------------------------------- 468 if ( ! schemaManager.getAttributeTypeRegistry().contains( id ) 469 && modification.getAttribute().size() == 0 470 && modification.getOperation() == ModificationOperation.REPLACE_ATTRIBUTE ) 471 { 472 // The attributeType does not exist in the schema. 473 // It's an error 474 String message = I18n.err( I18n.ERR_467, id ); 475 throw new LdapInvalidAttributeTypeException( message ); 476 } 477 else 478 { 479 // TODO : handle options 480 AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( id ); 481 modificationsList.add( toServerModification( modification, attributeType ) ); 482 } 483 } 484 485 return modificationsList; 486 } 487 else 488 { 489 return null; 490 } 491 } 492 493 494 public static List<Modification> toServerModification( ModificationItem[] modifications, 495 SchemaManager schemaManager ) throws LdapException 496 { 497 if ( modifications != null ) 498 { 499 List<Modification> modificationsList = new ArrayList<Modification>(); 500 501 for ( ModificationItem modification: modifications ) 502 { 503 String attributeId = modification.getAttribute().getID(); 504 String id = stripOptions( attributeId ); 505 Set<String> options = getOptions( attributeId ); 506 507 // ------------------------------------------------------------------- 508 // DIRSERVER-646 Fix: Replacing an unknown attribute with no values 509 // (deletion) causes an error 510 // ------------------------------------------------------------------- 511 512 // TODO - after removing JNDI we need to make the server handle 513 // this in the codec 514 515 if ( ! schemaManager.getAttributeTypeRegistry().contains( id ) 516 && modification.getAttribute().size() == 0 517 && modification.getModificationOp() == DirContext.REPLACE_ATTRIBUTE ) 518 { 519 continue; 520 } 521 522 // ------------------------------------------------------------------- 523 // END DIRSERVER-646 Fix 524 // ------------------------------------------------------------------- 525 526 527 // TODO : handle options 528 AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( id ); 529 modificationsList.add( toServerModification( (ModificationItem)modification, attributeType ) ); 530 } 531 532 return modificationsList; 533 } 534 else 535 { 536 return null; 537 } 538 } 539 540 541 /** 542 * Utility method to extract a modification item from an array of modifications. 543 * 544 * @param mods the array of ModificationItems to extract the Attribute from. 545 * @param type the attributeType spec of the Attribute to extract 546 * @return the modification item on the attributeType specified 547 */ 548 public static final Modification getModificationItem( List<Modification> mods, AttributeType type ) 549 { 550 for ( Modification modification:mods ) 551 { 552 EntryAttribute attribute = modification.getAttribute(); 553 554 if ( attribute.getAttributeType() == type ) 555 { 556 return modification; 557 } 558 } 559 560 return null; 561 } 562 563 564 /** 565 * Utility method to extract an attribute from a list of modifications. 566 * 567 * @param mods the list of ModificationItems to extract the Attribute from. 568 * @param type the attributeType spec of the Attribute to extract 569 * @return the extract Attribute or null if no such attribute exists 570 */ 571 public static EntryAttribute getAttribute( List<Modification> mods, AttributeType type ) 572 { 573 Modification mod = getModificationItem( mods, type ); 574 575 if ( mod != null ) 576 { 577 return mod.getAttribute(); 578 } 579 580 return null; 581 } 582 583 584 /** 585 * Encapsulate a ServerSearchResult enumeration into a SearchResult enumeration 586 * @param result The ServerSearchResult enumeration 587 * @return A SearchResultEnumeration 588 */ 589 public static NamingEnumeration<SearchResult> toSearchResultEnum( final NamingEnumeration<ServerSearchResult> result ) 590 { 591 if ( result instanceof EmptyEnumeration<?> ) 592 { 593 return new EmptyEnumeration<SearchResult>(); 594 } 595 596 return new NamingEnumeration<SearchResult> () 597 { 598 public void close() throws NamingException 599 { 600 result.close(); 601 } 602 603 604 /** 605 * @see javax.naming.NamingEnumeration#hasMore() 606 */ 607 public boolean hasMore() throws NamingException 608 { 609 return result.hasMore(); 610 } 611 612 613 /** 614 * @see javax.naming.NamingEnumeration#next() 615 */ 616 public SearchResult next() throws NamingException 617 { 618 ServerSearchResult rec = result.next(); 619 620 SearchResult searchResult = new SearchResult( 621 rec.getDn().getName(), 622 rec.getObject(), 623 toBasicAttributes( rec.getServerEntry() ), 624 rec.isRelative() ); 625 626 return searchResult; 627 } 628 629 630 /** 631 * @see java.util.Enumeration#hasMoreElements() 632 */ 633 public boolean hasMoreElements() 634 { 635 return result.hasMoreElements(); 636 } 637 638 639 /** 640 * @see java.util.Enumeration#nextElement() 641 */ 642 public SearchResult nextElement() 643 { 644 try 645 { 646 ServerSearchResult rec = result.next(); 647 648 SearchResult searchResult = new SearchResult( 649 rec.getDn().getName(), 650 rec.getObject(), 651 toBasicAttributes( rec.getServerEntry() ), 652 rec.isRelative() ); 653 654 return searchResult; 655 } 656 catch ( NamingException ne ) 657 { 658 NoSuchElementException nsee = 659 new NoSuchElementException( I18n.err( I18n.ERR_468 ) ); 660 nsee.initCause( ne ); 661 throw nsee; 662 } 663 } 664 }; 665 } 666 667 668 /** 669 * Remove the options from the attributeType, and returns the ID. 670 * 671 * RFC 4512 : 672 * attributedescription = attributetype options 673 * attributetype = oid 674 * options = *( SEMI option ) 675 * option = 1*keychar 676 */ 677 private static String stripOptions( String attributeId ) 678 { 679 int optionsPos = attributeId.indexOf( ";" ); 680 681 if ( optionsPos != -1 ) 682 { 683 return attributeId.substring( 0, optionsPos ); 684 } 685 else 686 { 687 return attributeId; 688 } 689 } 690 691 692 /** 693 * Get the options from the attributeType. 694 * 695 * For instance, given : 696 * jpegphoto;binary;lang=jp 697 * 698 * your get back a set containing { "binary", "lang=jp" } 699 */ 700 private static Set<String> getOptions( String attributeId ) 701 { 702 int optionsPos = attributeId.indexOf( ";" ); 703 704 if ( optionsPos != -1 ) 705 { 706 Set<String> options = new HashSet<String>(); 707 708 String[] res = attributeId.substring( optionsPos + 1 ).split( ";" ); 709 710 for ( String option:res ) 711 { 712 if ( !StringTools.isEmpty( option ) ) 713 { 714 options.add( option ); 715 } 716 } 717 718 return options; 719 } 720 else 721 { 722 return null; 723 } 724 } 725 }