Tree

Content that is organized into folders can be retrieved using the Tree API.

The Tree API lets you pull back an entire path-based folder and file structure of content within a single API call. The API call lets you specify a root node, a maximum depth to traverse down the path structure, paths that should be automatically expanded and query terms for filtering of root nodes.

The Tree API is deal to support a variety of cases including:

  • retrieval of multiple deeply-nested paths within a single call
  • support for a UI tree control that needs to refresh its state (both full and partial)
  • dynamic loading of specific nested-paths within a larger tree
  • filtering of content by path and query

Sample Data Structure

In order to demonstrate how the Tree API works, let's put in place an imaginary data set. Suppose we have the following content nodes:

  • { "title": "tyrion", "location": "dragonstone"}
  • { "title": "cersei", "location": "kingslanding"}
  • { "title": "jaime", "location": "kingslanding"}
  • { "title": "daenerys", "location": "dragonstone"}
  • { "title": "jon", "location": "winterfell"}
  • { "title": "arya", "location": "winterfell"}
  • { "title": "sansa", "location": "winterfell"}
  • { "title": "brandon", "location": "winterfell"}

And suppose these are organized into the following paths:

/shows/game-of-thrones/lannister/cersei
/shows/game-of-thrones/lannister/jaime
/shows/game-of-thrones/lannister/tyrion
/shows/game-of-thrones/stark/arya
/shows/game-of-thrones/stark/brandon
/shows/game-of-thrones/stark/sansa
/shows/game-of-thrones/targaryeon/daenerys
/shows/game-of-thrones/targaryeon/jon

Now let's see what we can do.

Tree API Request

The basic structure for a Tree API call is like this:

POST /repositories/{repositoryId}/branches/{branchId}/nodes/{nodeId}/tree

To execute the call, you must first identify the root node of the tree. Cloud CMS lets you work against at tree that starts at any root node that you wish. You can use the branch root / or you might use an offset of the branch root, such as /shows.

To identify the root node, you must supply:

  • repositoryId - the ID of the repository to work against
  • branchId - the ID of the branch to work against
  • nodeId - the ID of the node to work against (or use root to indicate the root node)

Thus, you could make a call like this:

POST /repositories/{repositoryId}/branches/{branchId}/nodes/root/tree

And pull back the entire tree - which essentially looks like this:

/
    shows/
        game-of-thrones/
            lannister/
                cersei
                jaime
                tyrion
            stark/
                arya
                brandon
                sansa
            targaryeon/
                daenerys
                jon

In addition, you may specify path as a request parameter to indicate an offset path from the root. For example, suppose you wanted to pull back a tree of everything under /shows/game-of-thrones. To do so, you can use the path parameter (just as with all the other Node API calls) to specify an offset from the root node - like this:

POST /repositories/{repositoryId}/branches/{branchId}/nodes/root/tree?path=/shows/game-of-thrones

And the results would essentially be:

game-of-thrones/
    lannister/
        cersei
        jaime
        tyrion
    stark/
        arya
        brandon
        sansa
    targaryeon/
        daenerys
        jon

Tree API Response

The exact API tree response is a JSON object. Depending on the options you set, it may come back with properties loaded, explicit paths expanded, depths traversed and so on. But more or less, for the query above, it may come back looking like this:

{
  "id" : "821c40ab613d9b5bcbbc656b62229301",
  "repositoryId" : "filefolder_tree_test2",
  "branchId" : "824da2339616ed245084",
  "rootNodeId" : "821c40ab613d9b5bcbbc656b62229301",
  "container" : true,
  "filename" : "",
  "label" : "root",
  "path" : "/",
  "description" : "Repository Root",
  "typeQName" : "n:root",
  "qname" : "r:root",
  "properties" : {
    "type" : "object",
    "description" : "Repository Root"
  },
  "children" : [ {
    "id" : "78961600fea78bd0b25b",
    "repositoryId" : "filefolder_tree_test2",
    "branchId" : "824da2339616ed245084",
    "rootNodeId" : "821c40ab613d9b5bcbbc656b62229301",
    "container" : true,
    "filename" : "shows",
    "label" : "shows",
    "path" : "/shows",
    "title" : "shows",
    "typeQName" : "n:node",
    "qname" : "o:78961600fea78bd0b25b",
    "properties" : {
      "title" : "shows"
    },
    "children" : [ {
      "id" : "a7d96e6722ee03e374e3",
      "repositoryId" : "filefolder_tree_test2",
      "branchId" : "824da2339616ed245084",
      "rootNodeId" : "821c40ab613d9b5bcbbc656b62229301",
      "container" : true,
      "filename" : "game-of-thrones",
      "label" : "game-of-thrones",
      "path" : "/shows/game-of-thrones",
      "title" : "game-of-thrones",
      "typeQName" : "n:node",
      "qname" : "o:a7d96e6722ee03e374e3",
      "properties" : {
        "title" : "game-of-thrones"
      },
      "children" : [ {
        "id" : "29d93e724e535d3b2bf0",
        "repositoryId" : "filefolder_tree_test2",
        "branchId" : "824da2339616ed245084",
        "rootNodeId" : "821c40ab613d9b5bcbbc656b62229301",
        "container" : true,
        "filename" : "stark",
        "label" : "stark",
        "path" : "/shows/game-of-thrones/stark",
        "title" : "stark",
        "typeQName" : "n:node",
        "qname" : "o:29d93e724e535d3b2bf0",
        "properties" : {
          "title" : "stark"
        },
        "children" : [ {
          "id" : "c7432422d859fedb901d",
          "repositoryId" : "filefolder_tree_test2",
          "branchId" : "824da2339616ed245084",
          "rootNodeId" : "821c40ab613d9b5bcbbc656b62229301",
          "container" : false,
          "filename" : "sansa",
          "label" : "sansa",
          "path" : "/shows/game-of-thrones/stark/sansa",
          "title" : "sansa",
          "typeQName" : "n:node",
          "qname" : "o:c7432422d859fedb901d",
          "properties" : {
            "title" : "sansa",
            "location" : "winterfell"
          }
        }, {
          "id" : "b946b22c6e396111f9e3",
          "repositoryId" : "filefolder_tree_test2",
          "branchId" : "824da2339616ed245084",
          "rootNodeId" : "821c40ab613d9b5bcbbc656b62229301",
          "container" : false,
          "filename" : "brandon",
          "label" : "brandon",
          "path" : "/shows/game-of-thrones/stark/brandon",
          "title" : "brandon",
          "typeQName" : "n:node",
          "qname" : "o:b946b22c6e396111f9e3",
          "properties" : {
            "title" : "brandon",
            "location" : "winterfell"
          }
        }, {
          "id" : "f124c3612b94f6717d6f",
          "repositoryId" : "filefolder_tree_test2",
          "branchId" : "824da2339616ed245084",
          "rootNodeId" : "821c40ab613d9b5bcbbc656b62229301",
          "container" : false,
          "filename" : "arya",
          "label" : "arya",
          "path" : "/shows/game-of-thrones/stark/arya",
          "title" : "arya",
          "typeQName" : "n:node",
          "qname" : "o:f124c3612b94f6717d6f",
          "properties" : {
            "title" : "arya",
            "location" : "winterfell"
          }
        } ],
        "childCount" : 3
      }, {
        "id" : "dd7d8f72389419e2a6c8",
        "repositoryId" : "filefolder_tree_test2",
        "branchId" : "824da2339616ed245084",
        "rootNodeId" : "821c40ab613d9b5bcbbc656b62229301",
        "container" : true,
        "filename" : "targaryeon",
        "label" : "targaryeon",
        "path" : "/shows/game-of-thrones/targaryeon",
        "title" : "targaryeon",
        "typeQName" : "n:node",
        "qname" : "o:dd7d8f72389419e2a6c8",
        "properties" : {
          "title" : "targaryeon"
        },
        "children" : [ {
          "id" : "874823083f56b7dd9e97",
          "repositoryId" : "filefolder_tree_test2",
          "branchId" : "824da2339616ed245084",
          "rootNodeId" : "821c40ab613d9b5bcbbc656b62229301",
          "container" : false,
          "filename" : "daenerys",
          "label" : "daenerys",
          "path" : "/shows/game-of-thrones/targaryeon/daenerys",
          "title" : "daenerys",
          "typeQName" : "n:node",
          "qname" : "o:874823083f56b7dd9e97",
          "properties" : {
            "title" : "daenerys",
            "location" : "dragonstone"
          }
        }, {
          "id" : "cdc3da075c11be87f4d5",
          "repositoryId" : "filefolder_tree_test2",
          "branchId" : "824da2339616ed245084",
          "rootNodeId" : "821c40ab613d9b5bcbbc656b62229301",
          "container" : false,
          "filename" : "jon",
          "label" : "jon",
          "path" : "/shows/game-of-thrones/targaryeon/jon",
          "title" : "jon",
          "typeQName" : "n:node",
          "qname" : "o:cdc3da075c11be87f4d5",
          "properties" : {
            "title" : "jon",
            "location" : "dragonstone"
          }
        } ],
        "childCount" : 2
      }, {
        "id" : "09847ca015714d0a8b2a",
        "repositoryId" : "filefolder_tree_test2",
        "branchId" : "824da2339616ed245084",
        "rootNodeId" : "821c40ab613d9b5bcbbc656b62229301",
        "container" : true,
        "filename" : "lannister",
        "label" : "lannister",
        "path" : "/shows/game-of-thrones/lannister",
        "title" : "lannister",
        "typeQName" : "n:node",
        "qname" : "o:09847ca015714d0a8b2a",
        "properties" : {
          "title" : "lannister"
        },
        "children" : [ {
          "id" : "b6856cacd4be79c17353",
          "repositoryId" : "filefolder_tree_test2",
          "branchId" : "824da2339616ed245084",
          "rootNodeId" : "821c40ab613d9b5bcbbc656b62229301",
          "container" : false,
          "filename" : "cersei",
          "label" : "cersei",
          "path" : "/shows/game-of-thrones/lannister/cersei",
          "title" : "cersei",
          "typeQName" : "n:node",
          "qname" : "o:b6856cacd4be79c17353",
          "properties" : {
            "title" : "cersei",
            "location" : "kingslanding"
          }
        }, {
          "id" : "108e36ee44ae48c0af23",
          "repositoryId" : "filefolder_tree_test2",
          "branchId" : "824da2339616ed245084",
          "rootNodeId" : "821c40ab613d9b5bcbbc656b62229301",
          "container" : false,
          "filename" : "tyrion",
          "label" : "tyrion",
          "path" : "/shows/game-of-thrones/lannister/tyrion",
          "title" : "tyrion",
          "typeQName" : "n:node",
          "qname" : "o:108e36ee44ae48c0af23",
          "properties" : {
            "title" : "tyrion",
            "location" : "kingslanding"
          }
        }, {
          "id" : "22e412565b03876efded",
          "repositoryId" : "filefolder_tree_test2",
          "branchId" : "824da2339616ed245084",
          "rootNodeId" : "821c40ab613d9b5bcbbc656b62229301",
          "container" : false,
          "filename" : "jaime",
          "label" : "jaime",
          "path" : "/shows/game-of-thrones/lannister/jaime",
          "title" : "jaime",
          "typeQName" : "n:node",
          "qname" : "o:22e412565b03876efded",
          "properties" : {
            "title" : "jaime",
            "location" : "kingslanding"
          }
        } ],
        "childCount" : 3
      } ],
      "childCount" : 3
    } ],
    "childCount" : 1
  } ],
  "childCount" : 1
}

Which is a nested tree structure with children arrays holding the contained child elements. Each element in the tree structure is a summary of the node present that layer in the tree. Each element contains the following required properties:

  • repositoryId, branchId and id - identify the repository, branch and node of the underlying content item
  • rootNodeId - the ID of the root of the repository
  • path - the path relative to the root for the current element
  • container - whether the current element is a container (folder)
  • filename - the filename for the current element
  • label - a display friendly label for the current element

If the element is backed by a Node (meaning that the Tree API call had sufficient privileges to read the node backing the element, then the following will be populated as well:

  • title - the node title (if available)
  • description - the node description (if available)
  • typeQName - the QName of the definition backing the node
  • qname - the QName of the node

If the element is a container, it will also provide the following:

  • children - an array of its children
  • childCount - the number of children in the array

Retrieving Node Properties

If you specify properties=true in your Tree API request, each element in the tree will additionally have a properties object with all of the properties of any node-backend elements that were able to be read.

  • properties - properties map

You can specify properties=true like this:

POST /repositories/{repositoryId}/branches/{branchId}/nodes/root/tree?properties=true

Depth Limiting

You can limit the depth of your tree by using the depth request property.

  • A depth of 1 limits the results to only the immediate children
  • A depth of 2 limits the results to immediate children and grandchildren
  • An unspecified depth hands back the full tree (without limits)

You can pass depth to the Tree API like this:

POST /repositories/{repositoryId}/branches/{branchId}/nodes/root/tree?depth=3

Suppose we set depth=3. Then we would essentially get back this:

/
    shows/
        game-of-thrones/
            lannister/
            stark/
            targaryeon/

Containers and Lazy Loading

Any nodes that are containers will have the container property set to true.

In addition, they will have the following properties set:

  • children - an array of the child nodes of the container
  • childCount - the size of the children array
  • loaded - whether the children array has been loaded or not

In the example above, we did a depth 3 lookup and came back with the following:

/
    shows/
        game-of-thrones/
            lannister/
            stark/
            targaryeon/

Note that:

  1. shows is at depth 1
  2. game-of-thrones is at depth 2
  3. lannister, stark and targaryeon are all at depth 3

All of these will have container set to true and have children and childCount properties on them.

The shows and game-of-thrones containers will have loaded set to true.

The lannister, stark and targaryeon will have loaded set to false. They will also have a children array of size 0.

This is because the depth was constrained to 3. As such, the children for the 3rd tier containers were not loaded and the children array was not populated. The loaded flag is set to false which provides a way for you to identify whether further lazy-loading may occur should you need to expand the tree further.

Containers-Only

At times, you may wish to only pull back a tree of folders and ignore or filter out any files. To do so, set the containers=true request parameter, like this:

POST /repositories/{repositoryId}/branches/{branchId}/nodes/root/tree?container=true

This will hand back a reduced tree consisting only of folders:

/
    shows/
        game-of-thrones/
            lannister/
            stark/
            targaryeon/

Query the Tree

You can query the tree results to filter down the result set to include only those members which match your query.

Suppose, for example, that you wanted to filter the tree to only include content that has location=dragonstone. To do so, you can pass a JSON object to the Tree API consisting of your query. The query is the same as any other query to Cloud CMS and conforms to the MongoDB query syntax.

POST /repositories/{repositoryId}/branches/{branchId}/nodes/root/tree

{
    "location": "dragonstone"
}

From a content perspective, this query matches the following:

  • { "title": "tyrion", "location": "dragonstone"}
  • { "title": "daenerys", "location": "dragonstone"}

Which corresponds to the paths:

/shows/game-of-thrones/lannister/tyrion
/shows/game-of-thrones/targaryeon/daenerys

And essentially, the following tree is produced:

/
    shows/
        game-of-thrones/
            lannister/
                tyrion
            targaryeon/
                daenerys

Note that the parent folders of any matching content are returned back. The parent structure back to the root node of your Tree API call will be returned so as to be consistent with everything else the Tree API does. It always returns things relative to the root node you specify.

That said, the elements for shows, game-of-thrones, lannister and targaryeon will be stripped to a minimum (as though there were not found). Only their core properties will be returned. The core properties consist of what is necessary so as to be useful for further manipulation or retrieval in additional calls.

Search the Tree

You can also search the tree results to filter down the result set. Searches execute against Elastic Search and allow you to take advantage of the Elastic Search DSL.

Suppose, for example, that you wanted to filter the tree to only include content where the word tyrion appears. To do so, you can pass a JSON object to the Tree API consisting of your search. The search should be passed in using the search field, like this:

POST /repositories/{repositoryId}/branches/{branchId}/nodes/root/tree

{
    "search": "tyrion"
}

You may also wish to pass in an object that conforms to the Elastic Search DSL. Here is an example where we search for all documents where the word Tyrion appears anywhere in any part of the document:

POST /repositories/{repositoryId}/branches/{branchId}/nodes/root/tree

{
    "search": {
        "query_string": {
            "query": "*tyrion*"
        }
    }
}

Note that searches are case insensitive.

Composite Search and Query

The Tree API further lets you execute both a query and a search at the same time. In other words, the Tree API lets you execute a query against MongoDB and a search against Elastic Search. The results are composed into a unified result set.

Suppose, for example, that you wanted to filter the tree to only include content where location=dragonstone and the word tyrion appears somewhere in the document. You could do so like this:

POST /repositories/{repositoryId}/branches/{branchId}/nodes/root/tree

{
    "query": {
        "location": "dragonstone"
    },
    "search": "tyrion"
}

And so on.

Expand Paths

You may also want to have certain paths auto-expanded when the tree is loaded. You can do this by passing in one or more paths or node IDs using the leaf request attribute.

Suppose, for example, that we want to load our example tree using a depth of 2 so that we only load up to the game-of-thrones folder. To set depth=2, we would do:

POST /repositories/{repositoryId}/branches/{branchId}/nodes/root/tree?depth=2

And we'd get:

/
    shows/
        game-of-thrones/

Suppose that's essentially right but we'd also like to expand the path down to Jon Snow. We can do it like this:

POST /repositories/{repositoryId}/branches/{branchId}/nodes/root/tree?depth=2&leaf=/shows/game-of-thrones/targaryeon/jon

And we'd get:

/
    shows/
        game-of-thrones/
            targaryeon/
                jon

We could also expand both Jon and Cersei like this:

POST /repositories/{repositoryId}/branches/{branchId}/nodes/root/tree?depth=2&leaf=/shows/game-of-thrones/targaryeon/jon&leaf=/shows/game-of-thrones/lannister/cersei

And we'd get:

/
    shows/
        game-of-thrones/
            lannister
                cersei
            targaryeon/
                jon

Oh but that's not all. Suppose that we don't know the paths for Jon and Cersei but we want to expand them, wherever they are in the tree, by referencing their node IDs. Based on the JSON from above, we know that:

  • Jon Snow's node ID is cdc3da075c11be87f4d5
  • Cersei Lannister's node ID is b6856cacd4be79c17353.

We can make the same call like this:

POST /repositories/{repositoryId}/branches/{branchId}/nodes/root/tree?depth=2&leaf=cdc3da075c11be87f4d5&leaf=b6856cacd4be79c17353

And we'd get:

/
    shows/
        game-of-thrones/
            lannister
                cersei
            targaryeon/
                jon