Chapter 12. Entity attributes

Table of Contents

Unix attributes
NTFS attributes
Custom attributes

An EntityAttributes object captures a collection of attributes for an Entity, such as the information on who owns it. It can be used for representing metadata in a structured way.

The entity attributes of an entity are properties of its parent directory. The entity attributes for all of a directory's child entities are stored in one of the directory's metadata files, so all entity attribute capability implementations require that the file system they are used for support the FSCDirectoryMetadata capability.

The UnixAttributes contains metadata information about entities that can be found in a Unix file system. The attributes are the user and group id:s of the entity's owner, the UnixEntityMode of the entity and the entity's creation time.

UnixAttributes support is provided by the MetadataUnixAttributesCapabilityProvider. It uses a B+ Tree HeliDB database stored in a metadata file for a directory to store all the entity attributes for that directory's child entities. This has the consequence that the root directory itself does not support entity attributes.

The NtfsAttributes contains a subset of the metadata information about entities that can be found in an NTFS file system. The attributes are the user and group id:s of the entity's owner, the UnixEntityMode of the entity, the entity's creation time and the set of NtFileAttributes.

NtfsAttributes support is provided by the MetadataNtfsAttributesCapabilityProvider. It uses a B+ Tree HeliDB database stored in a metadata file for a directory to store all the entity attributes for that directory's child entities. This has the consequence that the root directory itself does not support entity attributes.

The MetadataCustomAttributesCapabilityProvider can be used to have custom EntityAttributes for entities. The client must provide factory objects for default attributes for different entity types and an object that is used to parse the entity attributes metadata file, a HeliDB Serializer.

For entity attributes that have records of a fixed size in the attributes metadata file, there are abstract stub implementations of the required classes. For entity attributes that don't have fixed record size, some more effort is required. See the API documentation for details.

The rather long example below shows how a simple EntityAttributes object and all other classes required to use it together with the MetadataCustomAttributesCapabilityProvider are implemented.

Example 12.1. Using the custom entity attributes capability

// Our custom entity attributes
// Implement the attributes object to be immutable.
public class IndexAndBackupAttributes implements EntityAttributes
{
  private final Date m_indexDate;
  private final Date m_backupDate;
  
  public IndexAndBackupAttributes(Date indexDate, Date backupDate)
  {
    // Null checks
    indexDate.getClass();
    backupDate.getClass();
    m_indexDate = indexDate;
    m_backupDate = backupDate;
  }
  
  public Date getIndexDate()
  {
    return m_indexDate;
  }
  
  public Date getBackupDate()
  {
    return m_backupDate;
  }
  
  // EntityAttributes inherits Cloneable
  public Object clone()
  {
    return new IndexAndBackupAttributes(m_indexDate, m_backupDate);
  }
  
  // EntityAttributes recommends that we implement equals
  @Override
  public boolean equals(Object o)
  {
    if (o instanceof IndexAndBackupAttributes)
    {
      IndexAndBackupAttributes a2 = (IndexAndBackupAttributes) o;
      return a2.m_indexDate.equals(m_indexDate) && a2.m_backupDate.equals(m_backupDate);
    }
    else
    {
      return false;
    }
  }
  
  // We implemented equals, so now we have to implement hashCode too...
  @Override
  public int hashCode()
  {
    // Implement as suggested in Joshua Bloch's Effective Java
    int res = ((int) m_indexDate.getTime() ^ (int) (m_indexDate.getTime() >>> 32));
    return 31 * res + 
      (int) (m_backupDate.getTime() ^ (int) (m_backupDate.getTime() >>> 32));
  }
}

// This is the Serializer that is used to serialize
// attributes to and interpret attributes from the file where they are stored.
// One instance is used by all objects.
public class IndexAndBackupSerializer
  implements Serializer<IndexAndBackupAttributes>
{
  // The size of a serialized object in bytes.
  // A long is 8 bytes long
  private static final int SERIALIZED_SIZE = 8 + 8;
  
  // Get the size in bytes of the object when serialized.
  public int getSerializedSize()
  {
    return SERIALIZED_SIZE;
  }
  
  public boolean isNullValuesPermitted()
  {
    // No.
    return false;
  }
  
  // This is used by both serialization methods
  private void serializeInternal(IndexAndBackupAttributes o, byte[] barr, int offset)
    // We are allowed to throw this
    throws ArrayIndexOutOfBoundsException
  {
    // Serialize the dates to long:s. Use the LongSerializer.
    int pos = offset;
    LongSerializer.INSTANCE.serialize(o.getIndexDate().getTime(), barr, pos);
    pos += LongSerializer.DATA_SIZE;
    LongSerializer.INSTANCE.serialize(o.getBackupDate().getTime(), barr, pos);
  }
 
  public byte[] serialize(IndexAndBackupAttributes o)
  {
    byte[] res = new byte[SERIALIZED_SIZE];
    serializeInternal(o, res, 0);
    return res;
  }
 
  public int serialize(IndexAndBackupAttributes o, byte[] barr, int offset)
  {
    serializeInternal(o, barr, offset);
    return SERIALIZED_SIZE;
  } 
  
  private void validateSize(int size)
  {
    if (size != SERIALIZED_SIZE)
    {
      throw new SerializationException(
        "Invalid size of data " + size + 
        ". Must be " + SERIALIZED_SIZE + " bytes");
    }
  }
  
  // This is used by both interpret methods.
  private IndexAndBackupAttributes interpretInternal(byte[] barr, int offset)
  {
    // Use the LongSerializer to read the dates
    // that are serialized as longs
    int pos = offset;
    long indexDate = 
      LongSerializer.INSTANCE.interpret(barr, pos, LongSerializer.DATA_SIZE);
    pos += LongSerializer.DATA_SIZE;
    long backupDate =
      LongSerializer.INSTANCE.interpret(barr, pos, LongSerializer.DATA_SIZE);
    return new IndexAndBackupAttributes(
      new Date(indexDate),
      new Date(backupDate));
  }
  
  public IndexAndBackupAttributes interpret(byte[] barr) 
    throws SerializationException
  {
    validateSize(barr.length);
    return interpretInternal(barr, 0);
  }
  
  public IndexAndBackupAttributes interpret(byte[] barr, int offset, int length)
    throws SerializationException
  {
    validateSize(length);
    return interpretInternal(barr, offset);
  }
  
  public IndexAndBackupAttributes read(InputStream is, int size)
  {
    validateSize(size);
    byte[] barr = new byte[SERIALIZED_SIZE];
    try
    {
      int noRead = is.read(barr);
      if (noRead != SERIALIZED_SIZE)
      {
        throw new NotEnoughDataException(SERIALIZED_SIZE, noRead);
      }
      return interpret(barr);
    }
    catch (IOException e)
    {
      throw new WrappedIOException(e);
    }
  }
  
  public IndexAndBackupAttributes read(RandomAccess ra, int size)
  {
    validateSize(size);
    byte[] barr = new byte[SERIALIZED_SIZE];
    int noRead = ra.read(barr);
    if (noRead != SERIALIZED_SIZE)
    {
      throw new NotEnoughDataException(SERIALIZED_SIZE, noRead);
    }
    return interpret(barr);
  }
}

// This ObjectFactory is used to create default values of the attributes
// for different entity types.
public class IndexAndBackupAttributesFactory 
  implements ObjectFactory<IndexAndBackupAttributes>
{
  public IndexAndBackupAttributes create()
  {
    // Initialize the object with the current time.
    Date d = new Date();
    return new IndexAndBackupAttributes(d, d);
  }
}

// This object creates a file system whose entities have 
// IndexAndBackupAttributes, creates a couple of entities and set their
// attribute values.
public class MyIndexAndBackupAttributesUsingClass
{
  public FileSystem createAndInitFileSystem(File root)
  {
    // Default attributes factories for the different entity types.
    IndexAndBackupAttributesFactory af = new IndexAndBackupAttributesFactory();
    Map<EntityType, ObjectFactory<IndexAndBackupAttributes>> daf =
      new HashMap <EntityType, ObjectFactory<IndexAndBackupAttributes>>();
    daf.put(ETFile.TYPE, af);
    daf.put(ETDirectory.TYPE, af);
    
    FileSystem res =
      new FSRWFileSystemBuilder().
        setRoot(root).
        // Add the necessary directory metadata capability provider first.
        addCapabilityProvider(
          new FileBasedDirectoryMetadataCapabilityProvider()).
        // Add the custom entity attributes capability provider
        addCapabilityProvider(
          new MetadataCustomAttributesCapabilityProvider().
            // Don't store entities' attributes lazily (this is the default, so
            // this invocation is unnecessary)
            setStoreAttributesLazily(false).
            setDefaultAttributesFactories(daf).
            // Set the types of attributes that will be handled by the 
            // capability
            setAttributesType(IndexAndBackupAttributes.class).
            // This factory creates ChildEntityAttributeManager
            // objects for all directories.
            setChildEntityAttributeManagerFactory(
              // This can be used for capabilities that store their attributes
              // in a HeliDB database.
              new DatabaseChildEntityAttributeManagerFactory<IndexAndBackupAttributes>(
                new IndexAndBackupSerializer(), daf, 8192))).create();
    Directory fsroot = res.getRootDirectory();
	
    EFile f = Directories.newFile(fsroot, "f");
    // This has not been backed up since the 28th of January 1982. Sorry.
    Calendar cal = Calendar.getInstance();
    cal.clear();
    cal.set(Calendar.YEAR, 1982);
    cal.set(Calendar.MONTH, 0);
    cal.set(Calendar.DAY_OF_MONTH, 28);
    IndexAndBackupAttributes iab = 
      new IndexAndBackupAttributes(new Date(), cal.getTime());
    ECEntityAttributesUtil.setAttributes(f, iab);
	
    return res;                
  }
}