Adding a Web Service Step by Step Guide for Core Developers
This guide walks a core developer through adding a new set of web service methods for a core object. It is a similar process for module developers, but for that setup, see Adding a Web Service Step-by-Step Guide for Module Developers
Get the Code
The module can be checked out through git at https://github.com/openmrs/openmrs-module-webservices.rest
You can build the module like any other mavenized module by doing a mvn clean install
in the root module package.
Read the Conventions
The first thing to do is to read through the conventions section on the REST Web Services API For Clients. This outlines the very basics of how to name the URLs, design the objects, etc.
Note: When creating a REST resource and sub-resource URI and name respectively, the absolute and relative path names should be written in all-lowercase ASCII letters. Avoid capital letters, and camel caps to mention a few. The norm is to use lowercase letters.
Example:
/ws/rest/v1/conceptclass
Testing a URL
Through the webapp
With the webservices.rest module installed, there is a Test page linked to from the Administration page.
From this page, you can make jQuery POST/PUSH/GET/DELETE web service calls to the server and see the results.
The default value is to do a GET on a person. Check your database for a valid person UUID and try the query. (The post data does not matter in this case)
From the command line with CURL
A valid curl call with authentication:
curl -u admin:password http://localhost:8080/openmrs/ws/rest/person/b3f64e4c-860c-11e0-94eb-0027136865c4
Adding new Web Service URLs
Our best practices may change over the course as we learn new things, but ConceptResource1_8 should always be an up-to-date best-practice example that you can use as a pattern.
This example will use the Location object in the OpenMRS API.
Create a new LocationResource1_8 object for the object (note the 1_8 postfix indicating the lowest version of OpenMRS this resource can work with)
See also code for LocationResource1_8
The resource objects are used for the automatic translation into JSON/XML.
In the org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8 package, create a new class that extends
MetadataDelegatingCrudResource<Location>
.public class LocationResource1_8 extends MetadataDelegatingCrudResource<Location>
The MetadataDelegatingCrudResource and DataDelegatingCrudResource classes are helper classes for "metadata" and "data" OpenmrsObjects.
The DelegatingCrudResource superclass can be used for any other object type
It's also possible to not have a resource class for some simple rest URLs. See the SessionController class.
Add @Resource(name = "v1/location", supportedClass = Location.class, supportedOpenmrsVersions = "1.8.")* annotation to the class.
This tells the framework to put the resource at uri location: /ws/rest/location/uuid
By convention, resource names are all lowercase.
SupportedClass indicates to use this resource for the Location class and supportedOpenmrsVersions indicates to use this resource only when the module is running on OpenMRS 1.8.*.
Optionally you can specify order, which will allow you to overwrite some other resource e.g. a module-provided resource for Location. Note that this will work only if you are using the same uri to access the resource you are overwriting.
Expose properties that are on the Location object through the resource:
Create a method named "DelegatingResourceDescription getRepresentationDescription(Representation rep)"
Switch on the "rep" argument (or use if/else)
Create a DelegatingResourceDescription according to the rep, using addProperty for the properties that you need
Tip: Use description.addProperty("auditInfo", findMethod("getAuditInfo")) for not creator/dateCreated/changedBy/dateChanged properties
Tip2: the MetadataDelegatingCrudResource class defines the "ref" representation for you.
DO NOT add locationId to the getRepresentationDescription method. That is an internal number that should never be exposed over web services
Adding a new property (fullAddress) that is not on the Location object (only done here as an example. Patient.name is a better example)
There is no Location.getFullAddress() method, so automatic translation of this into JSON/XML will fail in the webservices module.
Add description.addProperty("fullAddress", findMethod("getFullAddress")) to the getRepresentationDescription method
Add a method SimpleObject getFullAddress(Location location) on the LocationResource class.
This method is called anytime a user requests to see the "fullAddress" property in the JSON/XML
The implementation of the method can be anything using the Location object. Something like:
return location.getAddress1() + " " + location.getAddress2() + " " + location.getCityVillage() + " " + location.getStateProvince()
Add a
newDelegate()
methodThis is used by the parent class to know how to construct our object (our delegate)
Add a
save(Location location)
methodThis is used by the parent class to know how to persist our object to the database. This is typically just Context.getSomeService().saveMyObject(object)
Add a
getByUniqueId(String s)
methodThe parent class calls this for all getters. You should look up your object by uuid. If your object can also be looked up by a unique name, you can do that in this method as well as the uuid.
This method is what allows your object to be a spring "Converter". So other WS methods can refer to your object by uuid/name as well
DO NOT look up by primary key. Primary keys should not be exposed over web services
Add a
purge(Location location, RequestContext context)
methodYou can get the reason (if needed) out of the request context
Add a
doGetAll(RequestContext context)
This should return a list of every location object that is still valid (not retired)
The context object has parameters like "limit", "maxresults", "start", etc
Add
doSearch(String query, RequestContext context)
(see Adding Web Service Search Handler on how to implement custom searches)This should return a list of location objects with a fuzzy search on the query.
The context object has parameters like "limit", "maxresults", "start", etc
There is a helper class named ServiceSearcher that you can use to help with the paging, index, etc
If you have a non-standard setter method:
TODO
Sub-resources (like patient name)
TODO