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 StringBuffer render( StringBuffer buf, List<String> qdescrs ) 144 { 145 if ( ( qdescrs == null ) || ( qdescrs.size() == 0 ) ) 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 StringBuffer renderQDescrs( StringBuffer buf, List<String> qdescrs ) 180 { 181 if ( ( qdescrs == null ) || ( qdescrs.size() == 0 ) ) 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 StringBuffer renderQDString( StringBuffer 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 StringBuffer render( ObjectClass[] ocs ) 253 { 254 StringBuffer buf = new StringBuffer(); 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 StringBuffer render( StringBuffer 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 StringBuffer render( AttributeType[] ats ) 316 { 317 StringBuffer buf = new StringBuffer(); 318 return render( buf, ats ); 319 } 320 321 322 /** 323 * Renders a list of attributeTypes for things like the must or may list of 324 * objectClasses using the ( oid $ oid ) format into an existing buffer. 325 * 326 * @param buf 327 * the string buffer to render the list of attributeTypes into 328 * @param ats 329 * the attributeTypes to list 330 * @return a buffer which contains the rendered list 331 */ 332 public static StringBuffer render( StringBuffer buf, AttributeType[] ats ) 333 { 334 if ( ats == null || ats.length == 0 ) 335 { 336 return buf; 337 } 338 else if ( ats.length == 1 ) 339 { 340 buf.append( ats[0].getName() ); 341 } 342 else 343 { 344 buf.append( "( " ); 345 for ( int ii = 0; ii < ats.length; ii++ ) 346 { 347 if ( ii + 1 < ats.length ) 348 { 349 buf.append( ats[ii].getName() ).append( " $ " ); 350 } 351 else 352 { 353 buf.append( ats[ii].getName() ); 354 } 355 } 356 buf.append( " )" ); 357 } 358 359 return buf; 360 } 361 362 363 // ------------------------------------------------------------------------ 364 // schema object rendering operations 365 // ------------------------------------------------------------------------ 366 367 368 /** 369 * Renders the schema extensions into a new StringBuffer. 370 * 371 * @param extensions the schema extensions map with key and values 372 * @return a StringBuffer with the extensions component of a syntax description 373 */ 374 public static StringBuffer render( Map<String, List<String>> extensions ) 375 { 376 StringBuffer buf = new StringBuffer(); 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 423 /** 424 * Returns a String description of a schema. The resulting String format is : 425 * <br> 426 * (OID [DESC '<description>'] FQCN <fcqn> [BYTECODE <bytecode>] X-SCHEMA '<schema>') 427 * <br> 428 * @param description The description to transform to a String 429 * @return 430 */ 431 public static String render( LoadableSchemaObject description ) 432 { 433 StringBuffer buf = new StringBuffer(); 434 buf.append( "( " ).append( description.getOid() ); 435 436 if ( description.getDescription() != null ) 437 { 438 buf.append( " DESC " ); 439 renderQDString( buf, description.getDescription() ); 440 } 441 442 buf.append( " FQCN " ).append( description.getFqcn() ); 443 444 if ( !Strings.isEmpty( description.getBytecode() ) ) 445 { 446 buf.append( " BYTECODE " ).append( description.getBytecode() ); 447 } 448 449 buf.append( " X-SCHEMA '" ); 450 buf.append( getSchemaName( description ) ); 451 buf.append( "' )" ); 452 453 return buf.toString(); 454 } 455 456 457 private static String getSchemaName( SchemaObject desc ) 458 { 459 List<String> values = desc.getExtensions().get( MetaSchemaConstants.X_SCHEMA_AT ); 460 461 if ( values == null || values.size() == 0 ) 462 { 463 return MetaSchemaConstants.SCHEMA_OTHER; 464 } 465 466 return values.get( 0 ); 467 } 468 469 470 /** 471 * Remove the options from the attributeType, and returns the ID. 472 * 473 * RFC 4512 : 474 * attributedescription = attributetype options 475 * attributetype = oid 476 * options = *( SEMI option ) 477 * option = 1*keychar 478 */ 479 public static String stripOptions( String attributeId ) 480 { 481 int optionsPos = attributeId.indexOf( ';' ); 482 483 if ( optionsPos != -1 ) 484 { 485 return attributeId.substring( 0, optionsPos ); 486 } 487 else 488 { 489 return attributeId; 490 } 491 } 492 493 494 /** 495 * Get the options from the attributeType. 496 * 497 * For instance, given : 498 * jpegphoto;binary;lang=jp 499 * 500 * your get back a set containing { "binary", "lang=jp" } 501 */ 502 public static Set<String> getOptions( String attributeId ) 503 { 504 int optionsPos = attributeId.indexOf( ';' ); 505 506 if ( optionsPos != -1 ) 507 { 508 Set<String> options = new HashSet<String>(); 509 510 String[] res = attributeId.substring( optionsPos + 1 ).split( ";" ); 511 512 for ( String option : res ) 513 { 514 if ( !Strings.isEmpty( option ) ) 515 { 516 options.add( option ); 517 } 518 } 519 520 return options; 521 } 522 else 523 { 524 return null; 525 } 526 } 527 528 529 /** 530 * Transform an UUID in a byte array 531 * @param uuid The UUID to transform 532 * @return The byte[] representing the UUID 533 */ 534 public static byte[] uuidToBytes( UUID uuid ) 535 { 536 Long low = uuid.getLeastSignificantBits(); 537 Long high = uuid.getMostSignificantBits(); 538 byte[] bytes = new byte[16]; 539 540 bytes[0] = ( byte ) ( ( high & 0xff00000000000000L ) >> 56 ); 541 bytes[1] = ( byte ) ( ( high & 0x00ff000000000000L ) >> 48 ); 542 bytes[2] = ( byte ) ( ( high & 0x0000ff0000000000L ) >> 40 ); 543 bytes[3] = ( byte ) ( ( high & 0x000000ff00000000L ) >> 32 ); 544 bytes[4] = ( byte ) ( ( high & 0x00000000ff000000L ) >> 24 ); 545 bytes[5] = ( byte ) ( ( high & 0x0000000000ff0000L ) >> 16 ); 546 bytes[6] = ( byte ) ( ( high & 0x000000000000ff00L ) >> 8 ); 547 bytes[7] = ( byte ) ( high & 0x00000000000000ffL ); 548 bytes[8] = ( byte ) ( ( low & 0xff00000000000000L ) >> 56 ); 549 bytes[9] = ( byte ) ( ( low & 0x00ff000000000000L ) >> 48 ); 550 bytes[10] = ( byte ) ( ( low & 0x0000ff0000000000L ) >> 40 ); 551 bytes[11] = ( byte ) ( ( low & 0x000000ff00000000L ) >> 32 ); 552 bytes[12] = ( byte ) ( ( low & 0x00000000ff000000L ) >> 24 ); 553 bytes[13] = ( byte ) ( ( low & 0x0000000000ff0000L ) >> 16 ); 554 bytes[14] = ( byte ) ( ( low & 0x000000000000ff00L ) >> 8 ); 555 bytes[15] = ( byte ) ( low & 0x00000000000000ffL ); 556 557 return bytes; 558 } 559}