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