style used for web development. Systems and sites designed using this style aim for fast performance, reliability and the ability to scale (to grow and easily support extra users). To achieve these goals, developers work with reusable components that can be managed and updated without affecting the system as a whole while it is running. Source: https://en.wikipedia.org/wiki/Representational_state_transfer
Create New Contact Delete Contacts /contacts/id Retrieve a Contact Replace a Contact N/A (generally) Delete a Contact Source: https://en.wikipedia.org/wiki/Representational_state_transfer Conventions on URLs
Services (DAS) No Backend Code Zero-setup Limited Control No Business Logic Exposes the Internals Simple internal integrations ExtLib Components for REST Less Backend Code Minimal Setup Partial/Full Customization Error Handling Spaghetti Code URL Conventions Simple needs for a limited scope Hardcoding (XAgents, Web agents) Tailor-made No Learning Curve Hardcoding Everything Spaghetti Code URL Conventions Very specific needs for a limited scope Apache Wink Servlets Tailor-made Based on JAX-RS OSGi Benefits Learning Curve Barrier to Entry Large scope implementations, API Design
Subresource Resource • Any addressable object is a resource. • A resource class is; • Implements RESTful interactions (GET, POST, etc.) • A pure Java object decorated with annotations • Do not confuse with Model class. Resource Representation • The content of an object is called as Representation • JSON, XML, Text, Form data, etc.
new DominoAccessor(ContextInfo.getUserSession()); @GET() public Response getContactList( @QueryParam("start") int start, @QueryParam("count") int count) { List<Contact> contactList = accessor.pullContacts(start, count); String result = ModelUtils.toJson(contactList).toString(); return Response.ok(result, MediaType.APPLICATION_JSON).build(); } @Path("/{id}") @GET() public Response getContact(@PathParam("id") String id) { Contact contact = accessor.findContact(id); if(null == contact) { throw new WebApplicationException(Response.Status.NOT_FOUND); } else { String result = ModelUtils.toJson(contact).toString(); return Response.ok(result, MediaType.APPLICATION_JSON).build(); } } } The base URI for the resource In the demo, the root path of the plugin is “/twink”. So this class is enabled for requests made to: /twink/contacts/*
new DominoAccessor(ContextInfo.getUserSession()); @GET() public Response getContactList( @QueryParam("start") int start, @QueryParam("count") int count) { List<Contact> contactList = accessor.pullContacts(start, count); String result = ModelUtils.toJson(contactList).toString(); return Response.ok(result, MediaType.APPLICATION_JSON).build(); } @Path("/{id}") @GET() public Response getContact(@PathParam("id") String id) { Contact contact = accessor.findContact(id); if(null == contact) { throw new WebApplicationException(Response.Status.NOT_FOUND); } else { String result = ModelUtils.toJson(contact).toString(); return Response.ok(result, MediaType.APPLICATION_JSON).build(); } } } This method responds to GET requests. No path defined, so this is the default responder.
new DominoAccessor(ContextInfo.getUserSession()); @GET() public Response getContactList( @QueryParam("start") int start, @QueryParam("count") int count) { List<Contact> contactList = accessor.pullContacts(start, count); String result = ModelUtils.toJson(contactList).toString(); return Response.ok(result, MediaType.APPLICATION_JSON).build(); } @Path("/{id}") @GET() public Response getContact(@PathParam("id") String id) { Contact contact = accessor.findContact(id); if(null == contact) { throw new WebApplicationException(Response.Status.NOT_FOUND); } else { String result = ModelUtils.toJson(contact).toString(); return Response.ok(result, MediaType.APPLICATION_JSON).build(); } } } This method also responds to GET requests. But it the request path will be elected based on this format.
new DominoAccessor(ContextInfo.getUserSession()); @GET() public Response getContactList( @QueryParam("start") int start, @QueryParam("count") int count) { List<Contact> contactList = accessor.pullContacts(start, count); String result = ModelUtils.toJson(contactList).toString(); return Response.ok(result, MediaType.APPLICATION_JSON).build(); } @Path("/{id}") @GET() public Response getContact(@PathParam("id") String id) { Contact contact = accessor.findContact(id); if(null == contact) { throw new WebApplicationException(Response.Status.NOT_FOUND); } else { String result = ModelUtils.toJson(contact).toString(); return Response.ok(result, MediaType.APPLICATION_JSON).build(); } } } There are lots of options of returning response. ResponseBuilders and some other helpers make it quite easy.
new DominoAccessor(ContextInfo.getUserSession()); @GET() public Response getContactList( @QueryParam("start") int start, @QueryParam("count") int count) { List<Contact> contactList = accessor.pullContacts(start, count); String result = ModelUtils.toJson(contactList).toString(); return Response.ok(result, MediaType.APPLICATION_JSON).build(); } @Path("/{id}") @GET() public Response getContact(@PathParam("id") String id) { Contact contact = accessor.findContact(id); if(null == contact) { throw new WebApplicationException(Response.Status.NOT_FOUND); } else { String result = ModelUtils.toJson(contact).toString(); return Response.ok(result, MediaType.APPLICATION_JSON).build(); } } } Wink handles much of the error handling. Still you can inject your own errors.
Response postContactJson(String body) { Contact contact = ModelUtils.buildContactfromJson(body); accessor.saveNewContact(contact); String result = ModelUtils.toJson(contact).toString(); return Response.ok(result, MediaType.APPLICATION_JSON).build(); } @POST() @Consumes(MediaType.MULTIPART_FORM_DATA) public Response postContactForm(BufferedInMultiPart formData) { Contact contact = ModelUtils.buildContactfromMultipart(formData); accessor.saveNewContact(contact); String result = ModelUtils.toJson(contact).toString(); return Response.ok(result, MediaType.APPLICATION_JSON).build(); } } This methods respond to POST requests. This time the selection depends on the incoming data type. Client marks the request with Content-Type header and Wink will select the appropriate method here.
Response postContactJson(String body) { Contact contact = ModelUtils.buildContactfromJson(body); accessor.saveNewContact(contact); String result = ModelUtils.toJson(contact).toString(); return Response.ok(result, MediaType.APPLICATION_JSON).build(); } @POST() @Consumes(MediaType.MULTIPART_FORM_DATA) public Response postContactForm(BufferedInMultiPart formData) { Contact contact = ModelUtils.buildContactfromMultipart(formData); accessor.saveNewContact(contact); String result = ModelUtils.toJson(contact).toString(); return Response.ok(result, MediaType.APPLICATION_JSON).build(); } } Wink injects the incoming data into the method automatically. Apache Wink also provides several classes to process different data formats (Multipart, Atom, XML, JSON, etc.)
(Resources, Representations, actions, etc.) The distribution of tasks (Front-end and Back-end) responsibilities Collaborate with consumers, if you can Versioning / Test API
Subresource Model Classes Data Objects Conversion Resource Representation ←→ Model Data Access Model ←→ Documents Business Logic Actions (CRUD, etc.) Rules, validations, etc. Databases Resource Resource Documents Resource Resource Views Resource Resource etc. Security Utilities
class or method • @GET, @PUT, @POST, @DELETE, @HEAD Specify the HTTP request type of a resource • @Produces Specifies the response Internet media types (content negotiation) • @Consumes Specifies the accepted request Internet media types.
segment • @QueryParam, @MatrixParam, @FormParam Binds the method parameter to a query/matrix/form parameter • @HeaderParam, @CookieParam Binds the method parameter to a HTTP header/cookie parameter • @Context Returns the entire context of the object @Context HttpServletRequest request • @DefaultValue Specifies a default value for the above bindings when the key is not found. @Default(“1”) @QueryParam(“start”) int start
and representations. Wink comes with several providers and more can be developed for special purposes. • @Asset More advanced implementation of providers. Especially suitable for automatic transformation between data objects and representations. • @Parent Defines a parent resource that has a base URI. (See Versioning) • @Scope By default, every resource class instantiated per request. Scope can define longer life cycles for resource instances (e.g. singletons).
helpers • A library for JSON processing strongly suggested • Hardcoding JSON data structure becomes more and more difficult. • Automatic Serialization / Deserialization is life saving • Tip: Look into Jackson and GSON libraries
com.developi.wink.demo.api.v1.PingResource { @GET public Response ping() { return Response.ok("<h1>Hello World Version 1!</h1>", MediaType.TEXT_HTML).build(); } } @Parent(com.developi.wink.demo.api.v2.VersionRoot.class) @Path("/ping") public class com.developi.wink.demo.api.v2.PingResource { @GET public Response ping() { return Response.ok("<h1>Hello World Version 2!</h1>", MediaType.TEXT_HTML).build(); } } @Path("/v2") public class com.developi.wink.demo.api.v2.VersionRoot {} Responds to “/root/v2/ping” Responds to “/root/v1/ping”
ContextInfo.getUserSession() • At the servlet level, • No SessionAsSigner • No SessionAsSignerWithFullAccess • No CurrentDatabase • Elevated level of access is a bit tricky. • Refer to DominoRunner XSnippet
Apache Wink • One trick: You need to customize the servlet • Refer to the blog post by Paul Withers • Advantages • No recycle! • Modern Java practices (Maps, generics, etc.) • Much better development experience • Ability to use elevated session • Refer to the OpenNTF Domino API Project page for more
Experiment JAX-RS annotations Get yourself familiar with Plugin development Download Extension Library source code and look its design Study on RESTful design practices and JAX-RS concepts
https://github.com/sbasegmez/RestAssuredDemo • Apache Wink Project https://wink.apache.org/ • Paul Withers: From XPages Hero To OSGi Guru: Taking The Scary Out Of Building Extension Libraries http://www.slideshare.net/paulswithers1/ibm-connected-2015-mas103-xpages-performance-and-scalability • Paul Withers: XPages OSGi Plugins series http://www.intec.co.uk/xpages-osgi-plugins-1-an-introduction/ • John Cooper: Domino OSGI (Part 1) - Configuring Eclipse for XPages OSGI Plugins http://developmentblog.johnmcooper.co.uk/2014/05/configuring-eclipse-for-xpages-osgi-plugins-part1.html • John Dalsgaard: Wrap An Existing Jar File Into A Plug-in https://www.dalsgaard-data.eu/blog/wrap-an-existing-jar-file-into-a-plug-in/ • Toby Samples: JAX-RS or THE way to do REST in Domino series https://tobysamples.wordpress.com/2015/04/28/jax-rs-or-the-way-to-do-rest-in-domino-part-1/ • Jesse Gallagher: Eclipse Tutorial for Domino Developers https://github.com/jesse-gallagher/eclipse-tutorial-oct2015/wiki/Java