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