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.backends.jeb;
028
029
030
031 import java.util.ArrayList;
032 import java.util.LinkedHashMap;
033 import java.util.LinkedHashSet;
034 import java.util.Map;
035 import java.util.concurrent.ConcurrentHashMap;
036 import java.util.concurrent.atomic.AtomicInteger;
037
038 import com.sleepycat.je.Cursor;
039 import com.sleepycat.je.Database;
040 import com.sleepycat.je.DatabaseConfig;
041 import com.sleepycat.je.DatabaseEntry;
042 import com.sleepycat.je.DatabaseException;
043 import com.sleepycat.je.DeadlockException;
044 import com.sleepycat.je.Environment;
045 import com.sleepycat.je.LockMode;
046 import com.sleepycat.je.OperationStatus;
047
048 import org.opends.messages.Message;
049 import org.opends.server.api.CompressedSchema;
050 import org.opends.server.core.DirectoryServer;
051 import org.opends.server.loggers.debug.DebugTracer;
052 import org.opends.server.protocols.asn1.ASN1Element;
053 import org.opends.server.protocols.asn1.ASN1Exception;
054 import org.opends.server.protocols.asn1.ASN1OctetString;
055 import org.opends.server.protocols.asn1.ASN1Sequence;
056 import org.opends.server.types.Attribute;
057 import org.opends.server.types.AttributeType;
058 import org.opends.server.types.AttributeValue;
059 import org.opends.server.types.ByteArray;
060 import org.opends.server.types.DebugLogLevel;
061 import org.opends.server.types.DirectoryException;
062 import org.opends.server.types.ObjectClass;
063
064 import static org.opends.server.config.ConfigConstants.*;
065 import static org.opends.server.loggers.debug.DebugLogger.*;
066 import static org.opends.messages.JebMessages.*;
067 import static org.opends.server.util.StaticUtils.*;
068
069
070
071 /**
072 * This class provides a compressed schema implementation whose definitions are
073 * stored in a Berkeley DB JE database.
074 */
075 public final class JECompressedSchema
076 extends CompressedSchema
077 {
078 /**
079 * The tracer object for the debug logger.
080 */
081 private static final DebugTracer TRACER = getTracer();
082
083
084
085 /**
086 * The name of the database used to store compressed attribute description
087 * definitions.
088 */
089 public static final String DB_NAME_AD = "compressed_attributes";
090
091
092
093 /**
094 * The name of the database used to store compressed object class set
095 * definitions.
096 */
097 public static final String DB_NAME_OC = "compressed_object_classes";
098
099
100
101 // The counter used for attribute descriptions.
102 private AtomicInteger adCounter;
103
104 // The counter used for object class sets.
105 private AtomicInteger ocCounter;
106
107 // The map between encoded representations and attribute types.
108 private ConcurrentHashMap<ByteArray,AttributeType> atDecodeMap;
109
110 // The map between encoded representations and attribute options.
111 private ConcurrentHashMap<ByteArray,LinkedHashSet<String>> aoDecodeMap;
112
113 // The map between encoded representations and object class sets.
114 private ConcurrentHashMap<ByteArray,Map<ObjectClass,String>> ocDecodeMap;
115
116 // The map between attribute descriptions and their encoded
117 // representations.
118 private ConcurrentHashMap<AttributeType,
119 ConcurrentHashMap<LinkedHashSet<String>,ByteArray>> adEncodeMap;
120
121 // The map between object class sets and encoded representations.
122 private ConcurrentHashMap<Map<ObjectClass,String>,ByteArray> ocEncodeMap;
123
124 // The compressed attribute description schema database.
125 private Database adDatabase;
126
127 // The compresesd object class set schema database.
128 private Database ocDatabase;
129
130 // The environment in which the databases are held.
131 private Environment environment;
132
133
134
135 /**
136 * Creates a new instance of this JE compressed schema manager.
137 *
138 * @param environment A reference to the database environment in which the
139 * databases will be held.
140 *
141 * @throws DatabaseException If a problem occurs while loading the
142 * compressed schema definitions from the
143 * database.
144 */
145 public JECompressedSchema(Environment environment)
146 throws DatabaseException
147 {
148 this.environment = environment;
149
150 atDecodeMap = new ConcurrentHashMap<ByteArray,AttributeType>();
151 aoDecodeMap = new ConcurrentHashMap<ByteArray,LinkedHashSet<String>>();
152 ocDecodeMap = new ConcurrentHashMap<ByteArray,Map<ObjectClass,String>>();
153 adEncodeMap =
154 new ConcurrentHashMap<AttributeType,
155 ConcurrentHashMap<LinkedHashSet<String>,ByteArray>>();
156 ocEncodeMap = new ConcurrentHashMap<Map<ObjectClass,String>,ByteArray>();
157
158 adCounter = new AtomicInteger(1);
159 ocCounter = new AtomicInteger(1);
160
161 load();
162 }
163
164
165
166 /**
167 * Loads the compressed schema information from the database.
168 *
169 * @throws DatabaseException If a problem occurs while loading the
170 * definitions from the database.
171 */
172 private void load()
173 throws DatabaseException
174 {
175 DatabaseConfig dbConfig = new DatabaseConfig();
176
177 if(environment.getConfig().getReadOnly())
178 {
179 dbConfig.setReadOnly(true);
180 dbConfig.setAllowCreate(false);
181 dbConfig.setTransactional(false);
182 }
183 else if(!environment.getConfig().getTransactional())
184 {
185 dbConfig.setAllowCreate(true);
186 dbConfig.setTransactional(false);
187 dbConfig.setDeferredWrite(true);
188 }
189 else
190 {
191 dbConfig.setAllowCreate(true);
192 dbConfig.setTransactional(true);
193 }
194
195 adDatabase = environment.openDatabase(null, DB_NAME_AD, dbConfig);
196 ocDatabase = environment.openDatabase(null, DB_NAME_OC, dbConfig);
197
198 // Cursor through the object class database and load the object class set
199 // definitions. At the same time, figure out the highest token value and
200 // initialize the object class counter to one greater than that.
201 Cursor ocCursor = ocDatabase.openCursor(null, null);
202 int highestToken = 0;
203
204 try
205 {
206 DatabaseEntry keyEntry = new DatabaseEntry();
207 DatabaseEntry valueEntry = new DatabaseEntry();
208 OperationStatus status = ocCursor.getFirst(keyEntry, valueEntry,
209 LockMode.READ_UNCOMMITTED);
210 while (status == OperationStatus.SUCCESS)
211 {
212 ByteArray token = new ByteArray(keyEntry.getData());
213 highestToken = Math.max(highestToken, decodeInt(token.array()));
214
215 ArrayList<ASN1Element> elements =
216 ASN1Sequence.decodeAsSequence(valueEntry.getData()).elements();
217 LinkedHashMap<ObjectClass,String> ocMap =
218 new LinkedHashMap<ObjectClass,String>(elements.size());
219 for (int i=0; i < elements.size(); i++)
220 {
221 ASN1OctetString os = elements.get(i).decodeAsOctetString();
222 String ocName = os.stringValue();
223 String lowerName = toLowerCase(ocName);
224 ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true);
225 ocMap.put(oc, ocName);
226 }
227
228 ocEncodeMap.put(ocMap, token);
229 ocDecodeMap.put(token, ocMap);
230
231 status = ocCursor.getNext(keyEntry, valueEntry,
232 LockMode.READ_UNCOMMITTED);
233 }
234 }
235 catch (ASN1Exception ae)
236 {
237 if (debugEnabled())
238 {
239 TRACER.debugCaught(DebugLogLevel.ERROR, ae);
240 }
241
242 Message m =
243 ERR_JEB_COMPSCHEMA_CANNOT_DECODE_OC_TOKEN.get(ae.getMessage());
244 throw new DatabaseException(m.toString(), ae);
245 }
246 finally
247 {
248 ocCursor.close();
249 }
250
251 ocCounter.set(highestToken+1);
252
253
254 // Cursor through the attribute description database and load the attribute
255 // set definitions.
256 Cursor adCursor = adDatabase.openCursor(null, null);
257 highestToken = 0;
258
259 try
260 {
261 DatabaseEntry keyEntry = new DatabaseEntry();
262 DatabaseEntry valueEntry = new DatabaseEntry();
263 OperationStatus status = adCursor.getFirst(keyEntry, valueEntry,
264 LockMode.READ_UNCOMMITTED);
265 while (status == OperationStatus.SUCCESS)
266 {
267 ByteArray token = new ByteArray(keyEntry.getData());
268 highestToken = Math.max(highestToken, decodeInt(token.array()));
269
270 ArrayList<ASN1Element> elements =
271 ASN1Sequence.decodeAsSequence(valueEntry.getData()).elements();
272
273 ASN1OctetString os = elements.get(0).decodeAsOctetString();
274 String attrName = os.stringValue();
275 String lowerName = toLowerCase(attrName);
276 AttributeType attrType =
277 DirectoryServer.getAttributeType(lowerName, true);
278
279 LinkedHashSet<String> options =
280 new LinkedHashSet<String>(elements.size()-1);
281 for (int i=1; i < elements.size(); i++)
282 {
283 os = elements.get(i).decodeAsOctetString();
284 options.add(os.stringValue());
285 }
286
287 atDecodeMap.put(token, attrType);
288 aoDecodeMap.put(token, options);
289
290 ConcurrentHashMap<LinkedHashSet<String>,ByteArray> map =
291 adEncodeMap.get(attrType);
292 if (map == null)
293 {
294 map = new ConcurrentHashMap<LinkedHashSet<String>,ByteArray>(1);
295 map.put(options, token);
296 adEncodeMap.put(attrType, map);
297 }
298 else
299 {
300 map.put(options, token);
301 }
302
303 status = adCursor.getNext(keyEntry, valueEntry,
304 LockMode.READ_UNCOMMITTED);
305 }
306 }
307 catch (ASN1Exception ae)
308 {
309 if (debugEnabled())
310 {
311 TRACER.debugCaught(DebugLogLevel.ERROR, ae);
312 }
313
314 Message m =
315 ERR_JEB_COMPSCHEMA_CANNOT_DECODE_AD_TOKEN.get(ae.getMessage());
316 throw new DatabaseException(m.toString(), ae);
317 }
318 finally
319 {
320 adCursor.close();
321 }
322
323 adCounter.set(highestToken+1);
324 }
325
326
327
328 /**
329 * Closes the databases and releases any resources held by this compressed
330 * schema manager.
331 */
332 public void close()
333 {
334 try
335 {
336 adDatabase.sync();
337 } catch (Exception e) {}
338
339 try
340 {
341 adDatabase.close();
342 } catch (Exception e) {}
343
344 try
345 {
346 ocDatabase.sync();
347 } catch (Exception e) {}
348
349 try
350 {
351 ocDatabase.close();
352 } catch (Exception e) {}
353
354 adDatabase = null;
355 ocDatabase = null;
356 environment = null;
357 atDecodeMap = null;
358 aoDecodeMap = null;
359 ocDecodeMap = null;
360 adEncodeMap = null;
361 ocEncodeMap = null;
362 adCounter = null;
363 ocCounter = null;
364 }
365
366
367
368 /**
369 * {@inheritDoc}
370 */
371 @Override()
372 public byte[] encodeObjectClasses(Map<ObjectClass,String> objectClasses)
373 throws DirectoryException
374 {
375 ByteArray encodedClasses = ocEncodeMap.get(objectClasses);
376 if (encodedClasses == null)
377 {
378 synchronized (ocEncodeMap)
379 {
380 int setValue = ocCounter.getAndIncrement();
381 byte[] tokenArray = encodeInt(setValue);
382
383 ArrayList<ASN1Element> elements =
384 new ArrayList<ASN1Element>(objectClasses.size());
385 for (String ocName : objectClasses.values())
386 {
387 elements.add(new ASN1OctetString(ocName));
388 }
389
390 byte[] encodedOCs = new ASN1Sequence(elements).encode();
391 store(ocDatabase, tokenArray, encodedOCs);
392
393 encodedClasses = new ByteArray(tokenArray);
394 ocEncodeMap.put(objectClasses, encodedClasses);
395 ocDecodeMap.put(encodedClasses, objectClasses);
396
397 return tokenArray;
398 }
399 }
400 else
401 {
402 return encodedClasses.array();
403 }
404 }
405
406
407
408 /**
409 * {@inheritDoc}
410 */
411 @Override()
412 public Map<ObjectClass,String> decodeObjectClasses(
413 byte[] encodedObjectClasses)
414 throws DirectoryException
415 {
416 ByteArray byteArray = new ByteArray(encodedObjectClasses);
417 Map<ObjectClass,String> ocMap = ocDecodeMap.get(byteArray);
418 if (ocMap == null)
419 {
420 Message message = ERR_JEB_COMPSCHEMA_UNKNOWN_OC_TOKEN.get(
421 bytesToHex(encodedObjectClasses));
422 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
423 message);
424 }
425 else
426 {
427 return ocMap;
428 }
429 }
430
431
432
433 /**
434 * {@inheritDoc}
435 */
436 @Override()
437 public byte[] encodeAttribute(Attribute attribute)
438 throws DirectoryException
439 {
440 AttributeType type = attribute.getAttributeType();
441 LinkedHashSet<String> options = attribute.getOptions();
442
443 ConcurrentHashMap<LinkedHashSet<String>,ByteArray> map =
444 adEncodeMap.get(type);
445 if (map == null)
446 {
447 byte[] tokenArray;
448 synchronized (adEncodeMap)
449 {
450 map = new ConcurrentHashMap<LinkedHashSet<String>,ByteArray>(1);
451
452 int intValue = adCounter.getAndIncrement();
453 tokenArray = encodeInt(intValue);
454 ByteArray byteArray = new ByteArray(tokenArray);
455 map.put(options,byteArray);
456
457 ArrayList<ASN1Element> elements =
458 new ArrayList<ASN1Element>(options.size()+1);
459 elements.add(new ASN1OctetString(attribute.getName()));
460 for (String option : options)
461 {
462 elements.add(new ASN1OctetString(option));
463 }
464 byte[] encodedValue = new ASN1Sequence(elements).encode();
465 store(adDatabase, tokenArray, encodedValue);
466
467 adEncodeMap.put(type, map);
468 atDecodeMap.put(byteArray, type);
469 aoDecodeMap.put(byteArray, options);
470 }
471
472 return encodeAttribute(tokenArray, attribute);
473 }
474 else
475 {
476 ByteArray byteArray = map.get(options);
477 if (byteArray == null)
478 {
479 byte[] tokenArray;
480 synchronized (map)
481 {
482 int intValue = adCounter.getAndIncrement();
483 tokenArray = encodeInt(intValue);
484 byteArray = new ByteArray(tokenArray);
485 map.put(options,byteArray);
486
487 ArrayList<ASN1Element> elements =
488 new ArrayList<ASN1Element>(options.size()+1);
489 elements.add(new ASN1OctetString(attribute.getName()));
490 for (String option : options)
491 {
492 elements.add(new ASN1OctetString(option));
493 }
494 byte[] encodedValue = new ASN1Sequence(elements).encode();
495 store(adDatabase, tokenArray, encodedValue);
496
497 atDecodeMap.put(byteArray, type);
498 aoDecodeMap.put(byteArray, options);
499 }
500
501 return encodeAttribute(tokenArray, attribute);
502 }
503 else
504 {
505 return encodeAttribute(byteArray.array(), attribute);
506 }
507 }
508 }
509
510
511
512 /**
513 * Encodes the information in the provided attribute to a byte
514 * array.
515 *
516 * @param adArray The byte array that is a placeholder for the
517 * attribute type and set of options.
518 * @param attribute The attribute to be encoded.
519 *
520 * @return An encoded representation of the provided attribute.
521 */
522 private byte[] encodeAttribute(byte[] adArray, Attribute attribute)
523 {
524 LinkedHashSet<AttributeValue> values = attribute.getValues();
525 int totalValuesLength = 0;
526 byte[][] subArrays = new byte[values.size()*2][];
527 int pos = 0;
528 for (AttributeValue v : values)
529 {
530 byte[] vBytes = v.getValueBytes();
531 byte[] lBytes = ASN1Element.encodeLength(vBytes.length);
532
533 subArrays[pos++] = lBytes;
534 subArrays[pos++] = vBytes;
535
536 totalValuesLength += lBytes.length + vBytes.length;
537 }
538
539 byte[] adArrayLength = ASN1Element.encodeLength(adArray.length);
540 byte[] countBytes = ASN1Element.encodeLength(values.size());
541 int totalLength = adArrayLength.length + adArray.length +
542 countBytes.length + totalValuesLength;
543 byte[] array = new byte[totalLength];
544
545 System.arraycopy(adArrayLength, 0, array, 0,
546 adArrayLength.length);
547 pos = adArrayLength.length;
548 System.arraycopy(adArray, 0, array, pos, adArray.length);
549 pos += adArray.length;
550 System.arraycopy(countBytes, 0, array, pos, countBytes.length);
551 pos += countBytes.length;
552
553 for (int i=0; i < subArrays.length; i++)
554 {
555 System.arraycopy(subArrays[i], 0, array, pos,
556 subArrays[i].length);
557 pos += subArrays[i].length;
558 }
559
560 return array;
561 }
562
563
564
565 /**
566 * {@inheritDoc}
567 */
568 @Override()
569 public Attribute decodeAttribute(byte[] encodedEntry, int startPos,
570 int length)
571 throws DirectoryException
572 {
573 // Figure out how many bytes are in the token that is the placeholder for
574 // the attribute description.
575 int pos = startPos;
576 int adArrayLength = encodedEntry[pos] & 0x7F;
577 if (adArrayLength != encodedEntry[pos++])
578 {
579 int numLengthBytes = adArrayLength;
580 adArrayLength = 0;
581 for (int i=0; i < numLengthBytes; i++, pos++)
582 {
583 adArrayLength = (adArrayLength << 8) | (encodedEntry[pos] & 0xFF);
584 }
585 }
586
587
588 // Get the attribute description token and make sure it resolves to an
589 // attribute type and option set.
590 ByteArray adArray = new ByteArray(new byte[adArrayLength]);
591 System.arraycopy(encodedEntry, pos, adArray.array(), 0, adArrayLength);
592 pos += adArrayLength;
593 AttributeType attrType = atDecodeMap.get(adArray);
594 LinkedHashSet<String> options = aoDecodeMap.get(adArray);
595 if ((attrType == null) || (options == null))
596 {
597 Message message = ERR_JEB_COMPSCHEMA_UNRECOGNIZED_AD_TOKEN.get(
598 bytesToHex(adArray.array()));
599 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
600 message);
601 }
602
603
604 // Determine the number of values for the attribute.
605 int numValues = encodedEntry[pos] & 0x7F;
606 if (numValues != encodedEntry[pos++])
607 {
608 int numValuesBytes = numValues;
609 numValues = 0;
610 for (int i=0; i < numValuesBytes; i++, pos++)
611 {
612 numValues = (numValues << 8) | (encodedEntry[pos] & 0xFF);
613 }
614 }
615
616
617 // Read the appropriate number of values.
618 LinkedHashSet<AttributeValue> values =
619 new LinkedHashSet<AttributeValue>(numValues);
620 for (int i=0; i < numValues; i++)
621 {
622 int valueLength = encodedEntry[pos] & 0x7F;
623 if (valueLength != encodedEntry[pos++])
624 {
625 int valueLengthBytes = valueLength;
626 valueLength = 0;
627 for (int j=0; j < valueLengthBytes; j++, pos++)
628 {
629 valueLength = (valueLength << 8) | (encodedEntry[pos] & 0xFF);
630 }
631 }
632
633 byte[] valueBytes = new byte[valueLength];
634 System.arraycopy(encodedEntry, pos, valueBytes, 0, valueLength);
635 pos += valueLength;
636 values.add(new AttributeValue(attrType, new ASN1OctetString(valueBytes)));
637 }
638
639 return new Attribute(attrType, attrType.getPrimaryName(), options, values);
640 }
641
642
643
644 /**
645 * Stores the provided key-value pair in the specified database container.
646 *
647 * @param database The database in which to store the information.
648 * @param keyBytes The byte array containing the key to store.
649 * @param valueBytes The byte array containing the value to store.
650 *
651 * @throws DirectoryException If a problem occurs while attempting to store
652 * the data.
653 */
654 private void store(Database database, byte[] keyBytes, byte[] valueBytes)
655 throws DirectoryException
656 {
657 boolean successful = false;
658 DatabaseEntry keyEntry = new DatabaseEntry(keyBytes);
659 DatabaseEntry valueEntry = new DatabaseEntry(valueBytes);
660
661 for (int i=0; i < 3; i++)
662 {
663 try
664 {
665 OperationStatus status = database.putNoOverwrite(null, keyEntry,
666 valueEntry);
667 if (status == OperationStatus.SUCCESS)
668 {
669 successful = true;
670 break;
671 }
672 else
673 {
674 Message m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_STATUS.get(
675 status.toString());
676 throw new DirectoryException(
677 DirectoryServer.getServerErrorResultCode(), m);
678 }
679 }
680 catch (DeadlockException de)
681 {
682 continue;
683 }
684 catch (DatabaseException de)
685 {
686 Message m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_EX.get(de.getMessage());
687 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
688 m, de);
689 }
690 }
691
692 if (! successful)
693 {
694 Message m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_MULTIPLE_FAILURES.get();
695 throw new DirectoryException(
696 DirectoryServer.getServerErrorResultCode(), m);
697 }
698 }
699
700
701
702
703 /**
704 * Encodes the provided int value to a byte array.
705 *
706 * @param intValue The int value to be encoded.
707 *
708 * @return The byte array containing the encoded int value.
709 */
710 private byte[] encodeInt(int intValue)
711 {
712 byte[] array;
713 if (intValue <= 0xFF)
714 {
715 array = new byte[1];
716 array[0] = (byte) (intValue & 0xFF);
717 }
718 else if (intValue <= 0xFFFF)
719 {
720 array = new byte[2];
721 array[0] = (byte) ((intValue >> 8) & 0xFF);
722 array[1] = (byte) (intValue & 0xFF);
723 }
724 else if (intValue <= 0xFFFFFF)
725 {
726 array = new byte[3];
727 array[0] = (byte) ((intValue >> 16) & 0xFF);
728 array[1] = (byte) ((intValue >> 8) & 0xFF);
729 array[2] = (byte) (intValue & 0xFF);
730 }
731 else
732 {
733 array = new byte[4];
734 array[0] = (byte) ((intValue >> 24) & 0xFF);
735 array[1] = (byte) ((intValue >> 16) & 0xFF);
736 array[2] = (byte) ((intValue >> 8) & 0xFF);
737 array[3] = (byte) (intValue & 0xFF);
738 }
739
740 return array;
741 }
742
743
744
745 /**
746 * Decodes the contents of the provided byte array as an int.
747 *
748 * @param byteArray The byte array containing the data to decode.
749 *
750 * @return The decoded int value.
751 */
752 private int decodeInt(byte[] byteArray)
753 {
754 int intValue = 0;
755
756 for (byte b : byteArray)
757 {
758 intValue <<= 8;
759 intValue |= (b & 0xFF);
760 }
761
762 return intValue;
763 }
764 }
765