Commit 7705555e authored by Matija Obreza's avatar Matija Obreza
Browse files


parent 87542768
......@@ -115,6 +115,12 @@
......@@ -641,9 +647,11 @@
<!-- <linkcss /> -->
......@@ -653,7 +661,7 @@
<!-- <execution>
......@@ -663,7 +671,7 @@
</execution> -->
......@@ -672,9 +680,10 @@
<!-- <imagesDir>images</imagesDir> -->
Genesys API reference manual
December 2015: Documentation commit {buildNumber}
:revnumber: {projectVersion}
:doctype: book
:toc: left
:toclevels: 5
:icons: font
:source-highlighter: pygments
:pygments-css: class
:pygments-linenums-mode: table
All API access is over HTTPS, and accessed from the domain or
through for testing purposes. All data is sent and received as JSON.
In this manual, all URLs are pointing to the Genesys sandbox environment at
......@@ -37,7 +37,7 @@ This file needs to be created in a location that is included on the webapplicati
=== Configuration options
==== HTTP, CDN and cookie configuration
==== HTTP, CDN and cookies
[cols="1,1,3", options="header"]
.HTTP-related configuration
......@@ -171,7 +171,7 @@ Genesys uses a number of external service APIs to provide users with advanced fu
||22|The maximum length for abbreviated menu item titles.
==== Email and SMTP configuration
==== Email and SMTP
[cols="1,1,3", options="header"]
.SMTP configuration
......@@ -51,7 +51,7 @@ building the project. The zip can be found in target/ directory.
=== Install *genesys2-server* and configure Jetty
=== Install webapp and configure Jetty
Unpack `genesys2-server-{projectVersion}` archive and move its contents into the jetty directory,
next to existing `demo-base` and `webapps` directories.
== Managing Crop data
Genesys maintains a database of crops and crop groups (e.g. forages). In addition to the general
description, each crop defines a list of taxonomic rules that determine which taxonomies are
included (or excluded) in the group.
.Crop Taxonomic rules
The common crop name of an accession in Genesys is determined by its taxonomy.
For example, crop[banana] includes accessions with genus _Musa_.
. Each crop and crop group defines its own *taxonomic rules*
. The genus, species and sub-species data of an accession is matched against all taxonomic rules
.. Accession is linked with all matching crops
.. Accession may have more than one "crop"
Managing crop data in Genesys is done through methods available in */api/v0/crops*
=== Listing all crops
A number of crops and crop groups are usually defined and used in Genesys. To list all
crops, issue a GET request to /api/v0/crops/ endpoint:
The result is a JSON array of all crops in Genesys:
"id": 27,
"version": 1,
"rdfUri": null,
"shortName": "apple",
"name": "Apple",
"description": null,
"i18n": "{\"name\":{\"fa\":\"سیب\"}}"
}, {
"id": 1,
"shortName": "banana",
"name": "Banana", ... <1>
}, {
"shortName": "barley",
"name": "Barley", ...
<1> Some JSON elements removed for readability
=== Retrieving crop data
The crop details are retrievable with a HTTP GET method to `/api/v0/crops/{shortName}`.
[cols="1,3", options="header"]
.URL path parameters
.Sample `curl` request to fetch maize crop definition
The JSON representation of a single crop record includes the following fields:
[cols="1,1,2", options="header"]
.Fields of *Crop* records
==== Taxonomic rules
The `/api/v0/crops/{shortName}/rules` endpoint exposes access to crop's taxonomic rules.
We mentioned earlier that accessions with genus _Musa_ belong to bananas, similarly _Zea_
genus belongs to maize.
.Maize taxonomic rules
"id": 24,
"included": true, <1>
"genus": "Zea", <2>
"species": null, <3>
"subtaxa": null <4>
<1> The combination of genus, species and subtaxa can be either included or explicitly excluded from the crop.
<2> Genus _Zea_ is included in maize when accession's genus is _Zea_.
<3> Species field is `null` meaning that accession species is ignored by this rule.
<4> Subtaxa field is `null` meaninig that accession subtaxa value is ignored by this rule.
==== Exclusion rule
A rule can explicitly exclude accessions matching a particular combination of genus + species + subtaxa.
This is useful for cases where you wish to include all _Solanum_ species except for selected species
(e.g. _Solanum melongena_).
=== Registering a new crop
To create a new crop, a JSON with the following data must be submitted:
"shortName": "maize",
"name": "Maize",
"description": "Crop description in EN"
[cols="1,1,2", options="header"]
.Minimum required data to register a crop
The response is a single crop record as stored on the server.
==== `curl` example
=== Updating taxonomic rules
Taxonomic rules can be replaced using one call by providing the new list of rules as the
body of the HTTP PUT call to `/api/v0/crops/{shortName}/rules`. To specify
that all _Triticum_ and _Aegilops_ species should be included in *wheat* you would
send the following array of rules to `/api/v0/crops/wheat/rules`:
"included": true,
"genus": "Triticum"
}, {
"included": true,
"genus": "Aegilops"
.Setting new taxonomic rules for the selected crop
[cols="1,1,2", options="header"]
.Taxonomic rule fields
=== Deleting a crop
A crop record can be deleted by issuing a HTTP DELETE request to the `/api/v0/crops/{shortName}`.
This will remove the crop and crop rules from the system.
.Deleting a crop
== Managing Passport Data
Accession records are *upserted*, meaning that when the matching accession record
. exists, it will be updated
. does not exist, a new record will be created
Accession data in the database will be updated with whatever data is provided in the
request JSON.
TIP: If you want to clear or un-set a value, upsert it as *null*.
`curl` Call
And this thing HTTP request
Request fields
HTTP Response
Response fields
\ No newline at end of file
== Security model
Access to selected resources in Genesys is protected and user permissions are checked before
any API action is executed. Each organization contributing data to Genesys will have
one or more registered user accounts on Genesys.
To modify any data in Genesys, you will need appropriate permissions.
Permission to access and manage data for the organization is granted by upon request. Please contact with the list
of WIEWS codes of institutes you wish to manage.
== Security and OAuth
To access resources with the APIs described in this manual, you will first need to
create a user account. The simplest is to[use your Google+ account]
or alternatively[creating an account manually].
.Creating a user account
Access to the APIs is managed by[OAuth 2.0] protocol and implemented
using[spring-security OAuth]
modules. All API access is over HTTPS, and accessed from the domain or
through for testing purposes.
To obtain OAuth access and refresh tokens, you will first need a valid Client ID and Client Secret.
These are generated by for each individual consumer application.
The ID and Secret listed below are valid for the Sandbox environment and allows of out-of-band authentication
when using `curl` in the examples in this manual.
.Client ID and Secret for OOB
|Client ID
|Client Secret
=== Obtaining the access token
Most OAuth libraries, including[genesys-client-api]
Java library, will automatically obtain the access token following the OAuth protocol. This
section describes how to manually obtain the tokens.
Log-in to Genesys with your account or Google+
Obtain a verifier code by granting access to the "Genesys API reference" client. This is
initiated by opening the authorization URL in a browser (please substitute the CLIENTID and SECRET
with valid data):
The server will prompt you to authorize the access to your protected resources on Genesys.
After your confirmation, the server will present an authorization code.
Copy the authorization code: *THECODE* (looks like: 7wXP1r) and from shell, execute the `curl` command:
$ curl ''
The server will respond with access token details in JSON format:
"access_token": "28d96a4e-9a31-479b-abc8-17ee1e8c9906",
"token_type": "bearer",
"refresh_token": "2583fd78-bd88-4c2b-afc0-fb231b37d95f",
"expires_in": 43199,
"scope": "read write"
You can use the access token to sign future HTTP requests to the API by adding a HTTP request header:
Authorization: Bearer OAUTH-ACCESS-TOKEN
as `curl` parameter:
curl -H "Authorization: Bearer OAUTH-TOKEN"
or include it in the request URL as a query string parameter:
$ curl ''
== Client errors
The API returns descriptive information in the response body about why the request failed to execute.
=== 401 Unauthorized
*401 Unauthorized* indicates that the request did not include valid authentication data. Check your <<chOAtoken, OAuth access token>>.
HTTP/1.1 401 Unauthorized
Content-Type: application/json;charset=UTF-8
WWW-Authenticate: Bearer realm="genesys2", error="unauthorized", error_description="An Authentication object was not found in the SecurityContext"
{"error":"unauthorized","error_description":"An Authentication object was not found in the SecurityContext"}
package org.genesys2.tests.resttests;
import static org.hamcrest.Matchers.hasSize;
import static;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.genesys2.server.model.impl.Crop;
import org.genesys2.server.model.impl.CropRule;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.restdocs.RestDocumentation;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class ApiDocumentation extends AbstractRestTest {
private static final Log LOG = LogFactory.getLog(ApiDocumentation.class);
public final RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets");
WebApplicationContext webApplicationContext;
MockMvc mockMvc;
private static final ObjectMapper objectMapper;
private Crop crop;
private CropRule cropRule;
static {
objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
objectMapper.configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, false);
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(documentationConfiguration(this.restDocumentation).uris()
crop = cropService.addCrop("maize", "Maize", "Crop description in EN", null);
cropRule = cropService.addCropRule(crop, "Zea", "mays", true);
List<CropRule> cropRules = new ArrayList<>();
public void tearDown() {
public void listCropsTest() throws Exception {"Start test-method listCropsTest");
.andExpect(jsonPath("$", hasSize(1)))
.andExpect(jsonPath("$[0].id", is(notNullValue())))
.andExpect(jsonPath("$[0].version", is(0)))
.andExpect(jsonPath("$[0].shortName", is("maize")))
.andExpect(jsonPath("$[0].i18n", is(nullValue())))
.andExpect(jsonPath("$[0].name", is("Maize")))
.andExpect(jsonPath("$[0].description", is("Crop description in EN")))
.andDo(document("crop-list"));"Test listCropsTest passed");
public void createCropTest() throws Exception {"Start test-method createCropTest");
.content(objectMapper.writeValueAsString(new Object() {
public String shortName="maize";
public String name="Maize";
public String description="Crop description in EN";
.andExpect(jsonPath("$.id", is(notNullValue())))
.andExpect(jsonPath("$.version", is(0)))
.andExpect(jsonPath("$.shortName", is("maize")))
.andExpect(jsonPath("$.i18n", is(nullValue())))
.andExpect(jsonPath("$.name", is("Maize")))
.andExpect(jsonPath("$.description", is("Crop description in EN")))
fieldWithPath("shortName").description("Crop short name or code (e.g. maize)"),
fieldWithPath("name").description("Crop name in English"),
fieldWithPath("description").optional().description("Crop description in English")
fieldWithPath("id").description("Autogenerated ID"),
fieldWithPath("version").description("Record version"),
fieldWithPath("name").description("Crop name in English"),
fieldWithPath("shortName").description("Crop short name or code (e.g. maize)"),
fieldWithPath("description").description("Crop description in English"),
fieldWithPath("rdfUri").type(JsonFieldType.STRING).optional().description("URI of RDF term describing the crop"),
fieldWithPath("i18n").type(JsonFieldType.STRING).optional().description("i18n map"),
)));"Test createCropTest passed");
public void getCropTest() throws Exception {"Start test-method getCropTest");
mockMvc.perform(get("/api/v0/crops/{shortName}", crop.getShortName())
.andExpect(jsonPath("$.id", is(notNullValue())))
.andExpect(jsonPath("$.version", is(0)))
.andExpect(jsonPath("$.shortName", is("maize")))
.andExpect(jsonPath("$.i18n", is(nullValue())))
.andExpect(jsonPath("$.name", is("Maize")))
.andExpect(jsonPath("$.description", is("Crop description in EN")))
.andDo(document("crop-get", pathParameters(
parameterWithName("shortName").description("Crop short name or code (e.g. maize)")),
fieldWithPath("id").description("Autogenerated ID"),
fieldWithPath("version").description("Record version"),
fieldWithPath("name").description("Crop name in English"),
fieldWithPath("shortName").description("Crop short name or code (e.g. maize)"),
fieldWithPath("description").description("Crop description in English"),