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.schema; 021 022 023import java.util.HashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027import java.util.UUID; 028 029import org.apache.directory.api.i18n.I18n; 030import org.apache.directory.api.ldap.model.constants.MetaSchemaConstants; 031import org.apache.directory.api.ldap.model.entry.Attribute; 032import org.apache.directory.api.ldap.model.entry.Entry; 033import org.apache.directory.api.ldap.model.entry.Modification; 034import org.apache.directory.api.ldap.model.entry.Value; 035import org.apache.directory.api.ldap.model.exception.LdapException; 036import org.apache.directory.api.util.Strings; 037 038 039/** 040 * Various utility methods for schema functions and objects. 041 * 042 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 043 */ 044public final class SchemaUtils 045{ 046 /** 047 * Private constructor. 048 */ 049 private SchemaUtils() 050 { 051 } 052 053 054 /** 055 * Gets the target entry as it would look after a modification operation 056 * were performed on it. 057 * 058 * @param mods the modifications performed on the entry 059 * @param entry the source entry that is modified 060 * @return the resultant entry after the modifications have taken place 061 * @throws LdapException if there are problems accessing attributes 062 */ 063 public static Entry getTargetEntry( List<? extends Modification> mods, Entry entry ) 064 throws LdapException 065 { 066 Entry targetEntry = entry.clone(); 067 068 for ( Modification mod : mods ) 069 { 070 String id = mod.getAttribute().getId(); 071 072 switch ( mod.getOperation() ) 073 { 074 case REPLACE_ATTRIBUTE: 075 targetEntry.put( mod.getAttribute() ); 076 break; 077 078 case ADD_ATTRIBUTE: 079 Attribute combined = mod.getAttribute().clone(); 080 Attribute toBeAdded = mod.getAttribute(); 081 Attribute existing = entry.get( id ); 082 083 if ( existing != null ) 084 { 085 for ( Value<?> value : existing ) 086 { 087 combined.add( value ); 088 } 089 } 090 091 for ( Value<?> value : toBeAdded ) 092 { 093 combined.add( value ); 094 } 095 096 targetEntry.put( combined ); 097 break; 098 099 case REMOVE_ATTRIBUTE: 100 Attribute toBeRemoved = mod.getAttribute(); 101 102 if ( toBeRemoved.size() == 0 ) 103 { 104 targetEntry.removeAttributes( id ); 105 } 106 else 107 { 108 existing = targetEntry.get( id ); 109 110 if ( existing != null ) 111 { 112 for ( Value<?> value : toBeRemoved ) 113 { 114 existing.remove( value ); 115 } 116 } 117 } 118 119 break; 120 121 default: 122 throw new IllegalStateException( I18n.err( I18n.ERR_04328, mod.getOperation() ) ); 123 } 124 } 125 126 return targetEntry; 127 } 128 129 130 // ------------------------------------------------------------------------ 131 // qdescrs rendering operations 132 // ------------------------------------------------------------------------ 133 134 /** 135 * Renders qdescrs into an existing buffer. 136 * 137 * @param buf 138 * the string buffer to render the quoted description strs into 139 * @param qdescrs 140 * the quoted description strings to render 141 * @return the same string buffer that was given for call chaining 142 */ 143 public static StringBuilder render( StringBuilder buf, List<String> qdescrs ) 144 { 145 if ( ( qdescrs == null ) || qdescrs.isEmpty() ) 146 { 147 return buf; 148 } 149 else if ( qdescrs.size() == 1 ) 150 { 151 buf.append( "'" ).append( qdescrs.get( 0 ) ).append( "'" ); 152 } 153 else 154 { 155 buf.append( "( " ); 156 157 for ( String qdescr : qdescrs ) 158 { 159 buf.append( "'" ).append( qdescr ).append( "' " ); 160 } 161 162 buf.append( ")" ); 163 } 164 165 return buf; 166 } 167 168 169 /** 170 * Renders qdescrs into a new buffer.<br> 171 * <pre> 172 * descrs ::= qdescr | '(' WSP qdescrlist WSP ')' 173 * qdescrlist ::= [ qdescr ( SP qdescr )* ] 174 * qdescr ::= SQUOTE descr SQUOTE 175 * </pre> 176 * @param qdescrs the quoted description strings to render 177 * @return the string buffer the qdescrs are rendered into 178 */ 179 /* No qualifier */static StringBuilder renderQDescrs( StringBuilder buf, List<String> qdescrs ) 180 { 181 if ( ( qdescrs == null ) || qdescrs.isEmpty() ) 182 { 183 return buf; 184 } 185 186 if ( qdescrs.size() == 1 ) 187 { 188 buf.append( '\'' ).append( qdescrs.get( 0 ) ).append( '\'' ); 189 } 190 else 191 { 192 buf.append( "( " ); 193 194 for ( String qdescr : qdescrs ) 195 { 196 buf.append( '\'' ).append( qdescr ).append( "' " ); 197 } 198 199 buf.append( ")" ); 200 } 201 202 return buf; 203 } 204 205 206 /** 207 * Renders QDString into a new buffer.<br> 208 * 209 * @param qdescrs the quoted description strings to render 210 * @return the string buffer the qdescrs are rendered into 211 */ 212 private static StringBuilder renderQDString( StringBuilder buf, String qdString ) 213 { 214 buf.append( '\'' ); 215 216 for ( char c : qdString.toCharArray() ) 217 { 218 switch ( c ) 219 { 220 case 0x27: 221 buf.append( "\\27" ); 222 break; 223 224 case 0x5C: 225 buf.append( "\\5C" ); 226 break; 227 228 default: 229 buf.append( c ); 230 break; 231 } 232 } 233 234 buf.append( '\'' ); 235 236 return buf; 237 } 238 239 240 // ------------------------------------------------------------------------ 241 // objectClass list rendering operations 242 // ------------------------------------------------------------------------ 243 244 /** 245 * Renders a list of object classes for things like a list of superior 246 * objectClasses using the ( oid $ oid ) format. 247 * 248 * @param ocs 249 * the objectClasses to list 250 * @return a buffer which contains the rendered list 251 */ 252 public static StringBuilder render( ObjectClass[] ocs ) 253 { 254 StringBuilder buf = new StringBuilder(); 255 256 return render( buf, ocs ); 257 } 258 259 260 /** 261 * Renders a list of object classes for things like a list of superior 262 * objectClasses using the ( oid $ oid ) format into an existing buffer. 263 * 264 * @param buf 265 * the string buffer to render the list of objectClasses into 266 * @param ocs 267 * the objectClasses to list 268 * @return a buffer which contains the rendered list 269 */ 270 public static StringBuilder render( StringBuilder buf, ObjectClass[] ocs ) 271 { 272 if ( ocs == null || ocs.length == 0 ) 273 { 274 return buf; 275 } 276 else if ( ocs.length == 1 ) 277 { 278 buf.append( ocs[0].getName() ); 279 } 280 else 281 { 282 buf.append( "( " ); 283 284 for ( int ii = 0; ii < ocs.length; ii++ ) 285 { 286 if ( ii + 1 < ocs.length ) 287 { 288 buf.append( ocs[ii].getName() ).append( " $ " ); 289 } 290 else 291 { 292 buf.append( ocs[ii].getName() ); 293 } 294 } 295 296 buf.append( " )" ); 297 } 298 299 return buf; 300 } 301 302 303 // ------------------------------------------------------------------------ 304 // attributeType list rendering operations 305 // ------------------------------------------------------------------------ 306 307 /** 308 * Renders a list of attributeTypes for things like the must or may list of 309 * objectClasses using the ( oid $ oid ) format. 310 * 311 * @param ats 312 * the attributeTypes to list 313 * @return a buffer which contains the rendered list 314 */ 315 public static StringBuilder render( AttributeType[] ats ) 316 { 317 StringBuilder buf = new StringBuilder(); 318 319 return render( buf, ats ); 320 } 321 322 323 /** 324 * Renders a list of attributeTypes for things like the must or may list of 325 * objectClasses using the ( oid $ oid ) format into an existing buffer. 326 * 327 * @param buf 328 * the string buffer to render the list of attributeTypes into 329 * @param ats 330 * the attributeTypes to list 331 * @return a buffer which contains the rendered list 332 */ 333 public static StringBuilder render( StringBuilder buf, AttributeType[] ats ) 334 { 335 if ( ats == null || ats.length == 0 ) 336 { 337 return buf; 338 } 339 else if ( ats.length == 1 ) 340 { 341 buf.append( ats[0].getName() ); 342 } 343 else 344 { 345 buf.append( "( " ); 346 for ( int ii = 0; ii < ats.length; ii++ ) 347 { 348 if ( ii + 1 < ats.length ) 349 { 350 buf.append( ats[ii].getName() ).append( " $ " ); 351 } 352 else 353 { 354 buf.append( ats[ii].getName() ); 355 } 356 } 357 buf.append( " )" ); 358 } 359 360 return buf; 361 } 362 363 364 // ------------------------------------------------------------------------ 365 // schema object rendering operations 366 // ------------------------------------------------------------------------ 367 368 /** 369 * Renders the schema extensions into a new StringBuilder. 370 * 371 * @param extensions the schema extensions map with key and values 372 * @return a StringBuilder with the extensions component of a syntax description 373 */ 374 public static StringBuilder render( Map<String, List<String>> extensions ) 375 { 376 StringBuilder buf = new StringBuilder(); 377 378 if ( extensions.isEmpty() ) 379 { 380 return buf; 381 } 382 383 for ( Map.Entry<String, List<String>> entry : extensions.entrySet() ) 384 { 385 buf.append( " " ).append( entry.getKey() ).append( " " ); 386 387 List<String> values = entry.getValue(); 388 389 // For extensions without values like X-IS-HUMAN-READIBLE 390 if ( values == null || values.isEmpty() ) 391 { 392 continue; 393 } 394 395 // For extensions with a single value we can use one qdstring like 'value' 396 if ( values.size() == 1 ) 397 { 398 buf.append( "'" ).append( values.get( 0 ) ).append( "' " ); 399 continue; 400 } 401 402 // For extensions with several values we have to surround whitespace 403 // separated list of qdstrings like ( 'value0' 'value1' 'value2' ) 404 buf.append( "( " ); 405 for ( String value : values ) 406 { 407 buf.append( "'" ).append( value ).append( "' " ); 408 } 409 buf.append( ")" ); 410 } 411 412 if ( buf.charAt( buf.length() - 1 ) != ' ' ) 413 { 414 buf.append( " " ); 415 } 416 417 return buf; 418 } 419 420 421 /** 422 * Returns a String description of a schema. The resulting String format is : 423 * <br> 424 * (OID [DESC '<description>'] FQCN <fcqn> [BYTECODE <bytecode>] X-SCHEMA '<schema>') 425 * <br> 426 * @param description The description to transform to a String 427 * @return The rendered schema object 428 */ 429 public static String render( LoadableSchemaObject description ) 430 { 431 StringBuilder buf = new StringBuilder(); 432 buf.append( "( " ).append( description.getOid() ); 433 434 if ( description.getDescription() != null ) 435 { 436 buf.append( " DESC " ); 437 renderQDString( buf, description.getDescription() ); 438 } 439 440 buf.append( " FQCN " ).append( description.getFqcn() ); 441 442 if ( !Strings.isEmpty( description.getBytecode() ) ) 443 { 444 buf.append( " BYTECODE " ).append( description.getBytecode() ); 445 } 446 447 buf.append( " X-SCHEMA '" ); 448 buf.append( getSchemaName( description ) ); 449 buf.append( "' )" ); 450 451 return buf.toString(); 452 } 453 454 455 private static String getSchemaName( SchemaObject desc ) 456 { 457 List<String> values = desc.getExtension( MetaSchemaConstants.X_SCHEMA_AT ); 458 459 if ( values == null || values.isEmpty() ) 460 { 461 return MetaSchemaConstants.SCHEMA_OTHER; 462 } 463 464 return values.get( 0 ); 465 } 466 467 468 /** 469 * Remove the options from the attributeType, and returns the ID. 470 * <br> 471 * RFC 4512 : 472 * <pre> 473 * attributedescription = attributetype options 474 * attributetype = oid 475 * options = *( SEMI option ) 476 * option = 1*keychar 477 * </pre> 478 * 479 * @param attributeId The AttributeType to parse 480 * @return The AttributeType without its options 481 */ 482 public static String stripOptions( String attributeId ) 483 { 484 int optionsPos = attributeId.indexOf( ';' ); 485 486 if ( optionsPos != -1 ) 487 { 488 return attributeId.substring( 0, optionsPos ); 489 } 490 else 491 { 492 return attributeId; 493 } 494 } 495 496 497 /** 498 * Get the options from the attributeType. 499 * <br> 500 * For instance, given : 501 * jpegphoto;binary;lang=jp 502 * <br> 503 * your get back a set containing { "binary", "lang=jp" } 504 * 505 * @param attributeId The AttributeType to parse 506 * @return a Set of options found for this AttributeType, or null 507 */ 508 public static Set<String> getOptions( String attributeId ) 509 { 510 int optionsPos = attributeId.indexOf( ';' ); 511 512 if ( optionsPos != -1 ) 513 { 514 Set<String> options = new HashSet<>(); 515 516 String[] res = attributeId.substring( optionsPos + 1 ).split( ";" ); 517 518 for ( String option : res ) 519 { 520 if ( !Strings.isEmpty( option ) ) 521 { 522 options.add( option ); 523 } 524 } 525 526 return options; 527 } 528 else 529 { 530 return null; 531 } 532 } 533 534 535 /** 536 * Transform an UUID in a byte array 537 * @param uuid The UUID to transform 538 * @return The byte[] representing the UUID 539 */ 540 public static byte[] uuidToBytes( UUID uuid ) 541 { 542 Long low = uuid.getLeastSignificantBits(); 543 Long high = uuid.getMostSignificantBits(); 544 byte[] bytes = new byte[16]; 545 546 bytes[0] = ( byte ) ( ( high & 0xff00000000000000L ) >> 56 ); 547 bytes[1] = ( byte ) ( ( high & 0x00ff000000000000L ) >> 48 ); 548 bytes[2] = ( byte ) ( ( high & 0x0000ff0000000000L ) >> 40 ); 549 bytes[3] = ( byte ) ( ( high & 0x000000ff00000000L ) >> 32 ); 550 bytes[4] = ( byte ) ( ( high & 0x00000000ff000000L ) >> 24 ); 551 bytes[5] = ( byte ) ( ( high & 0x0000000000ff0000L ) >> 16 ); 552 bytes[6] = ( byte ) ( ( high & 0x000000000000ff00L ) >> 8 ); 553 bytes[7] = ( byte ) ( high & 0x00000000000000ffL ); 554 bytes[8] = ( byte ) ( ( low & 0xff00000000000000L ) >> 56 ); 555 bytes[9] = ( byte ) ( ( low & 0x00ff000000000000L ) >> 48 ); 556 bytes[10] = ( byte ) ( ( low & 0x0000ff0000000000L ) >> 40 ); 557 bytes[11] = ( byte ) ( ( low & 0x000000ff00000000L ) >> 32 ); 558 bytes[12] = ( byte ) ( ( low & 0x00000000ff000000L ) >> 24 ); 559 bytes[13] = ( byte ) ( ( low & 0x0000000000ff0000L ) >> 16 ); 560 bytes[14] = ( byte ) ( ( low & 0x000000000000ff00L ) >> 8 ); 561 bytes[15] = ( byte ) ( low & 0x00000000000000ffL ); 562 563 return bytes; 564 } 565 566 567 /** 568 * Tells if an AttributeType name is valid or not. An Attribute name is valid if 569 * it's a descr / numericoid, as described in rfc4512 : 570 * <pre> 571 * name = descr / numericOid 572 * descr = keystring 573 * keystring = leadkeychar *keychar 574 * leadkeychar = ALPHA 575 * keychar = ALPHA / DIGIT / HYPHEN / USCORE 576 * numericoid = number 1*( DOT number ) 577 * number = DIGIT / ( LDIGIT 1*DIGIT ) 578 * ALPHA = %x41-5A / %x61-7A ; "A"-"Z" / "a"-"z" 579 * DIGIT = %x30 / LDIGIT ; "0"-"9" 580 * HYPHEN = %x2D ; hyphen ("-") 581 * LDIGIT = %x31-39 ; "1"-"9" 582 * DOT = %x2E ; period (".") 583 * USCORE = %x5F ; underscore ("_") 584 * </pre> 585 * 586 * Note that we have extended this grammar to accept the '_' char, which is widely used in teh LDAP world. 587 * 588 * @param attributeName The AttributeType name to check 589 * @return true if it's valid 590 */ 591 public static boolean isAttributeNameValid( String attributeName ) 592 { 593 if ( Strings.isEmpty( attributeName ) ) 594 { 595 return false; 596 } 597 598 // Check the first char which must be ALPHA or DIGIT 599 boolean descr; 600 boolean zero = false; 601 boolean dot = false; 602 603 char c = attributeName.charAt( 0 ); 604 605 if ( ( ( c >= 'a' ) && ( c <= 'z' ) ) || ( ( c >= 'A' ) && ( c <= 'Z' ) ) ) 606 { 607 descr = true; 608 } 609 else if ( ( c >= '0' ) && ( c <= '9' ) ) 610 { 611 descr = false; 612 613 zero = c == '0'; 614 } 615 else 616 { 617 return false; 618 } 619 620 for ( int i = 1; i < attributeName.length(); i++ ) 621 { 622 c = attributeName.charAt( i ); 623 624 if ( descr ) 625 { 626 // This is a descr, iterate on KeyChars (ALPHA / DIGIT / HYPHEN / USCORE) 627 if ( ( ( c < 'a' ) || ( c > 'z' ) ) 628 && ( ( c < 'A' ) || ( c > 'Z' ) ) 629 && ( ( c < '0' ) || ( c > '9' ) ) 630 && ( c != '-' ) 631 && ( c != '_' ) ) 632 { 633 return false; 634 } 635 } 636 else 637 { 638 // This is a numericOid, check it 639 if ( c == '.' ) 640 { 641 // Not allowed if we already have had a dot 642 if ( dot ) 643 { 644 return false; 645 } 646 647 dot = true; 648 zero = false; 649 } 650 else if ( ( c >= '0' ) && ( c <= '9' ) ) 651 { 652 dot = false; 653 654 if ( zero ) 655 { 656 // We can't have a leading '0' followed by another number 657 return false; 658 } 659 else if ( c == '0' ) 660 { 661 zero = true; 662 } 663 } 664 else 665 { 666 // Not valid 667 return false; 668 } 669 } 670 } 671 672 return true; 673 } 674}