JBoss.orgCommunity Documentation
The Web Services module allows eXo technology to integrate with external products and services.
It is implementation of API for RESTful Web Services with extensions, Servlet and cross-domain AJAX web-frameworks and JavaBean-JSON transformer.
Representational State Transfer (REST)
is a style
of software architecture for distributed hypermedia systems such as the
World Wide Web. The term was introduced in the doctoral dissertation in
2000 by Roy Fielding, one of the principal authors of the Hypertext
Transfer Protocol (HTTP) specification, and has come into widespread use
in the networking community.
REST strictly refers to a collection of network architecture principles that outline how resources are defined and addressed. The term is often used in a looser sense to describe any simple interface that transmits domain-specific data over HTTP without an additional messaging layer such as SOAP or session tracking via HTTP cookies.
The key abstraction of information in REST is a
resource
. Any information that can be named can be a
resource: a document or image, a temporal service (e.g. "today's weather
in Los Angeles"), a collection of other resources, a non-virtual object
(e.g. a person), and so on. In other words, any concept that might be the
target of an author's hypertext reference must fit within the definition
of a resource. A resource is a conceptual mapping to a set of entities,
not the entity that corresponds to the mapping at any particular point in
time.
REST uses a resource identifier
to identify the
particular resource involved in an interaction between components. REST
connectors provide a generic interface for accessing and manipulating the
value set of a resource, regardless of how the membership function is
defined or the type of software that is handling the request. URL or URN
are the examples of a resource identifier.
REST components perform actions with a resource by using a
representation
to capture the current or intended state
of that resource and transferring that representation between components.
A representation is a sequence of bytes, plus representation
metadata
to describe those bytes. Other commonly used but less
precise names for a representation include: document, file, and
HTTP message entity, instance, or variant
. A representation
consists of data, metadata describing the data, and, on occasion, metadata
to describe the metadata (usually for the purpose of verifying message
integrity). Metadata are in the form of name-value pairs, where the name
corresponds to a standard that defines the value's structure and
semantics. The data format of a representation is known as a media
type.
Table 4.1. REST Data Elements
Data Element | Modern Web Examples |
---|---|
resource | the intended conceptual target of a hypertext reference |
resource identifier | URL, URN |
representation | HTML document, JPEG image |
representation metadata | media type, last-modified time |
resource metadata | source link, alternates, vary |
control data | if-modified-since, cache-control |
REST uses various connector
types to encapsulate
the activities of accessing resources and transferring resource
representations. The connectors present an abstract interface for
component communication, enhancing simplicity by providing a complete
separation of concepts and hiding the underlying implementation of
resources and communication mechanisms.
Table 4.2. REST Connectors
Connector | Modern Web Examples |
---|---|
client | libwww, libwww-perl |
server | libwww, Apache API, NSAPI |
cache | browser cache, Akamai cache network |
resolver | bind (DNS lookup library) |
tunnel | SOCKS, SSL after HTTP CONNECT |
The primary connector types are client and server. The essential difference between the two is that a client initiates communication by making a request, whereas a server listens for connections and responds to requests in order to supply access to its services. A component may include both client and server connectors.
An important part of RESTful architecture is a well-defined interface to communicate, in particular it is a set of HTTP methods such as POST, GET, PUT and DELETE. These methods are often compared with the CREATE, READ, UPDATE, DELETE (CRUD) operations associated with database technologies. An analogy can also be made:
PUT is analogous to CREATE or PASTE OVER,
GET to READ or COPY,
POST to UPDATE or PASTE AFTER, and
DELETE to DELETE or CUT.
RESTful architecture is not limited to those methods, one of good examples of extension is the WebDAV protocol.
The CRUD
(Create, Read, Update and Delete) verbs
are designed to operate with atomic data within the context of a database
transaction. REST is designed around the atomic transfer of a more complex
state and can be viewed as a mechanism for transferring structured
information from one application to another.
HTTP separates the notions of a web server and a web browser. This
allows the implementation of each to vary from the other based on the
client/server principle. When used RESTfully, HTTP is
stateless
. Each message contains all the information
necessary to understand the request.
As a result, neither the client nor the server needs to remember any communication-state between messages. Any state retained by the server must be modeled as a resource.
This section will show you how to overwrite the default providers in eXo JAX-RS implementation.
There is set of providers embedded in eXo JAX-RS implementation.
Implementations of MessageBodyReader and MessageBodyWriters are taking care about serialization/deserialization of message body (HTTP request/response's body).
The next set of media and Java types processed automatically, thanks to embedded Readers (Writers).
Table 4.3. Embedded Reader and Writers of message body
Media Type | Java Type |
---|---|
*/* | byte[] |
*/* | javax.activation.DataSource |
*/* | java.io.File |
*/* | java.io.InputStream |
*/* | java.io.Reader |
*/* | java.lang.String |
*/* | javax.ws.rs.core.StreamingOutput (Writer ONLY) |
application/json | 1. Object with simple constructor + get/set methods; 2. Java Collection (java.uitl.List<T>, java.uitl.Set<T>, java.util.Map<String, T>, etc) where T as described in 1. |
application/x-www-form-urlencoded | javax.ws.rs.core.MultivaluedMap<String, String> |
multipart/* | java.util.Iterator<org.apache.commons.fileupload.FileItem> |
application/xml, application/xhtml+xml, text/xml | javax.xml.bind.JAXBElement |
application/xml, application/xhtml+xml, text/xml | Object with JAXB annotations |
application/xml, application/xhtml+xml, text/xml | javax.xml.transform.stream.StreamSource |
application/xml, application/xhtml+xml, text/xml | javax.xml.transform.sax.SAXSource |
application/xml, application/xhtml+xml, text/xml | javax.xml.transform.dom.DOMSource |
In some case it may be required to use alternative provider for the same media and java type but such changes must not impact to any other services.
To be able overwrite default JAX-RS provider(s) developer need:
Deploy own RESTful service(s) by using subclass of javax.ws.rs.core.Application (hereinafter Application).
Service(s) NOT NEED to implement marker interface ResourceContainer and MUST NOT be configured as component(s) of eXo Container. Instead of it Application must be configured as component of eXo Container.
If RESTful services or providers require some dependencies from eXo Container then Application should inject it by own constructor and then delegate to services or providers. As alternative method getClasses() may be used for deliver services/providers classes instead of instances. In this case services/providers will work in per-request mode and RESTful framework will take care about resolving dependencies.
In example below see how to use Jackson JSON provider instead of embedded in eXo RESTful framework.
Create subclass of javax.ws.rs.core.Application with code as bellow and add it to the eXo Container configuration.
package org.exoplatform.test.jackson; import org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider; import java.util.HashSet; import java.util.Set; import javax.ws.rs.core.Application; public class Application1 extends Application { @Override public Set<Class<?>> getClasses() { Set<Class<?>> cls = new HashSet<Class<?>>(1); cls.add(Resource1.class); return cls; } @Override public Set<Object> getSingletons() { Set<Object> objs = new HashSet<Object>(1); objs.add(new JacksonJaxbJsonProvider()); return objs; } }
In this example we assumes Resource1 is Java class which carries JAX-RS annotations and it uses JSON. In this case RESTful framework will use JacksonJaxbJsonProvider for serializing/deserializing of all messages to/from Resource1. But it will not impact to other services in any kind.
RestServicesList service provides information about REST services deployed to the application server.
Path - path to service
Regex - service's URL regular expression
FQN - full qualified name of service's class
The list can be provided in two formats: HTML and JSON.
Class does not implement org.exoplatform.services.rest.resource.ResourceContainer and must never be binded to RESTful framework by using eXoContainer. This service must works as per-request resource.
To get the list of services in HTML format use listHTML() method:
@GET @Produces({MediaType.TEXT_HTML}) public byte[] listHTML() { ... }
To do this, perform a simple GET request to the RestServicesList link.
f.e. curl -u root:exo http://localhost:8080/rest/ will return such HTML code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" > <html> <head> <title>eXo JAXRS Implementation</title> </head> <body> <h3 style="text-align:center;">Root resources</h3> <table width="90%" style="table-layout:fixed;"> <tr> <th>Path</th> <th>Regex</th> <th>FQN</th> </tr> <tr> <td>script/groovy</td> <td>/script/groovy(/.*)?</td> <td>org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader</td> </tr> <tr> <td>/lnkproducer/</td> <td>/lnkproducer(/.*)?</td> <td>org.exoplatform.services.jcr.webdav.lnkproducer.LnkProducer</td> </tr> <tr> <td>/registry/</td> <td>/registry(/.*)?</td> <td>org.exoplatform.services.jcr.ext.registry.RESTRegistryService</td> </tr> <tr> <td>/jcr</td> <td>/jcr(/.*)?</td> <td>org.exoplatform.services.jcr.webdav.WebDavServiceImpl</td> </tr> <tr> <td>/</td> <td>(/.*)?</td> <td>org.exoplatform.services.rest.ext.service.RestServicesList</td> </tr> </table> </body> </html>
If you perform the same request with your browser, you'll see the table with the list of deployed services like this:
Table 4.4. Root resources
Path | Regex | FQN |
---|---|---|
script/groovy | /script/groovy(/.*)? | org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader |
/lnkproducer/ | /lnkproducer(/.*)? | org.exoplatform.services.jcr.webdav.lnkproducer.LnkProducer |
/registry/ | /registry(/.*)? | org.exoplatform.services.jcr.ext.registry.RESTRegistryService |
/jcr | /jcr(/.*)? | org.exoplatform.services.jcr.webdav.WebDavServiceImpl |
/ | (/.*)? | org.exoplatform.services.rest.ext.service.RestServicesList |
To get the list of services in HTML format use listJSON() method:
@GET @Produces({MediaType.APPLICATION_JSON}) public RootResourcesList listJSON() { ... }
To do this, add "Accept:application/json" header to your GET request
f.e. curl -u root:exo http://localhost:8080/rest/ -H "Accept:application/json" will return such JSON:
{"rootResources":[ { "fqn":"org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader", "regex":"/script/groovy(/.*)?", "path":"script/groovy" }, { "fqn":"org.exoplatform.services.jcr.webdav.lnkproducer.LnkProducer", "regex":"/lnkproducer(/.*)?", "path":"/lnkproducer/" }, { "fqn":"org.exoplatform.services.jcr.ext.registry.RESTRegistryService", "regex":"/registry(/.*)?", "path":"/registry/" }, { "fqn":"org.exoplatform.services.jcr.webdav.WebDavServiceImpl", "regex":"/jcr(/.*)?", "path":"/jcr" }, { "fqn":"org.exoplatform.services.rest.ext.service.RestServicesList", "regex":"(/.*)?", "path":"/" } ]}
This section describes how to use Groovy scripts as REST services. Consider these operations:
Load script and save it in JCR.
Instantiate script
Deploy a newly created Class as RESTful service.
Script Lifecycle Management.
Finally, we will discover simple example which can get JCR node UUID
In this section, we consider RESTful service to be compatible with JSR-311 specification. The last feature is currently available in version 1.11-SNAPSHOT.
There are two ways to save a script in JCR. The first way is to save it at server startup time by using configuration.xml and the second way is to upload the script via HTTP.
Load script at startup time
This way can be used for load prepared scripts, to use this way. we must configure org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoaderPlugin. This is simple configuration example.
<external-component-plugins> <target-component>org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader</target-component> <component-plugin> <name>test</name> <set-method>addPlugin</set-method> <type>org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoaderPlugin</type> <init-params> <value-param> <name>repository</name> <value>repository</value> </value-param> <value-param> <name>workspace</name> <value>production</value> </value-param> <value-param> <name>node</name> <value>/script/groovy</value> </value-param> <properties-param> <name>JcrGroovyTest.groovy</name> <property name="autoload" value="true" /> <property name="path" value="file:/home/andrew/JcrGroovyTest.groovy" /> </properties-param> </init-params> </component-plugin> </external-component-plugins>
The first is value-param sets JCR repository, the second is value-param sets workspace and the third one is sets JCR node where scripts from plugin will be stored. If specified node does not exist, then it will be created. List of scripts is set by properties-params. Name of each properties-param will be used as node name for stored script, property autoload says to deploy this script at startup time, property path sets the source of script to be loaded. In this example we try to load single script from local file /home/andrew/JcrGroovyTest.groovy.
Load script via HTTP
This is samples of HTTP requests. In this example, we will upload script from file with name test.groovy.
andrew@ossl:~> curl -u root:exo \ -X POST \ -H 'Content-type:script/groovy' \ --data-binary @test.groovy \ http://localhost:8080/rest/script/groovy/add/repository/production/script/groovy/test.groovy
This example imitate sending data with HTML form ('multipart/form-data'). Parameter autoload is optional. If parameter autoload=true then script will be instantiate and deploy script immediately.
andrew@ossl:~> curl -u root:exo \ -X POST \ -F "file=@test.groovy;name=test" \ -F "autoload=true" \ http://localhost:8080/rest/script/groovy/add/repository/production/script/groovy/test1.groovy
org.exoplatform.services.script.groovy.GroovyScriptInstantiator is part of project exo.core.component.script.groovy. GroovyScriptInstantiator can load script from specified URL and parse stream that contains Groovy source code. It has possibility inject component from Container in Groovy Class constructor. Configuration example:
<component> <type>org.exoplatform.services.script.groovy.GroovyScriptInstantiator</type> </component>
To deploy script automatically at server statup time, its property exo:autoload must be set as true. org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader check JCR workspaces which were specified in configuration and deploy all auto-loadable scripts.
Example of configuration.
<component> <type>org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader</type> <init-params> <object-param> <name>observation.config</name> <object type="org.exoplatform.services.jcr.ext.script.groovy.ObservationListenerConfiguration"> <field name="repository"> <string>repository</string> </field> <field name="workspaces"> <collection type="java.util.ArrayList"> <value> <string>production</string> </value> </collection> </field> </object> </object-param> </init-params> </component>
In example above JCR workspace "production" will be checked for autoload scripts. At once, this workspace will be listened for changes script's source code (property jcr:data).
If GroovyScript2RestLoader configured as was decribed in the previous section, then all "autoload" scripts deployed. In the first section, we added script from file /home/andrew/JcrGroovyTest.groovy to JCR node /script/groovy/JcrGroovyTest.groovy, repository repository, workspace production. In section "Load script via HTTP", it was refered about load scripts via HTTP, there is an opportunity to manage the life cycle of script.
Undeploy script, which is alredy deployed:
andrew@ossl:~> curl -u root:exo \ -X GET \ http://localhost:8080/rest/script/groovy/load/repository/production/script/groovy/JcrGroovyTest.groovy?state=false
Then deploy it again:
andrew@ossl:~> curl -u root:exo \ -X GET \ http://localhost:8080/rest/script/groovy/load/repository/production/script/groovy/JcrGroovyTest.groovy?state=true
or even more simple:
andrew@ossl:~> curl -u root:exo \ -X GET \ http://localhost:8080/rest/script/groovy/load/repository/production/script/groovy/JcrGroovyTest.groovy
Disable scripts autoloading, NOTE it does not change current state:
andrew@ossl:~> curl -u root:exo \ -X GET \ http://localhost:8080/rest/script/groovy/repository/production/script/groovy/JcrGroovyTest.groovy/autoload?state=false
Enable it again:
andrew@ossl:~> curl -u root:exo \ -X GET \ http://localhost:8080/rest/script/groovy/autoload/repository/production/script/groovy/JcrGroovyTest.groovy?state=true
and again more simpe variant:
andrew@ossl:~> curl -u root:exo \ -X GET \ http://localhost:8080/rest/script/groovy/autoload/repository/production/script/groovy/JcrGroovyTest.groovy
Change script source code:
andrew@ossl:~> curl -u root:exo \ -X POST \ -H 'Content-type:script/groovy' \ --data-binary @JcrGroovyTest.groovy \ http://localhost:8080/rest/script/groovy/update/repository/production/script/groovy/JcrGroovyTest.groovy
This example imitates sending data with HTML form ('multipart/form-data').
andrew@ossl:~> curl -u root:exo \ -X POST \ -F "file=@JcrGroovyTest.groovy;name=test" \ http://localhost:8080/rest/script/groovy/update/repository/production/script/groovy/JcrGroovyTest.groovy
Remove script from JCR:
andrew@ossl:~> curl -u root:exo \ -X GET \ http://localhost:8080/rest/script/groovy/delete/repository/production/script/groovy/JcrGroovyTest.groovy
Now we are going to try simple example of Groovy RESTfull service. There is one limitation, even if we use groovy, we should use Java style code and decline to use dynamic types, but of course we can use it in private methods and feilds. Create file JcrGroovyTest.groovy, in this example I save it in my home directory /home/andrew/JcrGroovyTest.groovy. Then, configure GroovyScript2RestLoaderPlugin as described in section Load script at startup time.
import javax.jcr.Node import javax.jcr.Session import javax.ws.rs.GET import javax.ws.rs.Path import javax.ws.rs.PathParam import org.exoplatform.services.jcr.RepositoryService import org.exoplatform.services.jcr.ext.app.ThreadLocalSessionProviderService @Path("groovy/test/{repository}/{workspace}") public class JcrGroovyTest { private RepositoryService repositoryService private ThreadLocalSessionProviderService sessionProviderService public JcrGroovyTest(RepositoryService repositoryService, ThreadLocalSessionProviderService sessionProviderService) { this.repositoryService = repositoryService this.sessionProviderService = sessionProviderService } @GET @Path("{path:.*}") public String nodeUUID(@PathParam("repository") String repository, @PathParam("workspace") String workspace, @PathParam("path") String path) { Session ses = null try { ses = sessionProviderService.getSessionProvider(null).getSession(workspace, repositoryService.getRepository(repository)) Node node = (Node) ses.getItem("/" + path) return node.getUUID() + "\n" } finally { if (ses != null) ses.logout() } }
After configuration is done, start the server. If configuration is correct and script does not have syntax error, you should see next:
In the screenshot, we can see the service deployed.
First, create a folder via WebDAV in the repository production, folder name 'test'. Now, we can try access this service. Open another console and type command:
andrew@ossl:~> curl -u root:exo \ http://localhost:8080/rest/groovy/test/repository/production/test
Whe you try to execute this command, you should have exception, because JCR node '/test' is not referenceable and has not UUID. We can try add mixin mix:referenceable. To do this, add one more method in script. Open script from local source code /home/andrew/JcrGroovyTest.groovy, add following code and save file.
@POST @Path("{path:.*}") public void addReferenceableMixin(@PathParam("repository") String repository, @PathParam("workspace") String workspace, @PathParam("path") String path) { Session ses = null try { ses = sessionProviderService.getSessionProvider(null).getSession(workspace, repositoryService.getRepository(repository)) Node node = (Node) ses.getItem("/" + path) node.addMixin("mix:referenceable") ses.save() } finally { if (ses != null) ses.logout() } }
Now we can try to change script deployed on the server without server restart. Type in console next command:
andrew@ossl:~> curl -i -v -u root:exo \ -X POST \ --data-binary @JcrGroovyTest.groovy \ -H 'Content-type:script/groovy' \ http://localhost:8080/rest/script/groovy/update/repository/production/script/groovy/JcrGroovyTest.groovy
Node '/script/groovy/JcrGroovyTest.groovy' has property exo:autoload=true so script will be re-deployed automatically when script source code changed.
Script was redeployed, now try to access a newly created method.
andrew@ossl:~> curl -u root:exo \ -X POST \ http://localhost:8080/rest/groovy/test/repository/production/test
Method excution should be quiet, without output, traces, etc. Then we can try again get node UUID.
andrew@ossl:~> curl -u root:exo \ http://localhost:8080/rest/groovy/test/repository/production/test 1b8c88d37f0000020084433d3af4941f
Node UUID: 1b8c88d37f0000020084433d3af4941f
We don't need this scripts any more, so remove it from JCR.
andrew@ossl:~> curl -u root:exo \ http://localhost:8080/rest/script/groovy/delete/repository/production/script/groovy/JcrGroovyTest.groovy
You should keep one class per one groovy file. The same actually for interface and it implementation. It's limitation of groovy parser that does not have type Class[] parseClass(InputStream) or Collection parseClass(InputStream) but only Class parseClass(InputStream) instead.
That is all.
eXo Webservice provides a framework to cross-domain AJAX. This section shows you how to use this framework.
You can checkout the source code at https://github.com/exoplatform/ws/tree/stable/2.4.x/exo.ws.frameworks.javascript.cross-domain-ajax.
XmlHttpRequest objects are bound by the same origin security policy of browsers, which prevents a page from accessing data from another server. This has put a serious limitation on Ajax developers: you can use XmlHttpRequests to make background calls to a server, but it has to be the same server that served up the current page. For more details, you can visit http://www.mozilla.org/projects/security/components/same-origin.html.
But actually writing client web applications that use this object can be tricky given restrictions imposed by web browsers on network connections across domains. So you need to find the way to bypass this limitation of AJAX.
To describe our method for cross-domain AJAX solution, let's consider the following scheme contains of 3 components:
1). User agent (a browser).
2). ServerA contains a main page with dedicated client and server IFRAMEs (see below) and an HTML client page (client.html) referenced from the client IFRAME. This client page contains dedicated script to push the data for request into server IFRAME.
3). ServerB contains remote service that want get access to and an HTML server page (server.html) referenced from the server IFRAME. This server page contains dedicated script to push the requested data into client IFRAME.
1) A Browser requests the Start page from the ServerA
2) The Start page is retrieved from the ServerA.
3) Create in the start page IFRAME (name it - "client iframe") and insert it in the document from ServerA (client.
4) In "client iframe" create ne IFRAME element ("server iframe") and insert it in the document from ServerB (server.html). Documents (client.html and server.html) contain special script that can transfer data between ifarmes.
5) "Client iframe" transfer information about HTTP method and URL that we want do cross-domain request to "server iframe".
6) "Server iframe" do simple XmlHttpRequest to the service that we need (it can do that because download from same domain) and get informaton from service.
7) "Server iframe" transfer data to "client iframe" and now we get information that we want.
1). Place the file client.html and xda.js on the serverA.
2). Place the file server.html on the serverB.
3). Declare xda.js in the main page.
<script type="text/javascript" src="xda.js"></script>
4). Create JS function which performs cross domain call as in the following example:
<script type="text/javascript"> function test(){ var facade = xdaInit(); facade.clientURI = "http://localhost/cross-domain-ajax/client/client.html"; facade.serverURI = "http://localhost:8080/cross-domain-ajax/server/server.html"; facade.apiURI = "http://localhost:8080/cross-domain-ajax/server/test.txt"; facade.method = "POST"; facade.load = function(result) { alert(result.responseText); } facade.setRequestHeader("keep-alive","200"); xda.create(facade); } </script>
5). Use this function (here it is bound to a button's onclick event).
<button onclick='test()'>test cross-domain</button>