The REST interface follows a few simple conventions. All primary entities (i.e. stand alone entities those whose lifecycle doesn't depend on another entity) have 16 standard methods. GET methods retrieve the entities, PUT methods update entities, POST methods create entities and DELETE methods delete entities.
The json methods return plain JSON objects, while the jsonp methods will return JSON objects wrapped in a function call to allow cross domain access to Javascript applications.
As an example, the 16 functions for the tag entity are shown below:
The URLs are constructed with a number of standard components:
The PUT and POST methods take a representation of the entities to be edited or created in the body of the request.
PUT methods require that the id of the entity is set. This is how the system knows which entities to update. POST methods ignore any ids, as these are created by the system when a new entity is created.
All REST methods return the entities that were touched by the request. GET and DELETE methods will return the state of the entities as they existed when the methods was called. PUT and POST methods will return the updated state of the entities after any changes have been made.
Secondary entities (i.e. those on the many side of a one-to-many relationship) do not have POST methods, because they can not exist without a primary entity. They can be created through the POST or PUT methods of their parents. See CREATING AND UPDATING ENTITIES for more information.
Topic entities have a number of additional GET methods over the other primary entities.
The format for the queries used by the first two methods are the query parameters used by the Skynet web interface search results screen (e.g. tag19=1&tag119=1&catint5=And&tag133=1&tag14=0&tag132=1) prefixed with query;, and with all ampersand symbols replaced by semi colons. So the final results looks like query;tag19=1;tag119=1;catint5=And;tag133=1;tag14=0;tag132=1.
All the entities have a collection called revisions. This is read only collection that contains the previous versions of an entity. It can be expanded like any other collection, but this collection will not be directly modified by any PUT or POST operations.
When updating an entity through a PUT method, or creating an entity through a POST method, it is possible to also create and remove relationships.
For Many-To-Many relationships (e.g. between a Topic and a Tag), where each entity is a primary entity that doesn't rely on the other for its lifecycle, modifying the relationships means creating or removing a relationship between two entities that currently exist (or between the entity that is being created with a POST METHOD and an entity that currently exists).
For One-To-Many relationships (e.g. between a Topic and a Source URL), where the secondary entity on the many side can't exist without the primary entity, modifying the relationship means creating or removing the secondary entity.
An example of modifying a Many-To-Many relationship is a POST to http://localhost:8080/TopicIndex/seam/resource/rest/1/user/post/json with the body
{
"configuredParameters":["name","description","roles"],
"name":"atestuser",
"description":"test user",
"roles":{
"items":[
{
"id":1,
"addItem":true
}
]
}
}
There are two important things to note here.
The first is the configuredParameters property. This is an array that lists the properties that are to be set in an update or create operation. When updating an entity, any property not mention in the configuredParameters property is not modified, and when creating an entity any property not mention in the configuredParameters property is not set. This is to define a difference between "set a property to null" and "don't set a property".
The second is the addItem property set on the item in the roles item collection. If this is set to true, the item in the collection will be linked in a relationship. If the removeItem property was set to true, an existing relationship to the item in the collection would be removed.
If you want to add a relationship between existing entities, you would do a PUT request to a URL like http://localhost:8080/TopicIndex/seam/resource/rest/1/user/put/json with a body like
{
"configuredParameters":["name","roles"],
"name":"atestuser",
"roles":{
"items":[
{
"id":2,
"addItem":true
}
]
}
}
Note that in this example we are updating the name of the user as well as adding a relationship to the role with the id 2.
To create secondary entities you need add them through their parent entity. An example PUT to http://localhost:8080/TopicIndex/seam/resource/rest/1/topic/put/json with the body
{
"id":33,
"configuredParameters":["sourceUrls_OTM"],
"sourceUrls_OTM":{
"items":[
{
"title":"Google",
"description":"A search engine",
"url":"http://www.google.com",
"addItem":true,
"configuredParameters":["title","description","url"]
}
]
}
}
will create an source URL entity and link it to the topic with the ID of 33.
Note that collections of One-To-Many relationships have the postfix "_OTM", and because we are creating a new entity the configuredParameters property has to be set on the secondary entity.
By default, a minimal amount of information about an entity or collection of entities is returned. For single entities, the basic properties are returned, while any collections have only the number of items in the collection. Take this example response from a call to http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/get/json/3
{
"name":"Migration",
"description":"All migration tags, from previous versions or from other app servers.",
"categories":{
"size":1,
"expand":"categories",
"startExpandIndex":null,
"endExpandIndex":null,
"items":null
},
"childTags":{
"size":0,
"expand":"childtags",
"startExpandIndex":null,
"endExpandIndex":null,
"items":null
},
"parentTags":{
"size":0,
"expand":"parenttags",
"startExpandIndex":null,
"endExpandIndex":null,
"items":null
},
"properties":{
"size":0,
"expand":"properties",
"startExpandIndex":null,
"endExpandIndex":null,
"items":null
},
"id":3,
"selfLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/get/json/3",
"editLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/put/json/3",
"addLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/post/json/3",
"expand":[
"categories",
"parenttags",
"childtags",
"properties"
],
"removeItem":false,
"deleteLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/delete/json/3",
"addItem":false,
"configuredParameters":null
}
The tag's properties like name, description and id are populated, while the collections categories, childTags, parentTags and properties only define the properties of the collection without actually returning the items in the collection. You can see this in the categories collection, which is shown below:
"categories":{
"size":1,
"expand":"categories",
"startExpandIndex":null,
"endExpandIndex":null,
"items":null
},
~~~~~~~~
You will also notice that the tag includes a collection called **expand**, which lists the **expand** properties of the various collections that are defined in the tag.
"expand":
"categories",
"parenttags",
"childtags",
"properties"
,
To return a populated collection, you need to supply an **expand** query parameter, which can be supplied to all entity REST methods. This parameter is a JSON representation of the com.redhat.topicindex.rest.expand.ExpandDataTrunk class (see **THE JAVA WAY** below for more information). A simple example is shown below:
{
"branches":
{
"trunk":{
"name":"categories"
}
}
}
To expand the categories collection, we would set the *name* property of one of the branch items of the ExpandDataTrunk instance to **categories**, [URL encode](http://www.albionresearch.com/misc/urlencode.php) the JSON string, and supply it to the **expand** query parameter. An example result from a call to http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/get/json/3?expand=%7B%22branches%22%3A%5B%7B%22trunk%22%3A%7B%22name%22%3A%22categories%22%7D%7D%5D%7D is:
~~~~~~~~~
{
"name":"Migration",
"description":"All migration tags, from previous versions or from other app servers.",
"categories":{
"size":1,
"expand":"categories",
"startExpandIndex":0,
"endExpandIndex":1,
"items":[
{
"name":"Common Names",
"description":"",
"mutuallyExclusive":false,
"tags":{
"size":76,
"expand":"tags",
"startExpandIndex":null,
"endExpandIndex":null,
"items":null
},
"sort":19,
"id":17,
"selfLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/category/get/json/17",
"editLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/category/put/json/17",
"addLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/category/post/json/17",
"expand":[
"tags"
],
"removeItem":false,
"deleteLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/category/delete/json/17",
"addItem":false,
"configuredParameters":null
}
]
},
"childTags":{
"size":0,
"expand":"childtags",
"startExpandIndex":null,
"endExpandIndex":null,
"items":null
},
"parentTags":{
"size":0,
"expand":"parenttags",
"startExpandIndex":null,
"endExpandIndex":null,
"items":null
},
"properties":{
"size":0,
"expand":"properties",
"startExpandIndex":null,
"endExpandIndex":null,
"items":null
},
"id":3,
"selfLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/get/json/3",
"editLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/put/json/3",
"addLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/post/json/3",
"expand":[
"categories",
"parenttags",
"childtags",
"properties"
],
"removeItem":false,
"deleteLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/delete/json/3",
"addItem":false,
"configuredParameters":null
}
~~~~~~~~~
As you can see, the **categories** collection now has a populated **items** property, which lists the category entities that the tag is related to.
~~~~~~~~~~
"categories":{
"size":1,
"expand":"categories",
"startExpandIndex":0,
"endExpandIndex":1,
"items":[
{
"name":"Common Names",
"description":"",
"mutuallyExclusive":false,
"tags":{
"size":76,
"expand":"tags",
"startExpandIndex":null,
"endExpandIndex":null,
"items":null
},
"sort":19,
"id":17,
"selfLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/category/get/json/17",
"editLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/category/put/json/17",
"addLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/category/post/json/17",
"expand":[
"tags"
],
"removeItem":false,
"deleteLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/category/delete/json/17",
"addItem":false,
"configuredParameters":null
}
]
},
~~~~~~~~~~
Expanding the results of a method call that returns a collection is similar. An example of a call to http://localhost:8080/TopicIndex/seam/resource/rest/1/tags/get/json/all is:
~~~~~
{
"size":4,
"expand":"tags",
"startExpandIndex":null,
"endExpandIndex":null,
"items":null
}
~~~~~
Again, this collection only includes information on the number of items that exist in the collection, without actually providing the items in the collection. To expand the returned collection, supply the following JSON to the **expand** query parameter.
{
"branches":
{
"trunk":{
"name":"tags"
}
}
}
An example of a call to http://localhost:8080/TopicIndex/seam/resource/rest/1/tags/get/json/all?expand=%7B%22branches%22%3A%5B%7B%22trunk%22%3A%7B%22name%22%3A%22tags%22%7D%7D%5D%7D is:
~~~~~
{
"size":4,
"startExpandIndex":0,
"endExpandIndex":3,
"expand":"tags",
"items":[
{
"name":"Administrator",
"description":"Installation, Configuration and Operation",
"parentTags":{
"size":0,
"startExpandIndex":null,
"endExpandIndex":null,
"expand":"parenttags",
"items":null
},
"categories":{
"size":0,
"startExpandIndex":null,
"endExpandIndex":null,
"expand":"categories",
"items":null
},
"childTags":{
"size":0,
"startExpandIndex":null,
"endExpandIndex":null,
"expand":"childtags",
"items":null
},
"properties":{
"size":0,
"startExpandIndex":null,
"endExpandIndex":null,
"expand":"properties",
"items":null
},
"id":1,
"deleteLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/delete/json/1",
"configuredParameters":null,
"selfLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/get/json/1",
"editLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/put/json/1",
"addLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/post/json/1",
"expand":[
"categories",
"parenttags",
"childtags",
"properties"
],
"addItem":false,
"removeItem":false
},
{
"name":"Developer",
"description":"Development",
"parentTags":{
"size":0,
"startExpandIndex":null,
"endExpandIndex":null,
"expand":"parenttags",
"items":null
},
"categories":{
"size":0,
"startExpandIndex":null,
"endExpandIndex":null,
"expand":"categories",
"items":null
},
"childTags":{
"size":0,
"startExpandIndex":null,
"endExpandIndex":null,
"expand":"childtags",
"items":null
},
"properties":{
"size":0,
"startExpandIndex":null,
"endExpandIndex":null,
"expand":"properties",
"items":null
},
"id":2,
"deleteLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/delete/json/2",
"configuredParameters":null,
"selfLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/get/json/2",
"editLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/put/json/2",
"addLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/post/json/2",
"expand":[
"categories",
"parenttags",
"childtags",
"properties"
],
"addItem":false,
"removeItem":false
},
{
"name":"Migration",
"description":"All migration tags, from previous versions or from other app servers.",
"parentTags":{
"size":0,
"startExpandIndex":null,
"endExpandIndex":null,
"expand":"parenttags",
"items":null
},
"categories":{
"size":1,
"startExpandIndex":null,
"endExpandIndex":null,
"expand":"categories",
"items":null
},
"childTags":{
"size":0,
"startExpandIndex":null,
"endExpandIndex":null,
"expand":"childtags",
"items":null
},
"properties":{
"size":0,
"startExpandIndex":null,
"endExpandIndex":null,
"expand":"properties",
"items":null
},
"id":3,
"deleteLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/delete/json/3",
"configuredParameters":null,
"selfLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/get/json/3",
"editLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/put/json/3",
"addLink":"http://localhost:8080/TopicIndex/seam/resource/rest/1/tag/post/json/3",
"expand":[
"categories",
"parenttags",
"childtags",
"properties"
],
"addItem":false,
"removeItem":false
}
]
}
~~~~~
###ADDITIONAL EXPAND OPTIONS###
The **expand** query parameter can be further refined with the **start** and **end** properties.
The **start** property sets the index of the first element to be expanded. If it is a negative value, it defines a position from the end of the collection. If omitted, it defaults to 0.
The **end** property sets the index of the last element to be expanded. If it is a negative value, it defines a position from the end of the collection. If omitted, it defaults to \[the size of the collection\] - 1.
The following **expand** query parameter will return expanded items from the second item (remember we use a 0 based index) to the second last item.
{
"trunk":{
"name":"tags",
"start":1
"end":-1
}
}
Expansion options can also be nested. The following **expand** query parameter will expand the items in the returned collection, and will also expand the **categories** collection for each tag.
{
"trunk":{
"name":"tags"
"branches":
{
"name":"categories"
}
}
}
~~~~~~~~
The easiest way to access the Skynet REST interface is through the JAVA interface. You will need to grab the REST JAR file, which is easiest to do through the SVN (click trunk, and then download the redhatcommonutils.jar file). You will also need to download RESTEasy.
The com.redhat.topicindex.rest.sharedinterface.RESTInterfaceV1 interface lists all the REST functions that are available. This interface has been designed to only depend on POJOs also found in the JAR file, and the standard JAX-RS annotations.
Using the RESTEasy client, you can access the REST interface through the RESTInterfaceV1 interface.
Note that when setting the properties on an entity to be created or updated, you can use the setPropertyExplicit methods. These will automatically populate the configuredParameters array with the correct values.
import java.net.URLEncoder;
import java.util.ArrayList;
import javax.ws.rs.core.PathSegment;
import org.codehaus.jackson.map.ObjectMapper;
import org.jboss.resteasy.client.ProxyFactory;
import org.jboss.resteasy.plugins.providers.RegisterBuiltin;
import org.jboss.resteasy.specimpl.PathSegmentImpl;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import com.redhat.topicindex.rest.collections.BaseRestCollectionV1;
import com.redhat.topicindex.rest.entities.ImageV1;
import com.redhat.topicindex.rest.expand.ExpandDataDetails;
import com.redhat.topicindex.rest.expand.ExpandDataTrunk;
import com.redhat.topicindex.rest.sharedinterface.RESTInterfaceV1;
public class Main
{
public static void main(String[] args)
{
RegisterBuiltin.register(ResteasyProviderFactory.getInstance());
testREST();
System.out.println("Done");
}
private static void testREST()
{
RESTInterfaceV1 client = null;
try
{
client = ProxyFactory.create(
RESTInterfaceV1.class,
"http://localhost:8080/TopicIndex/seam/resource/rest");
PathSegment path = new PathSegmentImpl("ids", false);
path.getMatrixParameters().put(
"49",
new ArrayList<String>(){{add("");}});
path.getMatrixParameters().put(
"50",
new ArrayList<String>(){{add("");}});
final ExpandDataTrunk data =
new ExpandDataTrunk(new ExpandDataDetails("images"));
final ObjectMapper mapper = new ObjectMapper();
final String dataJson = mapper.writeValueAsString(data);
final String dataJsonEncoded = URLEncoder.encode(dataJson, "UTF-8");
final BaseRestCollectionV1<ImageV1> images =
client.deleteJSONImages(path, dataJsonEncoded);
for (final ImageV1 image : images.getItems())
System.out.println(image.getFilename());
}
catch (final Exception ex)
{
System.out.println(ex.toString());
}
}
}