openBIS JSON API

Introduction

OpenBIS generic and screening APIs are available as JSON-RPC services (see http://en.wikipedia.org/wiki/JSON-RPC for more details on JSON-RPC itself). If you want to use the JSON API from within a browser you can construct appropriate HTTP requests by yourself or use provided openbis.js and openbis-screening.js javascript libraries to construct these requests for you. At the moment, the following services of openBIS API support JSON-RPC and are covered by our javascript libraries:

where OPENBIS_HOST and DSS_HOST represent host names of openBIS and DSS servers respectively, e.g. http://myopenbis.com:8888 and http://mydss.com:8889. For general information on the methods these services provide please see the JavaDoc documentation. For limitations of each service see "Known limitations" chapter below.

Parameters lists

There are two types of methods available in the JSON API:

These types define a way of passing parameters to API methods:

Parameters types

JSON objects that are sent to JSON-RPC services and reflect custom OpenBIS classes should contain information about their type. The type information is provided in an extra "@type" field in a JSON object. For instance, to send an instance of ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.MaterialIdentifier class to JSON-RPC service one should create a JSON object like:

{
	"@type" : "MaterialIdentifierGeneric",
	"materialTypeIdentifier" : {
		"@type" : "MaterialTypeIdentifierGeneric",
		"materialTypeCode" : "MY_MATERIAL_TYPE_CODE"
	},
	"materialCode" : "MY_MATERIAL_CODE"
}

As you can see the main JSON object that represents MaterialIdentifier class contains "@type" field equal to "MaterialIdentifierGeneric". This extra information helps the JSON-RPC service to properly recognize what sort of object it is. The "MaterialIdentifierGeneric" value was taken from the @JsonObject annotation value specified in OpenBIS JavaDoc for the MaterialIdentifier class. As MaterialIdentifier contains "materialTypeIdentifier" field that is also a custom OpenBIS class, this nested JSON object also contains "@type" field. The "MaterialTypeIdentifierGeneric" type of the nested object maps to ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.MaterialTypeIdentifier class.

Specifying the extra "@type" field is not always required. OpenBIS classes that do not have any subclasses do not require this extra field. They will be properly recognized by the server without it. Even though specifying "@type" field is not required for such classes it is still highly recommended. Explicitly defining types of objects will make your code not only easier to understand but also compatible with future versions of OpenBIS where classes that didn't have subtypes will have them.

Historical note: OpenBIS in versions older than 133 was using two kinds of extra fields for specifying the type information: "@type" and "@class". Starting from version 133 only "@type" field should be used.  The "@class" field is deprecated but still supported for classes that originally used it.

Sample usage in javascript with openbis.js

Example of API call when using openbis.js file:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!--  
  This example should be deployed as an openBIS core plugin webapp.
  If you don't know how to deploy your webapp as a core plugin see:
  https://wiki-bsse.ethz.ch/display/openBISDoc/openBIS+webapps
-->
<html lang="en">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <!-- 
          openBIS facade requires jQuery library
        -->
        <script type="text/javascript" src="/openbis/resources/js/jquery.js"></script>
        <script type="text/javascript" src="/openbis/resources/js/openbis.js"></script>
    </head>
    <body>
        <script type="text/javascript">
          $(document).ready(function(){
              /* 
                Facade created without an openBIS url explicitly specified assumes
                that an openBIS instance it should connect to is on the same server 
                as the webapp. To create a facade that communicates with a different 
                server openbis("http://myopenbis.com") constructor should be used. 
                
                Urls of data store servers are now automatically fetched from the openBIS 
                server and do not have to be manually specified anymore. 
              */
 
              var facade = new openbis();
              facade.login("admin", "password", function(){
                  facade.listProjects(function(response){
                      if(response.error){
                          alert("Couldn't get projects because of a following error: " + response.error);
                      }else{
                          alert("Got " + response.result.length + " project(s)");
                      }
                      facade.logout();
                  });
              });
          });
        </script>
    </body>
</html>

Sample usage javascript with openbis-screening.js

Example of API call when using openbis-screening.js file:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!-- 
  This example should be deployed as an openBIS core plugin webapp.
  If you don't know how to deploy your webapp as a core plugin see:
  https://wiki-bsse.ethz.ch/display/openBISDoc/openBIS+webapps
-->
<html lang="en">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <!-- 
          openBIS screening facade requires jQuery library and openBIS facade
        -->
        <script type="text/javascript" src="/openbis/resources/js/jquery.js"></script>
        <script type="text/javascript" src="/openbis/resources/js/openbis.js"></script>
        <script type="text/javascript" src="/openbis/resources/js/openbis-screening.js"></script>
    </head>
    <body>
        <script type="text/javascript">
          $(document).ready(function(){
              // creating the screening facade looks exactly the same as creating the generic facade 
              var facade = new openbis();
              // it has all the methods that the generic facade has 
              facade.login("admin", "password", function(){
                  // but also contains the screening specific functionality 
                  facade.listPlates(function(response){
                      if(response.error){
                          alert("Couldn't get plates because of a following error: " + response.error);
                      }else{
                          alert("Got " + response.result.length + " plate(s)");
                      }
                      facade.logout();                      
                  });
              });
          });
        </script>
    </body>
</html>

Sample usage javascript and jquery.js only

Example of API call when using jQuery library:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
        "http://www.w3.org/TR/html4/strict.dtd">

<html>
  <head>
    <title>Hello World</title>
    <script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"></script>
  </head>

  <body>
    <script type="text/javascript">

      function login(username, password, callback){
          // send AJAX request using jQuery library 
          $.ajax({
              // to a chosen OpenBIS JSON-RPC service (here IGeneralInformationService service) 
              url: "https://sprint-openbis.ethz.ch:8446/openbis/openbis/rmi-general-information-v1.json",
              // using POST HTTP method 
              type: "POST",
              // with a following POST request body (here we call "tryToAuthenticateForAllServices" method with "username" and "password" parameters).
              // The "id" and "jsonrpc" parameters are required by JSON-RPC standard and are always the same.
              data: '{ "id": "1", "jsonrpc": "2.0", "method": "tryToAuthenticateForAllServices", "params": [ "' + username + '", "' + password + '" ] }',
              // expect JSON object as a result (jQuery will automatically parse the response and provide us with JSON object instead of a string) 
              dataType: "json",
              // call this function if the request succeeds (here we can manipulate the "data" JSON object that represents the response. 
              // Results of the server method call are available in data.result field) 
              success: function(data){
            	    // if got session token 
            	    if(data.result){
                     alert("Login succeeded. Got session token: " + data.result);
                     callback(data.result);
            	    }else{
                     alert("Login failed. Incorrect username or password.");
                     callback();
            	    }
              },

              // call this function if the request fails (here you can handle any errors) 
              error: function(){
                  alert("Login failed");
                  callback();
              }
           });
      }

      

      function listProjects(sessionToken, callback){
          $.ajax({
              url: "https://sprint-openbis.ethz.ch:8446/openbis/openbis/rmi-general-information-v1.json",
              type: "POST",
              data: '{ "id": "1", "jsonrpc": "2.0", "method": "listProjects", "params": [ "' + sessionToken + '" ] }',
              dataType: "json",
              success: function(data){
                  alert("List projects succeeded. Got projects: " + (data.result ? data.result.length : 0));
                  callback();
              },
              error: function(){
                  alert("List projects failed");
                  callback();
              }
           });

      }

      

      function logout(sessionToken){
          $.ajax({
              url: "https://sprint-openbis.ethz.ch:8446/openbis/openbis/rmi-general-information-v1.json",
              type: "POST",
              data: '{ "id": "1", "jsonrpc": "2.0", "method": "logout", "params": [ "' + sessionToken + '" ] }',
              dataType: "json",
              success: function(data){
                  alert("Logout succeeded");
              },
              error: function(){
                  alert("Logout failed");
              }
           });    	  
      }


      $(document).ready(function(){
    	  login("admin", "password", function(sessionToken){
               if(sessionToken){
                    listProjects(sessionToken, function(){
                         logout(sessionToken);
                    });
               }
          });
      });
      

    </script>
  </body>
</html>

 

Sample Usage Python

For this example we use the JSON RPC implementation from https://github.com/joshmarshall/jsonrpclib

 

import jsonrpclib
server = jsonrpclib.Server('https://bs-openbis11.ethz.ch:8443/openbis/openbis/rmi-general-information-v1.json')

sessionToken = server.tryToAuthenticateForAllServices(<username>, <password>)
projects = server.listProjects(sessionToken)
experimentType = 'HT_SEQUENCING'

experiments = server.listExperiments(sessionToken, projects, experimentType)

for experiment in experiments:
  print experiment['code']

 

Sample Usage from Android

For this example we use the JSON RPC implementation from http://code.google.com/p/android-json-rpc/

public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

		// Just for testing
        StrictMode.ThreadPolicy policy = new StrictMode.
        ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy); 

        try {
        	String password = "admin";
        	String username = "admin";
        	String serverURL = "https://127.0.0.1:8443/openbis/openbis/rmi-general-information-v1.json"; 

        	// Create client specifying JSON-RPC version 2.0
        	JSONRPCClient service = JSONRPCClient.create(serverURL, JSONRPCParams.Versions.VERSION_2);
        	service.setConnectionTimeout(5000);
        	String sessionToken = service.callString("tryToAuthenticateForAllServices", username, password);
            pp(sessionToken);

            List<String> ls = new ArrayList<String>();
            System.out.println(projects);
            JSONArray js = service.callJSONArray("listProjects", sessionToken);
            System.out.println(js);
            
        } catch (Exception e) {
        	e.printStackTrace();
        }     
    }

 

Sample Usage Command Line

You can do a JSON-RPC call on the command line using curl. An example is:

curl --insecure https://bs-openbis11.ethz.ch:8443/openbis/openbis/rmi-general-information-v1.json -H "Content-Type: application/json" -H "Accept: application/json" --data '{"id": "1", "jsonrpc": "2.0", "method": "getMinorVersion"}'

 

Configuration

Addresses of all JSON-RPC services are fixed and cannot be changed. There is therefore no server configuration necessary.

Base64 encoded images

As we do not support streams in JSON API all methods for loading images that return streams (i.e. all variations of loadImages and loadThumbnailImages methods) do not work. Luckily all these methods have counterparts that return images as base64 encoded strings. For instance a method:   InputStream IDssServiceRpcScreening.loadImages(String sessionToken, List<PlateImageReference> imageReferences)  has a counter part:  List<String> IDssServiceRpcScreening.loadImagesBase64(String sessionToken, List<PlateImageReference> imageReferences). 

Having the Base64 encoded representation of images makes it pretty straightforward to transfer the images using JSON and to display them in a web browser. Currently all of the modern browsers support a feature of embedding Base64 encoded images straight into HTML or CSS. With that functionality, loading images from openBIS and displaying them in a browser would require a simple Javascript code similar to this one:

loadImagesBase64(myImagesParams, function(response){
  var images = response.result;
  for(var i = 0; i < images.length; i++){
    // $ sign comes from the jQuery library. It is not required. It is used here just to make the code short and easy.
    $("body").append('<img src="data:image/jpg;base64,' + images[i] + '">');
  }
});

The advantage of such a solution over creating images with regular "src" attribute pointing at some remote URL is performance. Instead of generating separate requests for every image, you can simply fetch multiple images from the server with a single HTTP call. Unfortunately this approach also has some limitations. First of all, images loaded this way are not cached. They are fetched from the server every time they are requested. Another limitation refers to the size of Base64 images that the browsers support. Older browsers support displaying only small images, e.g. smaller than 128KB. Tests of the modern browsers showed that they are capable of displaying images as large as 40MB.

Search Criteria

The openbis.js file includes objects that simplify constructing search criteria that are used in searches. The programming style is similar to the way Java search criteria object is uses. Here are some examples of how to work with the search criteria.

SearchCriteriaModel.prototype.executeBasicSearch = function(callback) {
	var searchCriteria = new SearchCriteria();
	searchCriteria.addMatchClause(SearchCriteriaMatchClause.createAttributeMatch("CODE", "DEFAULT"));
	server.searchForSamples(searchCriteria, callback);
}
SearchCriteriaModel.prototype.executeExperimentSearch = function(callback) {
	var searchCriteria = new SearchCriteria();
	searchCriteria.addMatchClause(SearchCriteriaMatchClause.createAnyPropertyMatch("foo"));
	server.searchForExperiments(searchCriteria, callback);
}
SearchCriteriaModel.prototype.executeSampleByExperimentSearch = function(callback) {
	var expSc = new SearchCriteria();
	expSc.addMatchClause(SearchCriteriaMatchClause.createAnyFieldMatch("SEARCH_CRITERIA"));
	var sc = new SearchCriteria();
	sc.addSubCriteria(SearchSubCriteria.createExperimentCriteria(expSc));
	server.searchForSamples(sc, callback);
}
SearchCriteriaModel.prototype.executeSampleByExperimentSearch = function(callback) {
	var expSc = new SearchCriteria();
	expSc.addMatchClause(SearchCriteriaMatchClause.createAnyFieldMatch("SEARCH_CRITERIA"));
	var sc = new SearchCriteria();
	sc.addSubCriteria(SearchSubCriteria.createExperimentCriteria(expSc));
	server.searchForSamples(sc, callback);
}
SearchCriteriaModel.prototype.executeSampleByChildrenSearch = function(callback) {
	var childSc = new SearchCriteria();
	childSc.addMatchClause(SearchCriteriaMatchClause.createPropertyMatch("SEARCH_INFO", "Sample bar description"));
	var sc = new SearchCriteria();
	sc.addSubCriteria(SearchSubCriteria.createSampleChildCriteria(childSc));
	server.searchForSamples(sc, callback);
}
SearchCriteriaModel.prototype.executeSampleByParentSearch = function(callback) {
	var parentSc = new SearchCriteria();
	parentSc.addMatchClause(SearchCriteriaMatchClause.createAttributeMatch("SPACE", "SEARCH"));
	var sc = new SearchCriteria();
	sc.addSubCriteria(SearchSubCriteria.createSampleParentCriteria(parentSc));
	server.searchForSamples(sc, callback);
}
SearchCriteriaModel.prototype.executeSampleByContainedSearch = function(callback) {
	var containerSc = new SearchCriteria();
	var timeFormatter = d3.time.format("%Y-%m-%d");
	var date = timeFormatter(new Date());
	containerSc.addMatchClause(SearchCriteriaMatchClause.createTimeAttributeMatch("MODIFICATION_DATE", "LESS_THAN_OR_EQUAL", date, "+1"));
	var sc = new SearchCriteria();
	sc.addSubCriteria(SearchSubCriteria.createSampleContainerCriteria(containerSc));
	server.searchForSamples(sc, callback);
}
SearchCriteriaModel.prototype.executeDataSetByContainedSearch = function(callback) {
	var containerSc = new SearchCriteria();
	containerSc.addMatchClause(SearchCriteriaMatchClause.createPropertyMatch("SEARCH_INFO", "Data set bar description"));
	var sc = new SearchCriteria();
	sc.addSubCriteria(SearchSubCriteria.createDataSetContainerCriteria(containerSc));
	server.searchForDataSets(sc, callback);
}

SearchCriteriaModel.prototype.executeDataSetByChildSearch = function(callback) {
	var childSc = new SearchCriteria();
	var timeFormatter = d3.time.format("%Y-%m-%d");
	var date = timeFormatter(new Date());
	childSc.addMatchClause(SearchCriteriaMatchClause.createTimeAttributeMatch("MODIFICATION_DATE", "LESS_THAN_OR_EQUAL", date, "+1"));
	var sc = new SearchCriteria();
	sc.addSubCriteria(SearchSubCriteria.createDataSetChildCriteria(childSc));
	server.searchForDataSets(sc, callback);
}

SearchCriteriaModel.prototype.executeDataSetByParentSearch = function(callback) {
	var parentSc = new SearchCriteria();
	var timeFormatter = d3.time.format("%Y-%m-%d");
	var date = timeFormatter(new Date());
	parentSc.addMatchClause(SearchCriteriaMatchClause.createTimeAttributeMatch("MODIFICATION_DATE", "LESS_THAN_OR_EQUAL", date, "+1"));
	var sc = new SearchCriteria();
	sc.addSubCriteria(SearchSubCriteria.createDataSetParentCriteria(parentSc));
	server.searchForDataSets(sc, callback);
}

 

 

Known limitations

Currently we do not support streams in JSON API.