Skip to main content

Discovering CMIS Type Collections and Hierarchies

Posted by manning_pubs on September 19, 2012 at 5:28 AM PDT



by Florian Müller, Jay Brown, and Jeff Potts, authors of CMIS and Apache Chemistry in Action

Save 40% on CMIS and Apache Chemistry in Action. Use promo code 12netja when you check out at manning.com

Enterprise Content Management (ECM) repositories often have a lot of types defined, sometimes hundreds in extreme cases. This means that there needs to be a way to organize and retrieve them in a scalable fashion. Some repository designs treat these type collections as a large flat list, while others treat them as a hierarchy. This article covers type collections and how to navigate, retrieve, and examine them with two code examples showing basic techniques for each.

NOTE: The code examples assume the reader has the Apache Chemistry Workbench running and that they have access to a public or local CMIS repository. You’ll find step-by-step instructions on setting up these prerequisite tools in Chapter 1 of CMIS and Apache Chemistry in Action. All the code in this article is in Groovy and can be executed exactly as shown in the Console window of the Apache Chemistry Workbench.

Content Management Interoperability Services (CMIS) defines a hierarchy to organize all of the type objects. If an underlying repository only has a flat list, then the objects would be exposed through CMIS as a hierarchy with a depth of one. Figure 1 shows the type hierarchy.

base hierarchy

Figure 1 CMIS metadata base hierarchy for types and property definitions showing their attributes for each

This is not meant to be the sum of the hierarchy, but rather just a starting point that is common to all repositories. Each of these base types can have child and grandchild types going down as deep and as wide as is necessary for each application.

Try it! Traversing the type hierarchy

Understanding how all of these types are laid out is the first step. Now that we have this down, it’s time to show you how your application can programmatically discover anything it needs to know about the metadata in a repository. For this, we are going back to our trusty Groovy Console and show how simple this seemingly complex operation can be. In this step, we are going to traverse the hierarchy of objects looking at their attributes along the way. We will even display attributes that are only present on Documents so that you can see how easy it is to determine the type of these objects. Even though we don’t examine all of the attributes that are available in this short example, you will notice that all of the attributes listed in the CMIS specification are accessible as getter methods on the various classes of type objects. For examples, please see the Javadocs for OpenCMIS here.

Listing 1 getTypeDescendants code example (type walker)

import org.apache.chemistry.opencmis.client.api.*
import org.apache.chemistry.opencmis.commons.enums.*

      boolean includePropertyDefintions = true;
      for (t in session.getTypeDescendants(
            null, // start at the top of the tree
            -1, // infinite depth recursion
            includePropertyDefintions // include prop defs
            )) {
         printTypes(t, "");
      }

   static void printTypes(Tree tree, String tab) {          #1
      ObjectType objType = tree.getItem();
      println(tab + "TYPE:" + objType.getDisplayName() +
            " (" + objType.getDescription() + ")");
      // Print some of the common attributes for this type
      print(tab + " Id:" + objType.getId());                            #2
      print(" Fileable:" + objType.isFileable());
      print(" Queryable:" + objType.isQueryable());

      if (objType instanceof DocumentType) {                            #3
         print(" [DOC Attrs->] Versionable:" +
            ((DocumentType)objType).isVersionable());
         print(" Content:" +
            ((DocumentType)objType).getContentStreamAllowed());
      }
      println(""); // end the line
      for (t in tree.getChildren()) {
         // there are more - call self for next level
         printTypes(t, tab + " ");
      }
   }


#1 Recursing the directory hierarchy.

#2 Print some of the attributes that are common to all types first.

#3 Show contentStreamAllowed and isVersionable only if the type is of type DocumentType.

If you look at the output from our type walker code (see figure 2), you will see the child levels expanded and each level indented to show the hierarchy visually.

getTypeDescendants output

Figure 2 Output from our getTypeDescendants code (type walker)

Now that we have gotten types and their attributes all figured out, let’s move on to the next exercise where we will expand our example to show property definitions and their attributes.

Try it! Examining property definitions on types

Now we are going to modify the type walker example and add in some code to walk all of the property definitions for each type. For each type, we will display a few key attributes like the property’s id, data type, and updateability. The code example below (listing 2) shows the modified version, type walker 2. Basically, we just add a new method, printPropDefsForType, which is called in the type loop. As you can see, it is trivially easy to get this information from the type object using OpenCMIS.

Listing 2 getTypeDescendants with property definitions code example (type walker v2)

import org.apache.chemistry.opencmis.client.api.*
import org.apache.chemistry.opencmis.commons.enums.*
import org.apache.chemistry.opencmis.commons.definitions.*             #1

boolean includePropertyDefintions = true;
for (t in session.getTypeDescendants(
    null, // match all types
    -1, // infinite depth recursion
    includePropertyDefintions // include prop defs
    )) {
printTypes(t, "");
}

static void printTypes(Tree tree, String tab) {
  ObjectType objType = tree.getItem();
  println(tab + "TYPE:" + objType.getDisplayName() +
        " (" + objType.getDescription() + ")");
  // Print some of the common attributes for this type
  print(tab + " Id:" + objType.getId());
  print(" Fileable:" + objType.isFileable());
  print(" Queryable:" + objType.isQueryable());

  if (objType.getBaseTypeId().equals(BaseTypeId.CMIS_DOCUMENT)) {
     print(" [DOC Attrs->] Versionable:" +
        ((DocumentType)objType).isVersionable());
     print(" Content:" +
        ((DocumentType)objType).getContentStreamAllowed());
  }
  println(""); // end the line
  printPropDefsForType(objType, tab);                                  #2

  for (t in tree.getChildren()) {
     // there are more - call self for next level
     printTypes(t, tab + " ");
  }
}

static void printPropDefsForType(ObjectType type, String tab) {
  Map> mapDefs =
     type.getPropertyDefinitions();                                    #3

  for (key in mapDefs.keySet()) {
     print(tab + " " + key + "->");
     PropertyDefinition defn = mapDefs.get(key);
     print(" Id:[" + defn.getId() + "]");
     print(" dataType:[" + defn.getPropertyType() + "]");
     println(" updateable:[" + defn.getUpdatability()+"]");
  }
}


#1 We need to add one more include for our PropertyDefinition object from OpenCMIS since it is referenced in our new printPropDefsForType method.

#2 Hook in a call to our new printPropDefsForType method after all of the type attributes are done printing but before we recurse further.

#3 GetPropertyDefinitions returns a map of all of the property definitions for this type keyed by the associated property name. This key choice will come in handy later.

Figure 3 shows the output of type walker v2. The figure shows the complete output for the single type named audioFile with the other types omitted for space.

getTypeDescendants truncated

Figure 3 Truncated output from our getTypeDescendants code with property definitions included (type walker v2)

This example has two parts because there are two very different ways of getting at the Type and PropertyDefinition objects in OpenCMIS. In listing 2, we retrieved our types from the types collection and walked the types tree directly.

Sometimes, however, it is a lot more convenient to just get the type object and/or corresponding property definition objects for a particular instance object that you have in hand, and not even worry about its type’s location in the types hierarchy.

Listing 3 shows how to do this using the root folder object as a generic example. Note that this technique will work for any CMIS object that you can encounter.

System and custom properties

When talking about properties in CMIS, some will refer to custom and system properties. These terms can have different meanings in different contexts, but, in the purest CMIS context, system properties usually refer to those properties that are defined in the specification, namely, the ones that you see in all of our examples and look like cmis:xxx (for example, cmis:objectId). Custom properties are everything else. Since custom properties are not defined by the spec, they are, therefore repository, (and type) specific. Repository developers should note that you should not use the cmis: prefix for naming any of your custom repository’s properties. That prefix (cmis:) is reserved for properties defined in the specification.

Listing 3 Retrieving and objects type and property definitions directly from the object instance

import org.apache.chemistry.opencmis.commons.*
import org.apache.chemistry.opencmis.commons.data.*
import org.apache.chemistry.opencmis.commons.enums.*
import org.apache.chemistry.opencmis.client.api.*
import org.apache.chemistry.opencmis.commons.definitions.*

// obtain the root folder instance object from the session
   Folder rootFolder = session.getRootFolder();

// this is how you get its type directly from the instance object
ObjectType typeObj = rootFolder.getType();                              #1
println("Id of folder’s type:" + typeObj.getId());

int DefCount = typeObj.getPropertyDefinitions().entrySet().size();
println("Prop definition total:" + DefCount);

// how to get property definitions directly from the property instance
// by just looking at the defs for the properties that are present
List> props = rootFolder.getProperties();
int propCount = props.size();                                           #2
println("Property count:" + propCount);
for (prop in props) {
   PropertyDefinition propDef = prop.getDefinition();                #3
   println(" property:" + prop.getDisplayName() +
         " id[" + propDef.getId() + "]");
}
/


#1 Anytime you have an object instance, you can always grab its type directly with the getType() method. By default OpenCMIS will retrieve this for you from cache if it is already present.

#2 You will note from this example that the amount of properties and property definitions are the same. (see output ) This will not always be the case. There can often be more definitions than properties in cases where there are unset (and not required) properties, or you used a property filter to omit select properties.

#3 Much like the getType() method, getDefinition() can be called on any Property object and the definition will be retrieved from cache if possible.

If you take a look at the output in figure 4 you can see that the number of property definitions that were defined on the cmis:folder type matches the number of properties that were on the instance of the folder object. See the callouts for the example for a discussion of why this is not always the case.

type and property definition

Figure 4 Output showing how to get type and property definition info directly from the instance object

Constraints on property definitions

The last aspect of property definitions that we need to explore is the concept of constraints. Aside from telling us what type of data the property holds and its cardinality, a property definition may also place constraints on the potential values as well. Constraints break down into two main groups as shown next.

Common constraints of property definitions

Here is a quick rundown of the constraints that can be present on any of the eight property definition object types:

  • choices—An explicit ordered set of values that are permissible for this property. For example, a string property definition named PrimaryColors might have choices = [Red, Green, Blue]. Each choice includes a displayName and a value. The displayName may be used by clients for presentation purposes.
  • property—For example a string property definition named PrimaryColors might have choices = [Red, Green, Blue]. Each choice includes a displayName and a value. The displayName may be used by clients for presentation purposes.
  • openchoice (boolean)—This attribute is only applicable to properties that provide a value for the choices attribute discussed above. If FALSE, then the data value for the property must only be one of the values specified in the choices attribute. If TRUE, then values other than those included in the choices attribute may be set for the property.
  • defaultvalue—Contains the value that the repository must set for the property if one is not provided at object creation time. If a property is set to required and does not have a default value, then an attempt to create an object with this property not set will result in throwing a constraint exception.

Property specific types of constraints

There are also four additional types of constraints that are possible for specific property types:

  • minValue and maxValue—Applies to Integer and Decimal property types only. Specifies the minimum and maximum values permitted for this property. If an application tries to set the value of this property to a value outside of this value, then the repository must throw a constraint exception.
  • maxLength—Applies to String property types only. Specifies the maximum length (in characters) allowed for a value of this property. If an application attempts to set the value of this property to a string longer than the specified maximum length, the repository must throw a constraint exception.
  • resolution—This is an enum (see list below) that applies to property definitions of DateTime only. Each value shown below implies all of the values above it, like bit flags. For example if the value of time is present this implies that time, date and year are persisted. The permitted values for this enum are:
    • year—Year resolution is persisted. Date and time portion of the value should be ignored.
    • date—Date resolution is persisted. Time portion of the value should be ignored.
    • time—Time resolution is persisted.
  • precision—This is an enum (see list below) that applies to property definitions of Decimal only. The permitted values for this enum are:
    • 32: 32-bit precision will be used (“single” as specified in IEEE-754-1985).
    • 64—64-bit precision will be used (“double” as specified in IEEE-754-1985).

    Next up we will exercise a bit of these using the workbench console.

    Try it! Examining constraints on property definitions

    Ready to see how this all looks in code? Let’s go back to the workbench again and have a look at this example. We have augmented one of the previous examples to also show choice lists, default values, and the integer specific constraint maxValue.

    Listing 4 shows how to do this using the root folder object as a generic example. Note that this technique will work for any CMIS object that you can encounter.

    Listing 4 Examining the constraints on property definitions

    import org.apache.chemistry.opencmis.client.api.*
    import org.apache.chemistry.opencmis.commons.enums.*
    import org.apache.chemistry.opencmis.commons.definitions.*

    ObjectType complex = session.getTypeDefinition("cmisbook:audio"); //
    printPropDefsForTypeWithWithContraints(complex, "");

    static void printPropDefsForTypeWithWithContraints(ObjectType type,
          String tab) {
       Map> mapDefs = type                   #1
             .getPropertyDefinitions();
       for (key in mapDefs.keySet()) {
          print(tab + " " + key + "->");
          PropertyDefinition defn = mapDefs.get(key);
          print(" Id:[" + defn.getId() + "]");
          print(" dataType:[" + defn.getPropertyType() + "]");
          println(" updateable:["+defn.getUpdatability()+"]");

          // show min max constraint test on integer type
          if (defn.getPropertyType().equals(PropertyType.INTEGER)) { //    #2
             PropertyIntegerDefinition propDefInt =
                   (PropertyIntegerDefinition) defn;
             if (propDefInt.getMaxValue() != null) {
                println(" Max value:"
                  + propDefInt.getMaxValue());
             }
          }

          // list default value if present
          if (defn.getDefaultValue() != null) {
             println(" default value:["
                   + defn.getDefaultValue().get(0) + "]"); //              #3
          }

          // list choices if present
          if (defn.getChoices().size() > 0) {
             // there are choices on this property
             print(" choice present: values:[");
             List choices = defn.getChoices();
             Cardinality card = defn.getCardinality();
             for (choice in choices) {
                if (card.equals(Cardinality.SINGLE)) {
                   print(choice.getValue().get(0) + " "); //               #4
             } else {
                 // code to iterate through all values in
                 // choice.getValue() if this was a
                 // multivalued choice.
             }
           }
           println("]");
        }
      }
    }
    /


    #1 Here we are going to just grab the type we want for this example directly by its id property rather than navigating for it like we have in the past.

    #2 This is the way you would check for type specific constraints. We are not going to show them all since the method is the same. First you determine the data type of the definition then cast it into the specific definition type to get at the data type specific methods.

    #3 For brevity we are assuming the default value is a single value. On the odd case where there was a multivalued default, there would be more values present in this array.

    #4 To get the value we call choice.getValue, but if this choice list were hierarchical then come of the choice elements could have another choice list below. In this case the choice.getValue will be an empty list and the choice.getChoice will return another choice list.

    Figure 5 shows the output pane from the OpenCMIS Workbench console window.

    choice lists and default values

    Figure 5 Truncated output from the code in the listing, showing choice lists and default values

    Attribute and attribute value inheritance

    Before we get to the new 1.1 metadata features we need to clarify one more thing relating to inheritance and attributes. You may recall the hierarchy of the CMIS type definitions and the attributes that are inherited from the base CMIS object type. An object type will inherit all of its parent type’s attributes but the values of the attribute are not inherited. Let’s consider the versionable attribute of cmis:document to illustrate this. All subtypes of cmis:document in a repository must have the versionable attribute that was introduced at the cmis:document level. However the specific boolean value of versionable for each of those sub types is set independently. So in a sample repository cmis:document might be versionable=true, and still have a subtype named invoiceDocument that had fileable=false.

    Summary

    In this article, you learned about some of the basic conceptual building blocks for metadata in CMIS systems. You also learned how to programmatically get at this information using the Apache Chemistry OpenCMIS API. Specifically, we explained CMIS types, property definitions and the attributes that describe them. We also learned about the different types of constraints that can be present on these types.


    Here are some other Manning titles you might be interested in:

    Hadoop in Practice

    Hadoop in Practice
    Alex Holmes

    Big Data

    Big Data
    Nathan Marz

    Machine Learning in Action

    Machine Learning in Action
    Peter Harrington


    AttachmentSize
    cover.jpg11.27 KB
    image001.gif17.11 KB
    image002.gif104.44 KB
    image003.gif88.96 KB
    image004.gif32.69 KB
    image005.gif38.08 KB
    image006.jpg1.52 KB
    image007.jpg1.52 KB
    image008.jpg1.48 KB