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 */ 020package org.apache.directory.api.ldap.model.entry; 021 022 023import java.text.ParseException; 024import java.util.Arrays; 025import java.util.Iterator; 026 027import javax.naming.NamingEnumeration; 028import javax.naming.NamingException; 029import javax.naming.directory.Attributes; 030import javax.naming.directory.BasicAttribute; 031import javax.naming.directory.BasicAttributes; 032 033import org.apache.directory.api.i18n.I18n; 034import org.apache.directory.api.ldap.model.exception.LdapException; 035import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeTypeException; 036import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; 037import org.apache.directory.api.ldap.model.name.Dn; 038import org.apache.directory.api.util.Chars; 039import org.apache.directory.api.util.Position; 040import org.apache.directory.api.util.Strings; 041 042 043/** 044 * A set of utility fuctions for working with Attributes. 045 * 046 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 047 */ 048public final class AttributeUtils 049{ 050 private AttributeUtils() 051 { 052 } 053 054 055 /** 056 * Check if an attribute contains a value. The test is case insensitive, 057 * and the value is supposed to be a String. If the value is a byte[], 058 * then the case sensitivity is useless. 059 * 060 * @param attr The attribute to check 061 * @param value The value to look for 062 * @return true if the value is present in the attribute 063 */ 064 public static boolean containsValueCaseIgnore( javax.naming.directory.Attribute attr, Object value ) 065 { 066 // quick bypass test 067 if ( attr.contains( value ) ) 068 { 069 return true; 070 } 071 072 try 073 { 074 if ( value instanceof String ) 075 { 076 String strVal = ( String ) value; 077 078 NamingEnumeration<?> attrVals = attr.getAll(); 079 080 while ( attrVals.hasMoreElements() ) 081 { 082 Object attrVal = attrVals.nextElement(); 083 084 if ( attrVal instanceof String && strVal.equalsIgnoreCase( ( String ) attrVal ) ) 085 { 086 return true; 087 } 088 } 089 } 090 else 091 { 092 byte[] valueBytes = ( byte[] ) value; 093 094 NamingEnumeration<?> attrVals = attr.getAll(); 095 096 while ( attrVals.hasMoreElements() ) 097 { 098 Object attrVal = attrVals.nextElement(); 099 100 if ( attrVal instanceof byte[] && Arrays.equals( ( byte[] ) attrVal, valueBytes ) ) 101 { 102 return true; 103 } 104 } 105 } 106 } 107 catch ( NamingException ne ) 108 { 109 return false; 110 } 111 112 return false; 113 } 114 115 116 /** 117 * Check if the attributes is a BasicAttributes, and if so, switch 118 * the case sensitivity to false to avoid tricky problems in the server. 119 * (Ldap attributeTypes are *always* case insensitive) 120 * 121 * @param attributes The Attributes to check 122 * @return The converted Attributes 123 */ 124 public static Attributes toCaseInsensitive( Attributes attributes ) 125 { 126 if ( attributes == null ) 127 { 128 return attributes; 129 } 130 131 if ( attributes instanceof BasicAttributes ) 132 { 133 if ( attributes.isCaseIgnored() ) 134 { 135 // Just do nothing if the Attributes is already case insensitive 136 return attributes; 137 } 138 else 139 { 140 // Ok, bad news : we have to create a new BasicAttributes 141 // which will be case insensitive 142 Attributes newAttrs = new BasicAttributes( true ); 143 144 NamingEnumeration<?> attrs = attributes.getAll(); 145 146 if ( attrs != null ) 147 { 148 // Iterate through the attributes now 149 while ( attrs.hasMoreElements() ) 150 { 151 newAttrs.put( ( javax.naming.directory.Attribute ) attrs.nextElement() ); 152 } 153 } 154 155 return newAttrs; 156 } 157 } 158 else 159 { 160 // we can safely return the attributes if it's not a BasicAttributes 161 return attributes; 162 } 163 } 164 165 166 /** 167 * Parse attribute's options : 168 * 169 * options = *( ';' option ) 170 * option = 1*keychar 171 * keychar = 'a'-z' | 'A'-'Z' / '0'-'9' / '-' 172 */ 173 private static void parseOptions( byte[] str, Position pos ) throws ParseException 174 { 175 while ( Strings.isCharASCII( str, pos.start, ';' ) ) 176 { 177 pos.start++; 178 179 // We have an option 180 if ( !Chars.isAlphaDigitMinus( str, pos.start ) ) 181 { 182 // We must have at least one keychar 183 throw new ParseException( I18n.err( I18n.ERR_04343 ), pos.start ); 184 } 185 186 pos.start++; 187 188 while ( Chars.isAlphaDigitMinus( str, pos.start ) ) 189 { 190 pos.start++; 191 } 192 } 193 } 194 195 196 /** 197 * Parse a number : 198 * 199 * number = '0' | '1'..'9' digits 200 * digits = '0'..'9'* 201 * 202 * @return true if a number has been found 203 */ 204 private static boolean parseNumber( byte[] filter, Position pos ) 205 { 206 byte b = Strings.byteAt( filter, pos.start ); 207 208 switch ( b ) 209 { 210 case '0': 211 // If we get a starting '0', we should get out 212 pos.start++; 213 return true; 214 215 case '1': 216 case '2': 217 case '3': 218 case '4': 219 case '5': 220 case '6': 221 case '7': 222 case '8': 223 case '9': 224 pos.start++; 225 break; 226 227 default: 228 // Not a number. 229 return false; 230 } 231 232 while ( Chars.isDigit( filter, pos.start ) ) 233 { 234 pos.start++; 235 } 236 237 return true; 238 } 239 240 241 /** 242 * 243 * Parse an OID. 244 * 245 * numericoid = number 1*( '.' number ) 246 * number = '0'-'9' / ( '1'-'9' 1*'0'-'9' ) 247 * 248 * @param str The OID to parse 249 * @param pos The current position in the string 250 * @throws ParseException If we don't have a valid OID 251 */ 252 private static void parseOID( byte[] str, Position pos ) throws ParseException 253 { 254 // We have an OID 255 parseNumber( str, pos ); 256 257 // We must have at least one '.' number 258 if ( !Strings.isCharASCII( str, pos.start, '.' ) ) 259 { 260 throw new ParseException( I18n.err( I18n.ERR_04344 ), pos.start ); 261 } 262 263 pos.start++; 264 265 if ( !parseNumber( str, pos ) ) 266 { 267 throw new ParseException( I18n.err( I18n.ERR_04345 ), pos.start ); 268 } 269 270 while ( true ) 271 { 272 // Break if we get something which is not a '.' 273 if ( !Strings.isCharASCII( str, pos.start, '.' ) ) 274 { 275 break; 276 } 277 278 pos.start++; 279 280 if ( !parseNumber( str, pos ) ) 281 { 282 throw new ParseException( I18n.err( I18n.ERR_04345 ), pos.start ); 283 } 284 } 285 } 286 287 288 /** 289 * Parse an attribute. The grammar is : 290 * attributedescription = attributetype options 291 * attributetype = oid 292 * oid = descr / numericoid 293 * descr = keystring 294 * numericoid = number 1*( '.' number ) 295 * options = *( ';' option ) 296 * option = 1*keychar 297 * keystring = leadkeychar *keychar 298 * leadkeychar = 'a'-z' | 'A'-'Z' 299 * keychar = 'a'-z' | 'A'-'Z' / '0'-'9' / '-' 300 * number = '0'-'9' / ( '1'-'9' 1*'0'-'9' ) 301 * 302 * @param str The parsed attribute, 303 * @param pos The position of the attribute in the current string 304 * @param withOption A flag telling of the attribute has an option 305 * @param relaxed A flag used to tell the parser to be in relaxed mode 306 * @return The parsed attribute if valid 307 * @throws ParseException If we faced an error while parsing the value 308 */ 309 public static String parseAttribute( byte[] str, Position pos, boolean withOption, boolean relaxed ) 310 throws ParseException 311 { 312 // We must have an OID or an DESCR first 313 byte b = Strings.byteAt( str, pos.start ); 314 315 if ( b == '\0' ) 316 { 317 throw new ParseException( I18n.err( I18n.ERR_04346 ), pos.start ); 318 } 319 320 int start = pos.start; 321 322 if ( Chars.isAlpha( b ) ) 323 { 324 // A DESCR 325 pos.start++; 326 327 while ( Chars.isAlphaDigitMinus( str, pos.start ) || ( relaxed && Chars.isUnderscore( str, pos.start ) ) ) 328 { 329 pos.start++; 330 } 331 332 // Parse the options if needed 333 if ( withOption ) 334 { 335 parseOptions( str, pos ); 336 } 337 338 return Strings.getString( str, start, pos.start - start, "UTF-8" ); 339 } 340 else if ( Chars.isDigit( b ) ) 341 { 342 // An OID 343 pos.start++; 344 345 // Parse the OID 346 parseOID( str, pos ); 347 348 // Parse the options 349 if ( withOption ) 350 { 351 parseOptions( str, pos ); 352 } 353 354 return Strings.getString( str, start, pos.start - start, "UTF-8" ); 355 } 356 else 357 { 358 throw new ParseException( I18n.err( I18n.ERR_04347 ), pos.start ); 359 } 360 } 361 362 363 /** 364 * A method to apply a modification to an existing entry. 365 * 366 * @param entry The entry on which we want to apply a modification 367 * @param modification the Modification to be applied 368 * @throws org.apache.directory.api.ldap.model.exception.LdapException if some operation fails. 369 */ 370 public static void applyModification( Entry entry, Modification modification ) throws LdapException 371 { 372 Attribute modAttr = modification.getAttribute(); 373 String modificationId = modAttr.getUpId(); 374 375 switch ( modification.getOperation() ) 376 { 377 case ADD_ATTRIBUTE: 378 Attribute modifiedAttr = entry.get( modificationId ); 379 380 if ( modifiedAttr == null ) 381 { 382 // The attribute should be added. 383 entry.put( modAttr ); 384 } 385 else 386 { 387 // The attribute exists : the values can be different, 388 // so we will just add the new values to the existing ones. 389 for ( Value<?> value : modAttr ) 390 { 391 // If the value already exist, nothing is done. 392 // Note that the attribute *must* have been 393 // normalized before. 394 modifiedAttr.add( value ); 395 } 396 } 397 398 break; 399 400 case REMOVE_ATTRIBUTE: 401 if ( modAttr.get() == null ) 402 { 403 // We have no value in the ModificationItem attribute : 404 // we have to remove the whole attribute from the initial 405 // entry 406 entry.removeAttributes( modificationId ); 407 } 408 else 409 { 410 // We just have to remove the values from the original 411 // entry, if they exist. 412 modifiedAttr = entry.get( modificationId ); 413 414 if ( modifiedAttr == null ) 415 { 416 break; 417 } 418 419 for ( Value<?> value : modAttr ) 420 { 421 // If the value does not exist, nothing is done. 422 // Note that the attribute *must* have been 423 // normalized before. 424 modifiedAttr.remove( value ); 425 } 426 427 if ( modifiedAttr.size() == 0 ) 428 { 429 // If this was the last value, remove the attribute 430 entry.removeAttributes( modifiedAttr.getUpId() ); 431 } 432 } 433 434 break; 435 436 case REPLACE_ATTRIBUTE: 437 if ( modAttr.get() == null ) 438 { 439 // If the modification does not have any value, we have 440 // to delete the attribute from the entry. 441 entry.removeAttributes( modificationId ); 442 } 443 else 444 { 445 // otherwise, just substitute the existing attribute. 446 entry.put( modAttr ); 447 } 448 449 break; 450 default: 451 break; 452 } 453 } 454 455 456 /** 457 * Convert a BasicAttributes or a AttributesImpl to an Entry 458 * 459 * @param attributes the BasicAttributes or AttributesImpl instance to convert 460 * @param dn The Dn which is needed by the Entry 461 * @return An instance of a Entry object 462 * 463 * @throws LdapException If we get an invalid attribute 464 */ 465 public static Entry toEntry( Attributes attributes, Dn dn ) throws LdapException 466 { 467 if ( attributes instanceof BasicAttributes ) 468 { 469 try 470 { 471 Entry entry = new DefaultEntry( dn ); 472 473 for ( NamingEnumeration<? extends javax.naming.directory.Attribute> attrs = attributes.getAll(); attrs 474 .hasMoreElements(); ) 475 { 476 javax.naming.directory.Attribute attr = attrs.nextElement(); 477 478 Attribute entryAttribute = toApiAttribute( attr ); 479 480 if ( entryAttribute != null ) 481 { 482 entry.put( entryAttribute ); 483 } 484 } 485 486 return entry; 487 } 488 catch ( LdapException ne ) 489 { 490 throw new LdapInvalidAttributeTypeException( ne.getMessage(), ne ); 491 } 492 } 493 else 494 { 495 return null; 496 } 497 } 498 499 500 /** 501 * Converts an {@link Entry} to an {@link Attributes}. 502 * 503 * @param entry 504 * the {@link Entry} to convert 505 * @return 506 * the equivalent {@link Attributes} 507 */ 508 public static Attributes toAttributes( Entry entry ) 509 { 510 if ( entry != null ) 511 { 512 Attributes attributes = new BasicAttributes( true ); 513 514 // Looping on attributes 515 for ( Iterator<Attribute> attributeIterator = entry.iterator(); attributeIterator.hasNext(); ) 516 { 517 Attribute entryAttribute = attributeIterator.next(); 518 519 attributes.put( toJndiAttribute( entryAttribute ) ); 520 } 521 522 return attributes; 523 } 524 525 return null; 526 } 527 528 529 /** 530 * Converts an {@link Attribute} to a JNDI Attribute. 531 * 532 * @param attribute the {@link Attribute} to convert 533 * @return the equivalent JNDI Attribute 534 */ 535 public static javax.naming.directory.Attribute toJndiAttribute( Attribute attribute ) 536 { 537 if ( attribute != null ) 538 { 539 javax.naming.directory.Attribute jndiAttribute = new BasicAttribute( attribute.getUpId() ); 540 541 // Looping on values 542 for ( Iterator<Value<?>> valueIterator = attribute.iterator(); valueIterator.hasNext(); ) 543 { 544 Value<?> value = valueIterator.next(); 545 jndiAttribute.add( value.getValue() ); 546 } 547 548 return jndiAttribute; 549 } 550 551 return null; 552 } 553 554 555 /** 556 * Convert a JNDI Attribute to an LDAP API Attribute 557 * 558 * @param jndiAttribute the JNDI Attribute instance to convert 559 * @return An instance of a LDAP API Attribute object 560 * @throws LdapInvalidAttributeValueException If we can't convert some value 561 */ 562 public static Attribute toApiAttribute( javax.naming.directory.Attribute jndiAttribute ) 563 throws LdapInvalidAttributeValueException 564 { 565 if ( jndiAttribute == null ) 566 { 567 return null; 568 } 569 570 try 571 { 572 Attribute attribute = new DefaultAttribute( jndiAttribute.getID() ); 573 574 for ( NamingEnumeration<?> values = jndiAttribute.getAll(); values.hasMoreElements(); ) 575 { 576 Object value = values.nextElement(); 577 578 if ( value instanceof String ) 579 { 580 attribute.add( ( String ) value ); 581 } 582 else if ( value instanceof byte[] ) 583 { 584 attribute.add( ( byte[] ) value ); 585 } 586 else 587 { 588 attribute.add( ( String ) null ); 589 } 590 } 591 592 return attribute; 593 } 594 catch ( NamingException ne ) 595 { 596 return null; 597 } 598 } 599}