Working with Annotation Objects

The following code examples demonstrate annotation-related operations. The examples for creating an annotation for an object are generic examples. In practice, annotations typically use some custom subclass, a specific content type for the content elements, or both. Because annotations are meant to be created and interpreted by a particular client application, the content of the Annotation object usually follows a format specific to that application. The Content Engine does not interpret the meaning of the content or any custom properties; therefore, the provided code examples are probably not sufficient for creating an Annotation object that is understood by client applications.

For an overview of annotations, see Annotations.

Annotating a Document

The following Java™ and C# examples show how to add an Annotation object to a document. An Annotation object is created and its properties are set, including the AnnotatedContentElement property. This optional property associates the annotation with a particular content element of the document.

Note: To set a specific document content element on an Annotation object's AnnotatedContentElement property, specify the ElementSequenceNumber property of the content element rather than the content element's indexed position in the ContentElementList object.

In these examples, the Annotation object is created directly with a Factory method. However, the Document interface inherits a convenience method, createAnnotation, that indirectly creates the Annotation object and sets a specified document content element on the Annotation object.

Java Example


// Create property filter for document's content elements, 
// which are needed to get element sequence numbers that identify elements.
PropertyFilter pf = new PropertyFilter();       
pf.addIncludeProperty(new FilterElement(null, null, null, PropertyNames.CONTENT_ELEMENTS, null));

// Fetch Document object.
Document doc=Factory.Document.fetchInstance(os, "{2A96C18B-CF94-4853-B105-3F0553A37D7F}", pf);

// Get element sequence number of 1st content element of the document.
   ContentElementList docContentList = doc.get_ContentElements();
   Integer elementSequenceNumber = ((ContentElement) docContentList.get(0)).get_ElementSequenceNumber();

// Create annotation.
   Annotation annObject = Factory.Annotation.createInstance(os, "Annotation");

// Set the Document object to which the annotation applies on the annotation.
   annObject.set_AnnotatedObject(doc);
                 
// Identify the document's ContentElement to which the annotation applies.
// The ContentElement is identified by its element sequence number.
   annObject.set_AnnotatedContentElement(elementSequenceNumber.intValue() );

// Set annotation's DescriptiveText property.
annObject.set_DescriptiveText("Annotation applied to the document's 1st content element.");

// Create File object with annotation content.
File annotationFile = new File("C:\\annotation.txt");

// Create ContentTransfer and ContentElementList objects for the annotation.
ContentTransfer ctObject = Factory.ContentTransfer.createInstance();
ContentElementList annContentList = Factory.ContentTransfer.createList();
try
{
   FileInputStream fileIS = new FileInputStream(annotationFile.getAbsolutePath());
   ctObject.setCaptureSource(fileIS);
}
catch (Exception e)
{
   System.out.println(e.getMessage() );
}

// Add ContentTransfer object to the list and set the list on the annotation.
annContentList.add(ctObject);
annObject.set_ContentElements(annContentList);

annObject.save(RefreshMode.REFRESH);

C# Example


// Create property filter for document's content elements, 
// which are needed to get element sequence numbers that identify elements.
PropertyFilter pf = new PropertyFilter();
pf.AddIncludeProperty(new FilterElement(null, null, null, PropertyNames.CONTENT_ELEMENTS, null));

// Fetch Document object.
IDocument doc=Factory.Document.FetchInstance(os, "{2A96C18B-CF94-4853-B105-3F0553A37D7F}", pf);

// Get element sequence number of 1st content element of the document.
IContentElementList docContentList = doc.ContentElements;
int elementSequenceNumber = (int)((IContentElement)docContentList[0]).ElementSequenceNumber;

// Create the annotation.
IAnnotation annObject = Factory.Annotation.CreateInstance(os, "Annotation");

// Set the Document object to which the annotation applies on the annotation.
annObject.AnnotatedObject = doc;
                
// Specify the document's ContentElement to which the annotation applies.
// The ContentElement is identified by its element sequence number.
annObject.AnnotatedContentElement = elementSequenceNumber;
                
// Set annotation's DescriptiveText property.
annObject.DescriptiveText = "Annotation applied to the document's 1st content element.";

// Create Stream object with annotation content.
Stream fileStream = File.OpenRead(@"C:\\annotation.txt");

// Create ContentTransfer and ContentElementList objects for the annotation.
IContentTransfer ctObject = Factory.ContentTransfer.CreateInstance();
IContentElementList annContentList = Factory.ContentTransfer.CreateList();
ctObject.SetCaptureSource(fileStream);

// Add ContentTransfer object to list and set the list on the annotation.
annContentList.Add(ctObject);
annObject.ContentElements = annContentList;

annObject.Save(RefreshMode.REFRESH);
      

Annotating a Folder

The following Java and C# examples show how to add an Annotation object to a folder. The procedure is similar for a custom object.

Java Example


// Get Folder object.
Folder folder=Factory.Folder.getInstance(os, "Folder", new Id("{1A570F51-63B9-4600-B717-03D44ECC54A1}") );

// Create Annotation object and set Folder as the annotated object.
Annotation annObject = Factory.Annotation.createInstance(os, "Annotation");
annObject.set_DescriptiveText("Annotation applied to folder via Content Engine Java API");
annObject.set_AnnotatedObject(folder);

// Create File object with annotation content.
File annotationFile = new File("C:\\annotation.txt");

// Create ContentTransfer and ContentElementList objects for the annotation.
ContentTransfer ctObject = Factory.ContentTransfer.createInstance();
ContentElementList contentList = Factory.ContentTransfer.createList();
try 
{
   FileInputStream fileIS = new FileInputStream(annotationFile.getAbsolutePath());
   ctObject.setCaptureSource(fileIS);
}
catch (Exception e)
{
    System.out.println(e.getMessage() );
}

// Add ContentTransfer object to list.
contentList.add(ctObject);
annObject.set_ContentElements(contentList);

annObject.save(RefreshMode.REFRESH);

C# Example


// Get Folder object.
IFolder folder=Factory.Folder.GetInstance(os, "Folder", new Id("{1A570F51-63B9-4600-B717-03D44ECC54A1}") );

// Create Annotation object and set Folder as the annotated object.
IAnnotation annObject = Factory.Annotation.CreateInstance(os, "Annotation");
annObject.DescriptiveText = "Annotation applied to folder via Content Engine .NET API";
annObject.AnnotatedObject = folder;

// Create Stream object with annotation content.
Stream fileStream = File.OpenRead(@"C:\\annotation.txt");

// Create ContentTransfer and ContentElementList objects for the annotation.
IContentTransfer ctObject = Factory.ContentTransfer.CreateInstance();
IContentElementList contentList = Factory.ContentTransfer.CreateList();
ctObject.SetCaptureSource(fileStream);

// Add ContentTransfer object to list.
contentList.Add(ctObject);
annObject.ContentElements = contentList;

annObject.Save(RefreshMode.REFRESH);
      

Retrieving Annotations

The following Java and C# examples show how to retrieve the annotations that are applied to a Document object. The code gets the AnnotationSet object from the Document object and iterates the set, printing property values of each Annotation object in the set. Because an annotation can have more than one content element, the code also iterates the StringList object of the Annotation object's ContentElementsPresent property.

Java Example


// Create PropertyFilter for annotations.
PropertyFilter pf = new PropertyFilter();
pf.addIncludeProperty(new FilterElement(null, null, null, "Annotations", null)); 

// Get document.
Document doc=Factory.Document.fetchInstance(os, "{2A96C18B-CF94-4853-B105-3F0553A37D7F}", pf);

// Get AnnotationSet from document.
// Iterate AnnotationSet and print property values of each annotation.
AnnotationSet as = doc.get_Annotations();
Iterator iter = as.iterator();
while (iter.hasNext() )
{
   Annotation annObject = (Annotation)iter.next();
   System.out.println("Name: " + annObject.get_Name() + "\n" +
      "Description: " + annObject.get_DescriptiveText() + "\n" +
      "Document's content element that's annotated: " + annObject.get_AnnotatedContentElement() + "\n" +
      "Content size: " + annObject.get_ContentSize().toString() + "\n" +
      "Storage area: " + annObject.get_StorageArea().get_DisplayName() + "\n" +
      "No. of content elements in annotation: " + annObject.get_ContentElements().size()
   );
   // Print the MIME type of the annotation's content element.
   // Some annotations may have more than one content element.
   StringList sl = annObject.get_ContentElementsPresent();
   for(int i=0; i < sl.size(); i++ )
   {
      System.out.println("MIME type of annotation content element #" + (i+1) + ": " + 
          sl.get(i) );
   }
}

C# Example


// Create PropertyFilter for annotations.
PropertyFilter pf = new PropertyFilter();
pf.AddIncludeProperty(new FilterElement(null, null, null, PropertyNames.ANNOTATIONS, null)); 

// Get document.
IDocument doc=Factory.Document.FetchInstance(os, "{2A96C18B-CF94-4853-B105-3F0553A37D7F}", pf);

// Get IAnnotationSet from document.
// Iterate IAnnotationSet and print property values of each annotation.
IAnnotationSet annSet = doc.Annotations;
System.Collections.IEnumerator annSetIter = annSet.GetEnumerator();
while (annSetIter.MoveNext())
{
   IAnnotation annObject = (IAnnotation)annSetIter.Current; 
   System.Console.WriteLine("Name: " + annObject.Name + "\n" +
      "Description: " + annObject.DescriptiveText + "\n" +
      "Document's content element that's annotated: " + annObject.AnnotatedContentElement + "\n" +
      "Content size: " + annObject.ContentSize.ToString() + "\n" +
      "Storage area: " + annObject.StorageArea.DisplayName + "\n" +
      "No. of content elements in annotation: " + annObject.ContentElements.Count
   );
   // Print the MIME type of the annotation's content element.
   // Some annotations may have more than one content element.
   IStringList sl = annObject.ContentElementsPresent;
   for(int i=0; i < sl.Count; i++ )
   {
      System.Console.WriteLine("MIME type of annotation content element #" + (i+1) + ": " + 
          sl[i] );
   }
}

Moving Content to a Different Storage Area

The following Java and C# examples show how to move the content of an Annotation object to a different storage area, in this case an area that is represented by a FileStorageArea object.

Java Example


// Get the storage area where you want to move the annotation content.
FileStorageArea fsa = Factory.FileStorageArea.fetchInstance(os, new Id("{AF44998A-9779-464E-AC23-E94F6E14D79B}"), null);

// Get the Annotation object whose content you want to move.
Annotation annObject = Factory.Annotation.getInstance(os, "Annotation", new Id("{CD467BBD-5CC1-44A4-9842-13368996BDD6}") );

// Move the content and save the Annotation object.
annObject.moveContent(fsa);
annObject.save(RefreshMode.REFRESH);

C# Example


// Get the storage area where you want to move the annotation content.
IFileStorageArea fsa = Factory.FileStorageArea.FetchInstance(os, new Id("{AF44998A-9779-464E-AC23-E94F6E14D79B}"), null);

// Get the IAnnotation object whose content you want to move.
IAnnotation annObject = Factory.Annotation.GetInstance(os, "Annotation", new Id("{CD467BBD-5CC1-44A4-9842-13368996BDD6}") );

// Move the content and save the IAnnotation object.
annObject.MoveContent(fsa);
annObject.Save(RefreshMode.REFRESH);
      

Creating an Annotation Subclass

The following Java and C# examples show how to create a subclass from the base, system-provided Annotation class. In the examples, the subclass is named Proposal because annotations of this class are intended for a proposal document for which comments are solicited. A custom property of type PropertyTemplateBoolean is added to the subclass. The custom property, called Change Type, is defined as required, and its purpose is to indicate to the proposal writer whether the annotation is merely a suggestion or a required change. Therefore, when a reviewer sets an annotation on the proposal document, the reviewer must set the Change Type property.

The new annotation subclass can be used to create new annotation instances, as shown in Creating a New Subclass Instance. The new subclass can also be applied to existing instances that are defined by different annotation classes, as shown in Changing an Annotation's Class.

Java Example


// Fetch selected class definition from the server.
ClassDefinition parentClassDef = Factory.ClassDefinition.fetchInstance(os, "Annotation", null);

String subclassName = "Proposal";
      
// Create subclass of the Annotation class.
ClassDefinition newClassDef = parentClassDef.createSubclass();
        
// Set required properties to locale-specific string.
LocalizedString locStr1 = getLocalizedString(subclassName, os.get_LocaleName() );
newClassDef.set_DisplayNames(Factory.LocalizedString.createList() );
newClassDef.get_DisplayNames().add(locStr1);
    
LocalizedString locStr2 = getLocalizedString("For annotations specific to Proposal document", 
       os.get_LocaleName() );
newClassDef.set_DescriptiveTexts(Factory.LocalizedString.createList() );
newClassDef.get_DescriptiveTexts().add(locStr2);
        
// Create new PropertyTemplate for boolean property with required value.
PropertyTemplateBoolean newPropTemplate = Factory.PropertyTemplateBoolean.createInstance(os);
newPropTemplate.set_Cardinality (Cardinality.SINGLE);
newPropTemplate.set_IsValueRequired(Boolean.TRUE);

// Set required properties to locale-specific string.
LocalizedString locStr3 = getLocalizedString("Change Type", os.get_LocaleName() );
newPropTemplate.set_DisplayNames (Factory.LocalizedString.createList() );
newPropTemplate.get_DisplayNames().add(locStr3);
        
LocalizedString locStr4 = getLocalizedString("Indicates suggested or required change to proposal",
       os.get_LocaleName() );
newPropTemplate.set_DescriptiveTexts(Factory.LocalizedString.createList() );
newPropTemplate.get_DescriptiveTexts().add(locStr4);
        
// Save property template to the server.
newPropTemplate.save(RefreshMode.REFRESH);

// Create property definition from property template.
PropertyDefinitionBoolean newPropDef = (PropertyDefinitionBoolean)newPropTemplate.createClassProperty();
        
// Get PropertyDefinitions property from the property cache.
PropertyDefinitionList propDefs = newClassDef.get_PropertyDefinitions(); 

// Add new property definition to class definition.
propDefs.add(newPropDef);

newClassDef.save(RefreshMode.REFRESH); 

private LocalizedString getLocalizedString(String text, String locale)
{
   LocalizedString locStr = Factory.LocalizedString.createInstance ();
   locStr.set_LocalizedText(text);
   locStr.set_LocaleName (locale);
   return locStr;
}

C# Example


// Fetch selected class definition from the server.
IClassDefinition parentClassDef = Factory.ClassDefinition.FetchInstance(os, "Annotation", null);

String subclassName = "Proposal";

// Create subclass of the Annotation class.
IClassDefinition newClassDef = parentClassDef.CreateSubclass();

// Set required properties to locale-specific string.
ILocalizedString locStr1 = GetLocalizedString(subclassName, os.LocaleName);
newClassDef.DisplayNames = Factory.LocalizedString.CreateList();
newClassDef.DisplayNames.Add(locStr1);

ILocalizedString locStr2 = GetLocalizedString("For annotations specific to Proposal document",
       os.LocaleName);
newClassDef.DescriptiveTexts = Factory.LocalizedString.CreateList();
newClassDef.DescriptiveTexts.Add(locStr2);

// Create new PropertyTemplate for boolean property with required value.
IPropertyTemplateBoolean newPropTemplate = Factory.PropertyTemplateBoolean.CreateInstance(os);
newPropTemplate.Cardinality = Cardinality.SINGLE;
newPropTemplate.IsValueRequired = true;

// Set required properties to locale-specific string.
ILocalizedString locStr3 = GetLocalizedString("Change Type", os.LocaleName);
newPropTemplate.DisplayNames = Factory.LocalizedString.CreateList();
newPropTemplate.DisplayNames.Add(locStr3);

ILocalizedString locStr4 = GetLocalizedString("Indicates suggested or required change to proposal",
       os.LocaleName);
newPropTemplate.DescriptiveTexts = Factory.LocalizedString.CreateList();
newPropTemplate.DescriptiveTexts.Add(locStr4);

// Save property template to the server.
newPropTemplate.Save(RefreshMode.REFRESH);

// Create property definition from property template.
IPropertyDefinitionBoolean newPropDef = (IPropertyDefinitionBoolean)newPropTemplate.CreateClassProperty();

// Get PropertyDefinitions property from the property cache.
IPropertyDefinitionList propDefs = newClassDef.PropertyDefinitions;

// Add new property definition to class definition.
propDefs.Add(newPropDef);

newClassDef.Save(RefreshMode.REFRESH);

private ILocalizedString GetLocalizedString(String text, String locale)
{
   ILocalizedString locStr = Factory.LocalizedString.CreateInstance();
   locStr.LocalizedText = text;
   locStr.LocaleName = locale;
   return locStr;
}

Creating a New Subclass Instance

As demonstrated in Creating an Annotation Subclass above, a subclass that is called Proposal was created. The following Java and C# examples show how to create a new annotation by using the Proposal subclass, and to set it on a document. The Proposal subclass includes a custom property that is required to be set on a new annotation; if it is not set, an exception is thrown when an attempt is made to save the new annotation.

Java Example


// Get proposal document.
Document doc=Factory.Document.getInstance(os, "Document", new Id("{512D355A-8DBF-40D4-9485-683CBDF3C860}") );

// Create new annotation of type Proposal, and set it on the proposal document.
Annotation annObject = Factory.Annotation.createInstance(os, "Proposal");
annObject.set_DescriptiveText("Annotation of class type Proposal");
annObject.set_AnnotatedObject(doc);

// Get Proposal class in order to identify the required custom property.
ClassDefinition newClass = Factory.ClassDefinition.fetchInstance(os, "Proposal", null);
PropertyDefinitionList pdl = newClass.get_PropertyDefinitions();
Iterator iter = pdl.iterator();
String custProperty=null;
while (iter.hasNext() )
{
    PropertyDefinition pd = (PropertyDefinition)iter.next();
    if (!pd.get_IsSystemOwned().booleanValue())
    {
        System.out.println("Custom property: " + pd.get_DisplayName() );
        System.out.println("Value required?: " + pd.get_IsValueRequired() );
        custProperty = pd.get_SymbolicName();
    }
}

// Set required custom property on new annotation.
com.filenet.api.property.Properties props = annObject.getProperties();
props.putValue(custProperty, true);
annObject.save(RefreshMode.REFRESH);

C# Example


// Get proposal document.
IDocument doc=Factory.Document.GetInstance(os, "Document", new Id("{512D355A-8DBF-40D4-9485-683CBDF3C860}") );

// Create new annotation of type Proposal, and set it on the proposal document.
IAnnotation annObject = Factory.Annotation.CreateInstance(os, "Proposal");
annObject.DescriptiveText = "Annotation of class type Proposal";
annObject.AnnotatedObject = doc;

// Get Proposal class in order to identify the required custom property.
IClassDefinition newClass = Factory.ClassDefinition.FetchInstance(os, "Proposal", null);
IPropertyDefinitionList pdl = newClass.PropertyDefinitions;
System.Collections.IEnumerator Iter = pdl.GetEnumerator();
String custProperty=null;
while (Iter.MoveNext())
{
   IPropertyDefinition pd = (IPropertyDefinition)Iter.Current;
   if (!(bool)pd.IsSystemOwned)
   {
      System.Console.WriteLine("Custom property: " + pd.DisplayName);
      System.Console.WriteLine("Value required?: " + pd.IsValueRequired);
      custProperty = pd.SymbolicName;
   }
}

// Set required custom property on new annotation.
annObject.Properties[custProperty] = true;
annObject.Save(RefreshMode.REFRESH);

Changing an Annotation's Class

As demonstrated in Creating an Annotation Subclass above, a subclass that is called Proposal was created. The following Java and C# examples show how to set this new annotation subclass on an existing annotation instance by changing the annotation's class. Note that the property settings of the existing annotation instance are unaffected by the new subclass. In addition, the required custom property of the Proposal subclass, called Change Type, does not need to be set on the existing instance.

Java Example


// Get annotation whose class you want to change.
Annotation annObject = Factory.Annotation.getInstance(os, "Annotation", new Id("{A59A7728-1804-499B-B564-7F6C5443174F}") );

// Change the class by specifying the ID of the new "Proposal" subclass,
// and save the annotation.
annObject.changeClass("{0E628F78-AF62-49D9-88CB-18B6F4E89210}");
annObject.save(RefreshMode.REFRESH);

C# Example


// Get annotation whose class you want to change.
IAnnotation annObject = Factory.Annotation.GetInstance(os, "Annotation", new Id("{A59A7728-1804-499B-B564-7F6C5443174F}"));

// Change the class by specifying the ID of the new "Proposal" subclass,
// and save the annotation.
annObject.ChangeClass("{0E628F78-AF62-49D9-88CB-18B6F4E89210}");
annObject.Save(RefreshMode.REFRESH);
      

Searching for Annotation Text

Searching for indexed annotation text is no different from searching for indexed document content. (See Searching for Content in the Working with Queries section.) The following SQL statements illustrate how to use the CONTAINS operator to retrieve indexed annotation text, in this case, the word "test".


SELECT anno.This FROM Annotation anno INNER JOIN ContentSearch c ON anno.This = c.QueriedObject WHERE CONTAINS(anno.*, 'test')

The following SQL statement searches for an indexed property string of an annotation (but would be the same for a folder property string):


SELECT anno.This FROM Annotation anno INNER JOIN ContentSearch c ON anno.This = c.QueriedObject WHERE CONTAINS(IndexedPropString1, 'test')