JBoss.orgCommunity Documentation

Chapter 4. eXoWS

4.1. Introduction to the Representational State Transfer (REST)
4.2. Overwrite default providers
4.2.1. Motivation
4.2.2. Usage
4.2.3. Example
4.3. RestServicesList Service
4.3.1. Usage
4.4. Groovy Scripts as REST Services
4.4.1. Loading script and save it in JCR
4.4.2. Instantiation
4.4.3. Deploying newly created Class as RESTful service
4.4.4. Script Lifecycle Management
4.4.5. Getting node UUID example
4.4.6. Groovy script restrictions
4.5. Framework for cross-domain AJAX
4.5.1. Motivation
4.5.2. Scheme (how it works)
4.5.3. A Working Sequence:
4.5.4. How to use it

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.


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.


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.

Note

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.

RestServicesList service provides information about REST services deployed to the application server.

The list can be provided in two formats: HTML and JSON.

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:


This section describes how to use Groovy scripts as REST services. Consider these operations:

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

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

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.