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.shared.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.shared.i18n.I18n; 034import org.apache.directory.shared.ldap.model.exception.LdapException; 035import org.apache.directory.shared.ldap.model.exception.LdapInvalidAttributeTypeException; 036import org.apache.directory.shared.ldap.model.exception.LdapInvalidAttributeValueException; 037import org.apache.directory.shared.ldap.model.name.Dn; 038import org.apache.directory.shared.util.Chars; 039import org.apache.directory.shared.util.Position; 040import org.apache.directory.shared.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( String 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( String filter, Position pos ) 200 { 201 char c = Strings.charAt(filter, pos.start); 202 203 switch ( c ) 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( String 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( String str, Position pos, boolean withOption ) throws ParseException 302 { 303 // We must have an OID or an DESCR first 304 char c = Strings.charAt(str, pos.start); 305 306 if ( c == '\0' ) 307 { 308 throw new ParseException( I18n.err( I18n.ERR_04346 ), pos.start ); 309 } 310 311 int start = pos.start; 312 313 if ( Chars.isAlpha(c) ) 314 { 315 // A DESCR 316 pos.start++; 317 318 while ( Chars.isAlphaDigitMinus(str, pos.start) ) 319 { 320 pos.start++; 321 } 322 323 // Parse the options if needed 324 if ( withOption ) 325 { 326 parseOptions( str, pos ); 327 } 328 329 return str.substring( start, pos.start ); 330 } 331 else if ( Chars.isDigit(c) ) 332 { 333 // An OID 334 pos.start++; 335 336 // Parse the OID 337 parseOID( str, pos ); 338 339 // Parse the options 340 if ( withOption ) 341 { 342 parseOptions( str, pos ); 343 } 344 345 return str.substring( start, pos.start ); 346 } 347 else 348 { 349 throw new ParseException( I18n.err( I18n.ERR_04347 ), pos.start ); 350 } 351 } 352 353 354 /** 355 * A method to apply a modification to an existing entry. 356 * 357 * @param entry The entry on which we want to apply a modification 358 * @param modification the Modification to be applied 359 * @throws org.apache.directory.shared.ldap.model.exception.LdapException if some operation fails. 360 */ 361 public static void applyModification( Entry entry, Modification modification ) throws LdapException 362 { 363 Attribute modAttr = modification.getAttribute(); 364 String modificationId = modAttr.getId(); 365 366 switch ( modification.getOperation() ) 367 { 368 case ADD_ATTRIBUTE: 369 Attribute modifiedAttr = entry.get( modificationId ); 370 371 if ( modifiedAttr == null ) 372 { 373 // The attribute should be added. 374 entry.put( modAttr ); 375 } 376 else 377 { 378 // The attribute exists : the values can be different, 379 // so we will just add the new values to the existing ones. 380 for ( Value<?> value : modAttr ) 381 { 382 // If the value already exist, nothing is done. 383 // Note that the attribute *must* have been 384 // normalized before. 385 modifiedAttr.add( value ); 386 } 387 } 388 389 break; 390 391 case REMOVE_ATTRIBUTE: 392 if ( modAttr.get() == null ) 393 { 394 // We have no value in the ModificationItem attribute : 395 // we have to remove the whole attribute from the initial 396 // entry 397 entry.removeAttributes( modificationId ); 398 } 399 else 400 { 401 // We just have to remove the values from the original 402 // entry, if they exist. 403 modifiedAttr = entry.get( modificationId ); 404 405 if ( modifiedAttr == null ) 406 { 407 break; 408 } 409 410 for ( Value<?> value : modAttr ) 411 { 412 // If the value does not exist, nothing is done. 413 // Note that the attribute *must* have been 414 // normalized before. 415 modifiedAttr.remove( value ); 416 } 417 418 if ( modifiedAttr.size() == 0 ) 419 { 420 // If this was the last value, remove the attribute 421 entry.removeAttributes( modifiedAttr.getId() ); 422 } 423 } 424 425 break; 426 427 case REPLACE_ATTRIBUTE: 428 if ( modAttr.get() == null ) 429 { 430 // If the modification does not have any value, we have 431 // to delete the attribute from the entry. 432 entry.removeAttributes( modificationId ); 433 } 434 else 435 { 436 // otherwise, just substitute the existing attribute. 437 entry.put( modAttr ); 438 } 439 440 break; 441 default: 442 break; 443 } 444 } 445 446 447 /** 448 * Convert a BasicAttributes or a AttributesImpl to an Entry 449 * 450 * @param attributes the BasicAttributes or AttributesImpl instance to convert 451 * @param dn The Dn which is needed by the Entry 452 * @return An instance of a Entry object 453 * 454 * @throws LdapException If we get an invalid attribute 455 */ 456 public static Entry toEntry( Attributes attributes, Dn dn ) throws LdapException 457 { 458 if ( attributes instanceof BasicAttributes ) 459 { 460 try 461 { 462 Entry entry = new DefaultEntry( dn ); 463 464 for ( NamingEnumeration<? extends javax.naming.directory.Attribute> attrs = attributes.getAll(); attrs.hasMoreElements(); ) 465 { 466 javax.naming.directory.Attribute attr = attrs.nextElement(); 467 468 Attribute entryAttribute = toApiAttribute( attr ); 469 470 if ( entryAttribute != null ) 471 { 472 entry.put( entryAttribute ); 473 } 474 } 475 476 return entry; 477 } 478 catch ( LdapException ne ) 479 { 480 throw new LdapInvalidAttributeTypeException( ne.getMessage(), ne ); 481 } 482 } 483 else 484 { 485 return null; 486 } 487 } 488 489 490 /** 491 * Converts an {@link Entry} to an {@link Attributes}. 492 * 493 * @param entry 494 * the {@link Entry} to convert 495 * @return 496 * the equivalent {@link Attributes} 497 */ 498 public static Attributes toAttributes( Entry entry ) 499 { 500 if ( entry != null ) 501 { 502 Attributes attributes = new BasicAttributes( true ); 503 504 // Looping on attributes 505 for ( Iterator<Attribute> attributeIterator = entry.iterator(); attributeIterator.hasNext(); ) 506 { 507 Attribute entryAttribute = ( Attribute ) attributeIterator.next(); 508 509 attributes.put( toJndiAttribute( entryAttribute ) ); 510 } 511 512 return attributes; 513 } 514 515 return null; 516 } 517 518 519 /** 520 * Converts an {@link Attribute} to a JNDI Attribute. 521 * 522 * @param attribute the {@link Attribute} to convert 523 * @return the equivalent JNDI Attribute 524 */ 525 public static javax.naming.directory.Attribute toJndiAttribute( Attribute attribute ) 526 { 527 if ( attribute != null ) 528 { 529 javax.naming.directory.Attribute jndiAttribute = new BasicAttribute( attribute.getId() ); 530 531 // Looping on values 532 for ( Iterator<Value<?>> valueIterator = attribute.iterator(); valueIterator.hasNext(); ) 533 { 534 Value<?> value = valueIterator.next(); 535 jndiAttribute.add( value.getValue() ); 536 } 537 538 return jndiAttribute; 539 } 540 541 return null; 542 } 543 544 545 /** 546 * Convert a JNDI Attribute to an LDAP API Attribute 547 * 548 * @param jndiAttribute the JNDI Attribute instance to convert 549 * @return An instance of a LDAP API Attribute object 550 */ 551 public static Attribute toApiAttribute( javax.naming.directory.Attribute jndiAttribute ) throws LdapInvalidAttributeValueException 552 { 553 if ( jndiAttribute == null ) 554 { 555 return null; 556 } 557 558 try 559 { 560 Attribute attribute = new DefaultAttribute( jndiAttribute.getID() ); 561 562 for ( NamingEnumeration<?> values = jndiAttribute.getAll(); values.hasMoreElements(); ) 563 { 564 Object value = values.nextElement(); 565 566 567 if ( value instanceof String ) 568 { 569 attribute.add( ( String ) value ); 570 } 571 else if ( value instanceof byte[] ) 572 { 573 attribute.add( ( byte[] ) value ); 574 } 575 else 576 { 577 attribute.add( ( String ) null ); 578 } 579 } 580 581 return attribute; 582 } 583 catch ( NamingException ne ) 584 { 585 return null; 586 } 587 } 588}