001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2009 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2016 ForgeRock AS.
016 */
017package org.opends.server.api;
018
019import static org.opends.messages.CoreMessages.*;
020import static com.forgerock.opendj.util.StaticUtils.toLowerCase;
021
022import java.util.AbstractMap.SimpleImmutableEntry;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.Iterator;
026import java.util.LinkedHashMap;
027import java.util.LinkedHashSet;
028import java.util.List;
029import java.util.Map;
030import java.util.Map.Entry;
031import java.util.Set;
032import java.util.concurrent.ConcurrentHashMap;
033import java.util.concurrent.CopyOnWriteArrayList;
034import java.util.concurrent.locks.Lock;
035import java.util.concurrent.locks.ReadWriteLock;
036import java.util.concurrent.locks.ReentrantReadWriteLock;
037
038import net.jcip.annotations.GuardedBy;
039
040import org.forgerock.opendj.ldap.AttributeDescription;
041import org.forgerock.opendj.ldap.ByteSequenceReader;
042import org.forgerock.opendj.ldap.ByteString;
043import org.forgerock.opendj.ldap.ByteStringBuilder;
044import org.forgerock.opendj.ldap.schema.AttributeType;
045import org.forgerock.opendj.ldap.schema.Schema;
046import org.opends.server.core.DirectoryServer;
047import org.opends.server.core.ServerContext;
048import org.opends.server.types.Attribute;
049import org.opends.server.types.AttributeBuilder;
050import org.opends.server.types.Attributes;
051import org.opends.server.types.DirectoryException;
052import org.opends.server.types.ObjectClass;
053import org.opends.server.util.RemoveOnceSDKSchemaIsUsed;
054
055/**
056 * This class provides a utility for interacting with compressed representations
057 * of schema elements. The default implementation does not persist encoded
058 * attributes and object classes.
059 */
060@org.opends.server.types.PublicAPI(
061    stability = org.opends.server.types.StabilityLevel.UNCOMMITTED,
062    mayInstantiate = false,
063    mayExtend = true,
064    mayInvoke = false)
065public class CompressedSchema
066{
067  /** Encloses all the encode and decode mappings for attribute and object classes. */
068  private static final class Mappings
069  {
070    /** Maps encoded representation's ID to its attribute description (the List's index is the ID). */
071    private final List<AttributeDescription> adDecodeMap = new CopyOnWriteArrayList<>();
072    /** Maps attribute description to its encoded representation's ID. */
073    private final Map<AttributeDescription, Integer> adEncodeMap;
074    /** Maps encoded representation's ID to its object class (the List's index is the ID). */
075    private final List<Map<ObjectClass, String>> ocDecodeMap = new CopyOnWriteArrayList<>();
076    /** Maps object class to its encoded representation's ID. */
077    private final Map<Map<ObjectClass, String>, Integer> ocEncodeMap;
078
079    private Mappings()
080    {
081      this.adEncodeMap = new ConcurrentHashMap<>();
082      this.ocEncodeMap = new ConcurrentHashMap<>();
083    }
084
085    private Mappings(int adEncodeMapSize, int ocEncodeMapSize)
086    {
087      this.adEncodeMap = new ConcurrentHashMap<>(adEncodeMapSize);
088      this.ocEncodeMap = new ConcurrentHashMap<>(ocEncodeMapSize);
089    }
090  }
091
092  private final ServerContext serverContext;
093  /** Lock to update the maps. */
094  final ReadWriteLock lock = new ReentrantReadWriteLock();
095  private final Lock exclusiveLock = lock.writeLock();
096  private final Lock sharedLock = lock.readLock();
097
098  /** Schema used to build the compressed information. */
099  @GuardedBy("lock")
100  private Schema schemaNG;
101  @GuardedBy("lock")
102  private Mappings mappings = new Mappings();
103
104  /**
105   * Creates a new empty instance of this compressed schema.
106   *
107   * @param serverContext
108   *            The server context.
109   */
110  public CompressedSchema(ServerContext serverContext)
111  {
112    this.serverContext = serverContext;
113  }
114
115  private Mappings getMappings()
116  {
117    sharedLock.lock();
118    try
119    {
120      return mappings;
121    }
122    finally
123    {
124      sharedLock.unlock();
125    }
126  }
127
128  private Mappings reloadMappingsIfSchemaChanged(boolean force)
129  {
130    // @RemoveOnceSDKSchemaIsUsed remove the "force" parameter
131    sharedLock.lock();
132    boolean shared = true;
133    try
134    {
135      Schema currentSchema = serverContext.getSchemaNG();
136      if (force || schemaNG != currentSchema)
137      {
138        sharedLock.unlock();
139        exclusiveLock.lock();
140        shared = false;
141
142        currentSchema = serverContext.getSchemaNG();
143        if (force || schemaNG != currentSchema)
144        {
145          // build new maps from existing ones
146          Mappings newMappings = new Mappings(mappings.adEncodeMap.size(), mappings.ocEncodeMap.size());
147          reloadAttributeTypeMaps(mappings, newMappings);
148          reloadObjectClassesMap(mappings, newMappings);
149
150          mappings = newMappings;
151          schemaNG = currentSchema;
152        }
153      }
154      return mappings;
155    }
156    finally
157    {
158      (shared ? sharedLock : exclusiveLock).unlock();
159    }
160  }
161
162  /**
163   * Reload the attribute types maps. This should be called when schema has changed, because some
164   * types may be out dated.
165   */
166  private void reloadAttributeTypeMaps(Mappings mappings, Mappings newMappings)
167  {
168    for (Entry<AttributeDescription, Integer> entry : mappings.adEncodeMap.entrySet())
169    {
170      AttributeDescription ad = entry.getKey();
171      Integer id = entry.getValue();
172      loadAttributeToMaps(id, ad.getAttributeType().getNameOrOID(), ad.getOptions(), newMappings);
173    }
174  }
175
176  /**
177   * Reload the object classes maps. This should be called when schema has changed, because some
178   * classes may be out dated.
179   */
180  private void reloadObjectClassesMap(Mappings mappings, Mappings newMappings)
181  {
182    for (Entry<Map<ObjectClass, String>, Integer> entry : mappings.ocEncodeMap.entrySet())
183    {
184      Map<ObjectClass, String> ocMap = entry.getKey();
185      Integer id = entry.getValue();
186      loadObjectClassesToMaps(id, ocMap.values(), newMappings, false);
187    }
188  }
189
190  /**
191   * Decodes the contents of the provided array as an attribute at the current
192   * position.
193   *
194   * @param reader
195   *          The byte string reader containing the encoded entry.
196   * @return The decoded attribute.
197   * @throws DirectoryException
198   *           If the attribute could not be decoded properly for some reason.
199   */
200  public final Attribute decodeAttribute(final ByteSequenceReader reader)
201      throws DirectoryException
202  {
203    // First decode the encoded attribute description id.
204    final int id = decodeId(reader);
205
206    // Before returning the attribute, make sure that the attribute type is not stale.
207    final Mappings mappings = reloadMappingsIfSchemaChanged(false);
208    final AttributeDescription ad = mappings.adDecodeMap.get(id);
209    if (ad == null)
210    {
211      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
212          ERR_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN.get(id));
213    }
214
215    AttributeType attrType = ad.getAttributeType();
216
217    // Determine the number of values for the attribute.
218    final int numValues = reader.readBERLength();
219
220    // For the common case of a single value with no options, generate less garbage.
221    if (numValues == 1 && !ad.hasOptions())
222    {
223      return Attributes.create(attrType, readValue(reader));
224    }
225    else
226    {
227      // Read the appropriate number of values.
228      final AttributeBuilder builder = new AttributeBuilder(attrType);
229      builder.setOptions(ad.getOptions());
230      for (int i = 0; i < numValues; i++)
231      {
232        builder.add(readValue(reader));
233      }
234      return builder.toAttribute();
235    }
236  }
237
238  private ByteString readValue(final ByteSequenceReader reader)
239  {
240    return reader.readByteSequence(reader.readBERLength()).toByteString();
241  }
242
243  /**
244   * Decodes an object class set from the provided byte string.
245   *
246   * @param reader
247   *          The byte string reader containing the object class set identifier.
248   * @return The decoded object class set.
249   * @throws DirectoryException
250   *           If the provided byte string reader cannot be decoded as an object
251   *           class set.
252   */
253  public final Map<ObjectClass, String> decodeObjectClasses(
254      final ByteSequenceReader reader) throws DirectoryException
255  {
256    // First decode the encoded object class id.
257    final int id = decodeId(reader);
258
259    // Look up the object classes.
260    final Mappings mappings = getMappings();
261    Map<ObjectClass, String> ocMap = mappings.ocDecodeMap.get(id);
262    if (ocMap == null)
263    {
264      // @RemoveOnceSDKSchemaIsUsed remove this first check (check is performed again later)
265      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
266          ERR_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN.get(id));
267    }
268    // Before returning the object classes, make sure that none of them are stale.
269    boolean forceReload = isAnyObjectClassDirty(ocMap.keySet());
270    final Mappings newMappings = reloadMappingsIfSchemaChanged(forceReload);
271    if (mappings != newMappings)
272    {
273      ocMap = newMappings.ocDecodeMap.get(id);
274      if (ocMap == null)
275      {
276        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
277            ERR_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN.get(id));
278      }
279    }
280    return ocMap;
281  }
282
283  @RemoveOnceSDKSchemaIsUsed
284  private boolean isAnyObjectClassDirty(Set<ObjectClass> objectClasses)
285  {
286    for (final ObjectClass oc : objectClasses)
287    {
288      if (oc.isDirty())
289      {
290        return true;
291      }
292    }
293    return false;
294  }
295
296  /**
297   * Encodes the information in the provided attribute to a byte array.
298   *
299   * @param builder
300   *          The buffer to encode the attribute to.
301   * @param attribute
302   *          The attribute to be encoded.
303   * @throws DirectoryException
304   *           If a problem occurs while attempting to determine the appropriate
305   *           identifier.
306   */
307  public final void encodeAttribute(final ByteStringBuilder builder,
308      final Attribute attribute) throws DirectoryException
309  {
310    // Re-use or allocate a new ID.
311    int id = getAttributeId(attribute.getAttributeDescription());
312
313    // Encode the attribute.
314    final byte[] idBytes = encodeId(id);
315    builder.appendBERLength(idBytes.length);
316    builder.appendBytes(idBytes);
317    builder.appendBERLength(attribute.size());
318    for (final ByteString v : attribute)
319    {
320      builder.appendBERLength(v.length());
321      builder.appendBytes(v);
322    }
323  }
324
325  private int getAttributeId(final AttributeDescription ad) throws DirectoryException
326  {
327    // avoid lazy registration races
328    boolean shared = true;
329    sharedLock.lock();
330    try
331    {
332      Integer id = mappings.adEncodeMap.get(ad);
333      if (id != null)
334      {
335        return id;
336      }
337
338      sharedLock.unlock();
339      exclusiveLock.lock();
340      shared = false;
341
342      id = mappings.adEncodeMap.get(ad);
343      if (id == null)
344      {
345        id = mappings.adDecodeMap.size();
346        mappings.adDecodeMap.add(ad);
347        mappings.adEncodeMap.put(ad, id);
348        storeAttribute(encodeId(id), ad.getAttributeType().getNameOrOID(), ad.getOptions());
349      }
350      return id;
351    }
352    finally
353    {
354      (shared ? sharedLock : exclusiveLock).unlock();
355    }
356  }
357
358  /**
359   * Encodes the provided set of object classes to a byte array. If the same set
360   * had been previously encoded, then the cached value will be used. Otherwise,
361   * a new value will be created.
362   *
363   * @param builder
364   *          The buffer to encode the object classes to.
365   * @param objectClasses
366   *          The set of object classes for which to retrieve the corresponding
367   *          byte array token.
368   * @throws DirectoryException
369   *           If a problem occurs while attempting to determine the appropriate
370   *           identifier.
371   */
372  public final void encodeObjectClasses(final ByteStringBuilder builder,
373      final Map<ObjectClass, String> objectClasses) throws DirectoryException
374  {
375    // Re-use or allocate a new ID.
376    int id = getObjectClassId(objectClasses);
377
378    // Encode the object classes.
379    final byte[] idBytes = encodeId(id);
380    builder.appendBERLength(idBytes.length);
381    builder.appendBytes(idBytes);
382  }
383
384  private int getObjectClassId(final Map<ObjectClass, String> objectClasses) throws DirectoryException
385  {
386    // avoid lazy registration races
387    boolean shared = true;
388    sharedLock.lock();
389    try
390    {
391      Integer id = mappings.ocEncodeMap.get(objectClasses);
392      if (id != null)
393      {
394        return id;
395      }
396
397      sharedLock.unlock();
398      exclusiveLock.lock();
399      shared = false;
400
401      id = mappings.ocEncodeMap.get(objectClasses);
402      if (id == null)
403      {
404        id = mappings.ocDecodeMap.size();
405        mappings.ocDecodeMap.add(objectClasses);
406        mappings.ocEncodeMap.put(objectClasses, id);
407        storeObjectClasses(encodeId(id), objectClasses.values());
408      }
409      return id;
410    }
411    finally
412    {
413      (shared ? sharedLock : exclusiveLock).unlock();
414    }
415  }
416
417  /**
418   * Returns a view of the encoded attributes in this compressed schema which can be used for saving
419   * the entire content to disk.
420   * <p>
421   * The iterator returned by this method is not thread safe.
422   *
423   * @return A view of the encoded attributes in this compressed schema.
424   */
425  protected final Iterable<Entry<byte[], Entry<String, Iterable<String>>>> getAllAttributes()
426  {
427    return new Iterable<Entry<byte[], Entry<String, Iterable<String>>>>()
428    {
429      @Override
430      public Iterator<Entry<byte[], Entry<String, Iterable<String>>>> iterator()
431      {
432        return new Iterator<Entry<byte[], Entry<String, Iterable<String>>>>()
433        {
434          private int id;
435          private List<AttributeDescription> adDecodeMap = getMappings().adDecodeMap;
436
437          @Override
438          public boolean hasNext()
439          {
440            return id < adDecodeMap.size();
441          }
442
443          @Override
444          public Entry<byte[], Entry<String, Iterable<String>>> next()
445          {
446            final byte[] encodedAttribute = encodeId(id);
447            final AttributeDescription ad = adDecodeMap.get(id++);
448            return new SimpleImmutableEntry<byte[], Entry<String, Iterable<String>>>(
449                encodedAttribute,
450                new SimpleImmutableEntry<String, Iterable<String>>(
451                    ad.getAttributeType().getNameOrOID(), ad.getOptions()));
452          }
453
454          @Override
455          public void remove()
456          {
457            throw new UnsupportedOperationException();
458          }
459        };
460      }
461    };
462  }
463
464  /**
465   * Returns a view of the encoded object classes in this compressed schema which can be used for
466   * saving the entire content to disk.
467   * <p>
468   * The iterator returned by this method is not thread safe.
469   *
470   * @return A view of the encoded object classes in this compressed schema.
471   */
472  protected final Iterable<Entry<byte[], Collection<String>>> getAllObjectClasses()
473  {
474    return new Iterable<Entry<byte[], Collection<String>>>()
475    {
476      @Override
477      public Iterator<Entry<byte[], Collection<String>>> iterator()
478      {
479        return new Iterator<Map.Entry<byte[], Collection<String>>>()
480        {
481          private int id;
482          private final List<Map<ObjectClass, String>> ocDecodeMap = getMappings().ocDecodeMap;
483
484          @Override
485          public boolean hasNext()
486          {
487            return id < ocDecodeMap.size();
488          }
489
490          @Override
491          public Entry<byte[], Collection<String>> next()
492          {
493            final byte[] encodedObjectClasses = encodeId(id);
494            final Map<ObjectClass, String> ocMap = ocDecodeMap.get(id++);
495            return new SimpleImmutableEntry<>(encodedObjectClasses, ocMap.values());
496          }
497
498          @Override
499          public void remove()
500          {
501            throw new UnsupportedOperationException();
502          }
503        };
504      }
505    };
506  }
507
508  /**
509   * Loads an encoded attribute into this compressed schema. This method may
510   * called by implementations during initialization when loading content from
511   * disk.
512   *
513   * @param encodedAttribute
514   *          The encoded attribute description.
515   * @param attributeName
516   *          The user provided attribute type name.
517   * @param attributeOptions
518   *          The non-null but possibly empty set of attribute options.
519   * @return The attribute type description.
520   */
521  protected final AttributeDescription loadAttribute(
522      final byte[] encodedAttribute, final String attributeName,
523      final Collection<String> attributeOptions)
524  {
525    final int id = decodeId(encodedAttribute);
526    return loadAttributeToMaps(id, attributeName, attributeOptions, getMappings());
527  }
528
529  /**
530   * Loads an attribute into provided encode and decode maps, given its id, name, and options.
531   *
532   * @param id
533   *          the id computed on the attribute.
534   * @param attributeName
535   *          The user provided attribute type name.
536   * @param attributeOptions
537   *          The non-null but possibly empty set of attribute options.
538   * @param mappings
539   *          attribute description encodeMap and decodeMap maps id to entry
540   * @return The attribute type description.
541   */
542  private AttributeDescription loadAttributeToMaps(final int id, final String attributeName,
543      final Iterable<String> attributeOptions, final Mappings mappings)
544  {
545    final AttributeType type = DirectoryServer.getAttributeType(attributeName);
546    final Set<String> options = getOptions(attributeOptions);
547    final AttributeDescription ad = AttributeDescription.create(type, options);
548    exclusiveLock.lock();
549    try
550    {
551      mappings.adEncodeMap.put(ad, id);
552      if (id < mappings.adDecodeMap.size())
553      {
554        mappings.adDecodeMap.set(id, ad);
555      }
556      else
557      {
558        // Grow the decode array.
559        while (id > mappings.adDecodeMap.size())
560        {
561          mappings.adDecodeMap.add(null);
562        }
563        mappings.adDecodeMap.add(ad);
564      }
565      return ad;
566    }
567    finally
568    {
569      exclusiveLock.unlock();
570    }
571  }
572
573  private Set<String> getOptions(final Iterable<String> attributeOptions)
574  {
575    Iterator<String> it = attributeOptions.iterator();
576    if (!it.hasNext())
577    {
578      return Collections.emptySet();
579    }
580    String firstOption = it.next();
581    if (!it.hasNext())
582    {
583      return Collections.singleton(firstOption);
584    }
585    LinkedHashSet<String> results = new LinkedHashSet<>();
586    results.add(firstOption);
587    while (it.hasNext())
588    {
589      results.add(it.next());
590    }
591    return results;
592  }
593
594  /**
595   * Loads an encoded object class into this compressed schema. This method may
596   * called by implementations during initialization when loading content from
597   * disk.
598   *
599   * @param encodedObjectClasses
600   *          The encoded object classes.
601   * @param objectClassNames
602   *          The user provided set of object class names.
603   * @return The object class set.
604   */
605  protected final Map<ObjectClass, String> loadObjectClasses(
606      final byte[] encodedObjectClasses,
607      final Collection<String> objectClassNames)
608  {
609    final int id = decodeId(encodedObjectClasses);
610    return loadObjectClassesToMaps(id, objectClassNames, mappings, true);
611  }
612
613  /**
614   * Loads a set of object classes into provided encode and decode maps, given the id and set of
615   * names.
616   *
617   * @param id
618   *          the id computed on the object classes set.
619   * @param objectClassNames
620   *          The user provided set of object class names.
621   * @param mappings
622   *          .ocEncodeMap maps id to entry
623   * @param mappings
624   *          .ocDecodeMap maps entry to id
625   * @param sync
626   *          indicates if update of maps should be synchronized
627   * @return The object class set.
628   */
629  private final Map<ObjectClass, String> loadObjectClassesToMaps(int id, final Collection<String> objectClassNames,
630      Mappings mappings, boolean sync)
631  {
632    final LinkedHashMap<ObjectClass, String> ocMap = new LinkedHashMap<>(objectClassNames.size());
633    for (final String name : objectClassNames)
634    {
635      final String lowerName = toLowerCase(name);
636      final ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true);
637      ocMap.put(oc, name);
638    }
639    if (sync)
640    {
641      exclusiveLock.lock();
642      try
643      {
644        updateObjectClassesMaps(id, mappings, ocMap);
645      }
646      finally
647      {
648        exclusiveLock.unlock();
649      }
650    }
651    else
652    {
653      updateObjectClassesMaps(id, mappings, ocMap);
654    }
655    return ocMap;
656  }
657
658  private void updateObjectClassesMaps(int id, Mappings mappings, LinkedHashMap<ObjectClass, String> ocMap)
659  {
660    mappings.ocEncodeMap.put(ocMap, id);
661    if (id < mappings.ocDecodeMap.size())
662    {
663      mappings.ocDecodeMap.set(id, ocMap);
664    }
665    else
666    {
667      // Grow the decode array.
668      while (id > mappings.ocDecodeMap.size())
669      {
670        mappings.ocDecodeMap.add(null);
671      }
672      mappings.ocDecodeMap.add(ocMap);
673    }
674  }
675
676  /**
677   * Persists the provided encoded attribute. The default implementation is to
678   * do nothing. Calls to this method are synchronized, so implementations can
679   * assume that this method is not being called by other threads. Note that
680   * this method is not thread-safe with respect to
681   * {@link #storeObjectClasses(byte[], Collection)}.
682   *
683   * @param encodedAttribute
684   *          The encoded attribute description.
685   * @param attributeName
686   *          The user provided attribute type name.
687   * @param attributeOptions
688   *          The non-null but possibly empty set of attribute options.
689   * @throws DirectoryException
690   *           If an error occurred while persisting the encoded attribute.
691   */
692  protected void storeAttribute(final byte[] encodedAttribute,
693      final String attributeName, final Iterable<String> attributeOptions)
694      throws DirectoryException
695  {
696    // Do nothing by default.
697  }
698
699  /**
700   * Persists the provided encoded object classes. The default implementation is
701   * to do nothing. Calls to this method are synchronized, so implementations
702   * can assume that this method is not being called by other threads. Note that
703   * this method is not thread-safe with respect to
704   * {@link #storeAttribute(byte[], String, Iterable)}.
705   *
706   * @param encodedObjectClasses
707   *          The encoded object classes.
708   * @param objectClassNames
709   *          The user provided set of object class names.
710   * @throws DirectoryException
711   *           If an error occurred while persisting the encoded object classes.
712   */
713  protected void storeObjectClasses(final byte[] encodedObjectClasses,
714      final Collection<String> objectClassNames) throws DirectoryException
715  {
716    // Do nothing by default.
717  }
718
719  /**
720   * Decodes the provided encoded schema element ID.
721   *
722   * @param idBytes
723   *          The encoded schema element ID.
724   * @return The schema element ID.
725   */
726  private int decodeId(final byte[] idBytes)
727  {
728    int id = 0;
729    for (final byte b : idBytes)
730    {
731      id <<= 8;
732      id |= b & 0xFF;
733    }
734    return id - 1; // Subtract 1 to compensate for old behavior.
735  }
736
737  private int decodeId(final ByteSequenceReader reader)
738  {
739    final int length = reader.readBERLength();
740    final byte[] idBytes = new byte[length];
741    reader.readBytes(idBytes);
742    return decodeId(idBytes);
743  }
744
745  /**
746   * Encodes the provided schema element ID.
747   *
748   * @param id
749   *          The schema element ID.
750   * @return The encoded schema element ID.
751   */
752  private byte[] encodeId(final int id)
753  {
754    final int value = id + 1; // Add 1 to compensate for old behavior.
755    final byte[] idBytes;
756    if (value <= 0xFF)
757    {
758      idBytes = new byte[1];
759      idBytes[0] = (byte) (value & 0xFF);
760    }
761    else if (value <= 0xFFFF)
762    {
763      idBytes = new byte[2];
764      idBytes[0] = (byte) ((value >> 8) & 0xFF);
765      idBytes[1] = (byte) (value & 0xFF);
766    }
767    else if (value <= 0xFFFFFF)
768    {
769      idBytes = new byte[3];
770      idBytes[0] = (byte) ((value >> 16) & 0xFF);
771      idBytes[1] = (byte) ((value >> 8) & 0xFF);
772      idBytes[2] = (byte) (value & 0xFF);
773    }
774    else
775    {
776      idBytes = new byte[4];
777      idBytes[0] = (byte) ((value >> 24) & 0xFF);
778      idBytes[1] = (byte) ((value >> 16) & 0xFF);
779      idBytes[2] = (byte) ((value >> 8) & 0xFF);
780      idBytes[3] = (byte) (value & 0xFF);
781    }
782    return idBytes;
783  }
784}