001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.core; 028 029 030 031 import java.io.File; 032 import java.io.FileInputStream; 033 import java.io.FileOutputStream; 034 import java.util.ArrayList; 035 import java.util.LinkedHashMap; 036 import java.util.LinkedHashSet; 037 import java.util.Map; 038 import java.util.concurrent.ConcurrentHashMap; 039 import java.util.concurrent.atomic.AtomicInteger; 040 041 import org.opends.messages.Message; 042 import org.opends.server.api.CompressedSchema; 043 import org.opends.server.loggers.debug.DebugTracer; 044 import org.opends.server.protocols.asn1.ASN1Element; 045 import org.opends.server.protocols.asn1.ASN1Integer; 046 import org.opends.server.protocols.asn1.ASN1OctetString; 047 import org.opends.server.protocols.asn1.ASN1Reader; 048 import org.opends.server.protocols.asn1.ASN1Sequence; 049 import org.opends.server.protocols.asn1.ASN1Writer; 050 import org.opends.server.types.Attribute; 051 import org.opends.server.types.AttributeType; 052 import org.opends.server.types.AttributeValue; 053 import org.opends.server.types.ByteArray; 054 import org.opends.server.types.DebugLogLevel; 055 import org.opends.server.types.DirectoryException; 056 import org.opends.server.types.ObjectClass; 057 058 import static org.opends.server.config.ConfigConstants.*; 059 import static org.opends.server.loggers.debug.DebugLogger.*; 060 import static org.opends.messages.CoreMessages.*; 061 import static org.opends.server.util.StaticUtils.*; 062 063 064 065 /** 066 * This class provides a default implementation of a compressed schema manager 067 * that will store the schema definitions in a binary file 068 * (config/schematokens.dat). 069 */ 070 public final class DefaultCompressedSchema 071 extends CompressedSchema 072 { 073 /** 074 * The tracer object for the debug logger. 075 */ 076 private static final DebugTracer TRACER = getTracer(); 077 078 079 080 // The counter used for attribute descriptions. 081 private AtomicInteger adCounter; 082 083 // The counter used for object class sets. 084 private AtomicInteger ocCounter; 085 086 // The map between encoded representations and attribute types. 087 private ConcurrentHashMap<ByteArray,AttributeType> atDecodeMap; 088 089 // The map between encoded representations and attribute options. 090 private ConcurrentHashMap<ByteArray,LinkedHashSet<String>> aoDecodeMap; 091 092 // The map between encoded representations and object class sets. 093 private ConcurrentHashMap<ByteArray,Map<ObjectClass,String>> ocDecodeMap; 094 095 // The map between attribute descriptions and their encoded 096 // representations. 097 private ConcurrentHashMap<AttributeType, 098 ConcurrentHashMap<LinkedHashSet<String>,ByteArray>> adEncodeMap; 099 100 // The map between object class sets and encoded representations. 101 private ConcurrentHashMap<Map<ObjectClass,String>,ByteArray> ocEncodeMap; 102 103 104 105 /** 106 * Creates a new instance of this compressed schema manager. 107 */ 108 public DefaultCompressedSchema() 109 { 110 atDecodeMap = new ConcurrentHashMap<ByteArray,AttributeType>(); 111 aoDecodeMap = new ConcurrentHashMap<ByteArray,LinkedHashSet<String>>(); 112 ocDecodeMap = new ConcurrentHashMap<ByteArray,Map<ObjectClass,String>>(); 113 adEncodeMap = 114 new ConcurrentHashMap<AttributeType, 115 ConcurrentHashMap<LinkedHashSet<String>,ByteArray>>(); 116 ocEncodeMap = new ConcurrentHashMap<Map<ObjectClass,String>,ByteArray>(); 117 118 adCounter = new AtomicInteger(1); 119 ocCounter = new AtomicInteger(1); 120 121 load(); 122 } 123 124 125 126 /** 127 * Loads the compressed schema information from disk. 128 */ 129 private void load() 130 { 131 ASN1Reader reader = null; 132 133 try 134 { 135 // Determine the location of the compressed schema data file. It should 136 // be in the config directory with a name of "schematokens.dat". If that 137 // file doesn't exist, then don't do anything. 138 String path = DirectoryServer.getServerRoot() + File.separator + 139 CONFIG_DIR_NAME + File.separator + 140 COMPRESSED_SCHEMA_FILE_NAME; 141 if (! new File(path).exists()) 142 { 143 return; 144 } 145 FileInputStream inputStream = new FileInputStream(path); 146 reader = new ASN1Reader(inputStream); 147 148 149 // The first element in the file should be a sequence of object class 150 // sets. Each object class set will itself be a sequence of octet 151 // strings, where the first one is the token and the remaining elements 152 // are the names of the associated object classes. 153 ASN1Sequence ocSequence = reader.readElement().decodeAsSequence(); 154 for (ASN1Element element : ocSequence.elements()) 155 { 156 ArrayList<ASN1Element> elements = element.decodeAsSequence().elements(); 157 ASN1OctetString os = elements.get(0).decodeAsOctetString(); 158 ByteArray token = new ByteArray(os.value()); 159 160 LinkedHashMap<ObjectClass,String> ocMap = 161 new LinkedHashMap<ObjectClass,String>(elements.size()-1); 162 for (int i=1; i < elements.size(); i++) 163 { 164 os = elements.get(i).decodeAsOctetString(); 165 String ocName = os.stringValue(); 166 String lowerName = toLowerCase(ocName); 167 ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true); 168 ocMap.put(oc, ocName); 169 } 170 171 ocEncodeMap.put(ocMap, token); 172 ocDecodeMap.put(token, ocMap); 173 } 174 175 176 // The second element in the file should be an integer element that holds 177 // the value to use to initialize the object class counter. 178 ASN1Element counterElement = reader.readElement(); 179 ocCounter.set(counterElement.decodeAsInteger().intValue()); 180 181 182 // The third element in the file should be a sequence of attribute 183 // description components. Each attribute description component will 184 // itself be a sequence of octet strings, where the first one is the 185 // token, the second is the attribute name, and all remaining elements are 186 // the attribute options. 187 ASN1Sequence adSequence = reader.readElement().decodeAsSequence(); 188 for (ASN1Element element : adSequence.elements()) 189 { 190 ArrayList<ASN1Element> elements = element.decodeAsSequence().elements(); 191 ASN1OctetString os = elements. get(0).decodeAsOctetString(); 192 ByteArray token = new ByteArray(os.value()); 193 194 os = elements.get(1).decodeAsOctetString(); 195 String attrName = os.stringValue(); 196 String lowerName = toLowerCase(attrName); 197 AttributeType attrType = 198 DirectoryServer.getAttributeType(lowerName, true); 199 200 LinkedHashSet<String> options = 201 new LinkedHashSet<String>(elements.size()-2); 202 for (int i=2; i < elements.size(); i++) 203 { 204 os = elements.get(i).decodeAsOctetString(); 205 options.add(os.stringValue()); 206 } 207 208 atDecodeMap.put(token, attrType); 209 aoDecodeMap.put(token, options); 210 211 ConcurrentHashMap<LinkedHashSet<String>,ByteArray> map = 212 adEncodeMap.get(attrType); 213 if (map == null) 214 { 215 map = new ConcurrentHashMap<LinkedHashSet<String>,ByteArray>(1); 216 map.put(options, token); 217 adEncodeMap.put(attrType, map); 218 } 219 else 220 { 221 map.put(options, token); 222 } 223 } 224 225 226 // The fourth element in the file should be an integer element that holds 227 // the value to use to initialize the attribute description counter. 228 counterElement = reader.readElement(); 229 adCounter.set(counterElement.decodeAsInteger().intValue()); 230 } 231 catch (Exception e) 232 { 233 if (debugEnabled()) 234 { 235 TRACER.debugCaught(DebugLogLevel.ERROR, e); 236 } 237 238 // FIXME -- Should we do something else here? 239 throw new RuntimeException(e); 240 } 241 finally 242 { 243 try 244 { 245 if (reader != null) 246 { 247 reader.close(); 248 } 249 } 250 catch (Exception e) 251 { 252 if (debugEnabled()) 253 { 254 TRACER.debugCaught(DebugLogLevel.ERROR, e); 255 } 256 } 257 } 258 } 259 260 261 262 /** 263 * Writes the compressed schema information to disk. 264 * 265 * @throws DirectoryException If a problem occurs while writing the updated 266 * information. 267 */ 268 private void save() 269 throws DirectoryException 270 { 271 ASN1Writer writer = null; 272 try 273 { 274 // Determine the location of the "live" compressed schema data file, and 275 // then append ".tmp" to get the name of the temporary file that we will 276 // use. 277 String path = DirectoryServer.getServerRoot() + File.separator + 278 CONFIG_DIR_NAME + File.separator + 279 COMPRESSED_SCHEMA_FILE_NAME; 280 String tempPath = path + ".tmp"; 281 282 FileOutputStream outputStream = new FileOutputStream(tempPath); 283 writer = new ASN1Writer(outputStream); 284 285 286 // The first element in the file should be a sequence of object class 287 // sets. Each object class set will itself be a sequence of octet 288 // strings, where the first one is the token and the remaining elements 289 // are the names of the associated object classes. 290 ArrayList<ASN1Element> ocElements = 291 new ArrayList<ASN1Element>(ocDecodeMap.size()); 292 for (Map.Entry<ByteArray,Map<ObjectClass,String>> mapEntry : 293 ocDecodeMap.entrySet()) 294 { 295 ByteArray token = mapEntry.getKey(); 296 Map<ObjectClass,String> ocMap = mapEntry.getValue(); 297 298 ArrayList<ASN1Element> elements = 299 new ArrayList<ASN1Element>(ocMap.size()+1); 300 elements.add(new ASN1OctetString(token.array())); 301 302 for (String ocName : ocMap.values()) 303 { 304 elements.add(new ASN1OctetString(ocName)); 305 } 306 307 ocElements.add(new ASN1Sequence(elements)); 308 } 309 writer.writeElement(new ASN1Sequence(ocElements)); 310 311 312 // The second element in the file should be an integer element that holds 313 // the value to use to initialize the object class counter. 314 writer.writeElement(new ASN1Integer(ocCounter.get())); 315 316 317 // The third element in the file should be a sequence of attribute 318 // description components. Each attribute description component will 319 // itself be a sequence of octet strings, where the first one is the 320 // token, the second is the attribute name, and all remaining elements are 321 // the attribute options. 322 ArrayList<ASN1Element> adElements = 323 new ArrayList<ASN1Element>(atDecodeMap.size()); 324 for (ByteArray token : atDecodeMap.keySet()) 325 { 326 AttributeType attrType = atDecodeMap.get(token); 327 LinkedHashSet<String> options = aoDecodeMap.get(token); 328 329 ArrayList<ASN1Element> elements = 330 new ArrayList<ASN1Element>(options.size()+2); 331 elements.add(new ASN1OctetString(token.array())); 332 elements.add(new ASN1OctetString(attrType.getNameOrOID())); 333 for (String option : options) 334 { 335 elements.add(new ASN1OctetString(option)); 336 } 337 338 adElements.add(new ASN1Sequence(elements)); 339 } 340 writer.writeElement(new ASN1Sequence(adElements)); 341 342 343 // The fourth element in the file should be an integer element that holds 344 // the value to use to initialize the attribute description counter. 345 writer.writeElement(new ASN1Integer(adCounter.get())); 346 347 348 // Close the writer and swing the temp file into place. 349 writer.close(); 350 File liveFile = new File(path); 351 File tempFile = new File(tempPath); 352 353 if (liveFile.exists()) 354 { 355 File saveFile = new File(liveFile.getAbsolutePath() + ".save"); 356 if (saveFile.exists()) 357 { 358 saveFile.delete(); 359 } 360 liveFile.renameTo(saveFile); 361 } 362 tempFile.renameTo(liveFile); 363 } 364 catch (Exception e) 365 { 366 if (debugEnabled()) 367 { 368 TRACER.debugCaught(DebugLogLevel.ERROR, e); 369 } 370 371 Message message = ERR_COMPRESSEDSCHEMA_CANNOT_WRITE_UPDATED_DATA.get( 372 stackTraceToSingleLineString(e)); 373 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 374 message, e); 375 } 376 finally 377 { 378 try 379 { 380 if (writer != null) 381 { 382 writer.close(); 383 } 384 } 385 catch (Exception e) 386 { 387 if (debugEnabled()) 388 { 389 TRACER.debugCaught(DebugLogLevel.ERROR, e); 390 } 391 } 392 } 393 } 394 395 396 397 /** 398 * {@inheritDoc} 399 */ 400 @Override() 401 public byte[] encodeObjectClasses(Map<ObjectClass,String> objectClasses) 402 throws DirectoryException 403 { 404 ByteArray encodedClasses = ocEncodeMap.get(objectClasses); 405 if (encodedClasses == null) 406 { 407 synchronized (ocEncodeMap) 408 { 409 int setValue = ocCounter.getAndIncrement(); 410 byte[] array = encodeInt(setValue); 411 412 encodedClasses = new ByteArray(array); 413 ocEncodeMap.put(objectClasses, encodedClasses); 414 ocDecodeMap.put(encodedClasses, objectClasses); 415 416 save(); 417 return array; 418 } 419 } 420 else 421 { 422 return encodedClasses.array(); 423 } 424 } 425 426 427 428 /** 429 * {@inheritDoc} 430 */ 431 @Override() 432 public Map<ObjectClass,String> decodeObjectClasses( 433 byte[] encodedObjectClasses) 434 throws DirectoryException 435 { 436 ByteArray byteArray = new ByteArray(encodedObjectClasses); 437 Map<ObjectClass,String> ocMap = ocDecodeMap.get(byteArray); 438 if (ocMap == null) 439 { 440 Message message = ERR_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN.get( 441 bytesToHex(encodedObjectClasses)); 442 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 443 message); 444 } 445 else 446 { 447 return ocMap; 448 } 449 } 450 451 452 453 /** 454 * {@inheritDoc} 455 */ 456 @Override() 457 public byte[] encodeAttribute(Attribute attribute) 458 throws DirectoryException 459 { 460 AttributeType type = attribute.getAttributeType(); 461 LinkedHashSet<String> options = attribute.getOptions(); 462 463 ConcurrentHashMap<LinkedHashSet<String>,ByteArray> map = 464 adEncodeMap.get(type); 465 if (map == null) 466 { 467 byte[] array; 468 synchronized (adEncodeMap) 469 { 470 map = new ConcurrentHashMap<LinkedHashSet<String>,ByteArray>(1); 471 472 int intValue = adCounter.getAndIncrement(); 473 array = encodeInt(intValue); 474 ByteArray byteArray = new ByteArray(array); 475 map.put(options,byteArray); 476 477 adEncodeMap.put(type, map); 478 atDecodeMap.put(byteArray, type); 479 aoDecodeMap.put(byteArray, options); 480 save(); 481 } 482 483 return encodeAttribute(array, attribute); 484 } 485 else 486 { 487 ByteArray byteArray = map.get(options); 488 if (byteArray == null) 489 { 490 byte[] array; 491 synchronized (map) 492 { 493 int intValue = adCounter.getAndIncrement(); 494 array = encodeInt(intValue); 495 byteArray = new ByteArray(array); 496 map.put(options,byteArray); 497 498 atDecodeMap.put(byteArray, type); 499 aoDecodeMap.put(byteArray, options); 500 save(); 501 } 502 503 return encodeAttribute(array, attribute); 504 } 505 else 506 { 507 return encodeAttribute(byteArray.array(), attribute); 508 } 509 } 510 } 511 512 513 514 /** 515 * Encodes the information in the provided attribute to a byte 516 * array. 517 * 518 * @param adArray The byte array that is a placeholder for the 519 * attribute type and set of options. 520 * @param attribute The attribute to be encoded. 521 * 522 * @return An encoded representation of the provided attribute. 523 */ 524 private byte[] encodeAttribute(byte[] adArray, Attribute attribute) 525 { 526 LinkedHashSet<AttributeValue> values = attribute.getValues(); 527 int totalValuesLength = 0; 528 byte[][] subArrays = new byte[values.size()*2][]; 529 int pos = 0; 530 for (AttributeValue v : values) 531 { 532 byte[] vBytes = v.getValueBytes(); 533 byte[] lBytes = ASN1Element.encodeLength(vBytes.length); 534 535 subArrays[pos++] = lBytes; 536 subArrays[pos++] = vBytes; 537 538 totalValuesLength += lBytes.length + vBytes.length; 539 } 540 541 byte[] adArrayLength = ASN1Element.encodeLength(adArray.length); 542 byte[] countBytes = ASN1Element.encodeLength(values.size()); 543 int totalLength = adArrayLength.length + adArray.length + 544 countBytes.length + totalValuesLength; 545 byte[] array = new byte[totalLength]; 546 547 System.arraycopy(adArrayLength, 0, array, 0, 548 adArrayLength.length); 549 pos = adArrayLength.length; 550 System.arraycopy(adArray, 0, array, pos, adArray.length); 551 pos += adArray.length; 552 System.arraycopy(countBytes, 0, array, pos, countBytes.length); 553 pos += countBytes.length; 554 555 for (int i=0; i < subArrays.length; i++) 556 { 557 System.arraycopy(subArrays[i], 0, array, pos, 558 subArrays[i].length); 559 pos += subArrays[i].length; 560 } 561 562 return array; 563 } 564 565 566 567 /** 568 * {@inheritDoc} 569 */ 570 @Override() 571 public Attribute decodeAttribute(byte[] encodedEntry, int startPos, 572 int length) 573 throws DirectoryException 574 { 575 // Figure out how many bytes are in the token that is the placeholder for 576 // the attribute description. 577 int pos = startPos; 578 int adArrayLength = encodedEntry[pos] & 0x7F; 579 if (adArrayLength != encodedEntry[pos++]) 580 { 581 int numLengthBytes = adArrayLength; 582 adArrayLength = 0; 583 for (int i=0; i < numLengthBytes; i++, pos++) 584 { 585 adArrayLength = (adArrayLength << 8) | (encodedEntry[pos] & 0xFF); 586 } 587 } 588 589 590 // Get the attribute description token and make sure it resolves to an 591 // attribute type and option set. 592 ByteArray adArray = new ByteArray(new byte[adArrayLength]); 593 System.arraycopy(encodedEntry, pos, adArray.array(), 0, adArrayLength); 594 pos += adArrayLength; 595 AttributeType attrType = atDecodeMap.get(adArray); 596 LinkedHashSet<String> options = aoDecodeMap.get(adArray); 597 if ((attrType == null) || (options == null)) 598 { 599 Message message = ERR_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN.get( 600 bytesToHex(adArray.array())); 601 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 602 message); 603 } 604 605 606 // Determine the number of values for the attribute. 607 int numValues = encodedEntry[pos] & 0x7F; 608 if (numValues != encodedEntry[pos++]) 609 { 610 int numValuesBytes = numValues; 611 numValues = 0; 612 for (int i=0; i < numValuesBytes; i++, pos++) 613 { 614 numValues = (numValues << 8) | (encodedEntry[pos] & 0xFF); 615 } 616 } 617 618 619 // Read the appropriate number of values. 620 LinkedHashSet<AttributeValue> values = 621 new LinkedHashSet<AttributeValue>(numValues); 622 for (int i=0; i < numValues; i++) 623 { 624 int valueLength = encodedEntry[pos] & 0x7F; 625 if (valueLength != encodedEntry[pos++]) 626 { 627 int valueLengthBytes = valueLength; 628 valueLength = 0; 629 for (int j=0; j < valueLengthBytes; j++, pos++) 630 { 631 valueLength = (valueLength << 8) | (encodedEntry[pos] & 0xFF); 632 } 633 } 634 635 byte[] valueBytes = new byte[valueLength]; 636 System.arraycopy(encodedEntry, pos, valueBytes, 0, valueLength); 637 pos += valueLength; 638 values.add(new AttributeValue(attrType, new ASN1OctetString(valueBytes))); 639 } 640 641 return new Attribute(attrType, attrType.getPrimaryName(), options, values); 642 } 643 644 645 646 /** 647 * Encodes the provided int value to a byte array. 648 * 649 * @param intValue The int value to be encoded. 650 * 651 * @return The byte array containing the encoded int value. 652 */ 653 private byte[] encodeInt(int intValue) 654 { 655 byte[] array; 656 if (intValue <= 0xFF) 657 { 658 array = new byte[1]; 659 array[0] = (byte) (intValue & 0xFF); 660 } 661 else if (intValue <= 0xFFFF) 662 { 663 array = new byte[2]; 664 array[0] = (byte) ((intValue >> 8) & 0xFF); 665 array[1] = (byte) (intValue & 0xFF); 666 } 667 else if (intValue <= 0xFFFFFF) 668 { 669 array = new byte[3]; 670 array[0] = (byte) ((intValue >> 16) & 0xFF); 671 array[1] = (byte) ((intValue >> 8) & 0xFF); 672 array[2] = (byte) (intValue & 0xFF); 673 } 674 else 675 { 676 array = new byte[4]; 677 array[0] = (byte) ((intValue >> 24) & 0xFF); 678 array[1] = (byte) ((intValue >> 16) & 0xFF); 679 array[2] = (byte) ((intValue >> 8) & 0xFF); 680 array[3] = (byte) (intValue & 0xFF); 681 } 682 683 return array; 684 } 685 } 686