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 againstbranchId
- the ID of the branch to work againstnodeId
- the ID of the node to work against (or useroot
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
andid
- identify the repository, branch and node of the underlying content itemrootNodeId
- the ID of the root of the repositorypath
- the path relative to the root for the current elementcontainer
- whether the current element is a container (folder)filename
- the filename for the current elementlabel
- 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 nodeqname
- the QName of the node
If the element is a container, it will also provide the following:
children
- an array of its childrenchildCount
- 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 containerchildCount
- the size of thechildren
arrayloaded
- whether thechildren
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:
shows
is at depth 1game-of-thrones
is at depth 2lannister
,stark
andtargaryeon
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