Let's take the example of a simple Nuxeo WebService that exposes a method to create a document in the default repository. The user credentials are passed to the method to log in and open a core session.
The code samples used for this example can be found on GitHub.
WebService Interface
First you have to define the interface of your WebService, answering the question: "Which methods do I want to expose?"
If we take a look at NuxeoSampleWS
, a standard signature defines the createDocument
method.
DocumentDescriptor createDocument(String username, String password,
String parentPath, String type, String name) throws LoginException,
ClientException;
The different parameters are:
username
: used to log in and open a core sessionpassword
: used to log in and open a core sessionparentPath
: document parent pathtype
: document typename
: doc name
The return type DocumentDescriptor
is a simple POJO containing minimal information about a document.
Webservice Implementation
You just need a few annotations from the javax.jws
package to implement your SOAP based WebService.
@WebService
Allows you to define the actual WebService. You need to provide the following parameters:
name
: the name of the WebService, used as the name of thewsdl:portType
when mapped to WSDL 1.1.serviceName
: the service name of the WebService, used as the name of thewsdl:service
when mapped to WSDL 1.1.
@SOAPBinding
Specifies the mapping of the WebService onto the SOAP message protocol. You can provide the following parameters:
style
: defines the encoding style for messages send to and from the WebService. Keep the default valueStyle.DOCUMENT
.use
: defines the formatting style for messages sent to and from the WebService. Keep the default valueUse.LITERAL
.
@WebMethod
Exposes a method as a WebService operation. Such a method must be public.
@WebParam
Maps a method parameter to a WebService operation parameter. You need to provide the name
of the parameter.
Take a look at NuxeoSampleWSImpl
which uses all these annotations:
@WebService(name = "NuxeoSampleWebServiceInterface", serviceName = "NuxeoSampleWebService")
@SOAPBinding(style = Style.DOCUMENT)
public class NuxeoSampleWSImpl implements NuxeoSampleWS {
/** The serialVersionUID. */
private static final long serialVersionUID = 7220394261331723630L;
/**
* {@inheritDoc}
*/
@WebMethod
public DocumentDescriptor createDocument(@WebParam(name = "username")
String username, @WebParam(name = "password")
String password, @WebParam(name = "parentPath")
String parentPath, @WebParam(name = "name")
String name, @WebParam(name = "type")
String type) throws LoginException, ClientException {
...
}
...
}
WebService End Point and URL Mapping
Finally you have to define your WebService as an end point mapped to an URL of the Nuxeo server so it can be accessed in HTTP.
This is done by adding a contribution to the endpoint
extension point of the org.nuxeo.ecm.platform.ws.WSEndpointManager
component in your Nuxeo project.
Tomcat and JBoss 4.x distributions
<extension target="org.nuxeo.ecm.platform.ws.WSEndpointManager" point="endpoint">
<endpoint name="nuxeosample"
implementor="org.nuxeo.ecm.samples.ws.server.NuxeoSampleWSImpl"
address="/nuxeosample" />
</extension>
Once your plugin is deployed in the Nuxeo server, the Nuxeo sample WebService WSDL should be available at http://server:port/nuxeo/webservices/nuxeosample?wsdl.
Advanced Configuration Requiring a Configuration XML File
Since we moved to Apache CXF as a WebService provider, we encapsulate the service definition in our contribution model to prevent CXF Spring dependency. Sometimes, you'll need a specific configuration that will require a configuration XML file, as described in their documentation.
Here are the steps to do that in a Nuxeo way:
- Add the following dependencies
spring-core
,spring-beans
,spring-context
,spring-aop
,spring-expression
andspring-asm
. Ideally, using a Nuxeo Package. - Deploy your
cxf.xml
file innuxeo.war/WEB-INF/classes
, using thedeployment-fragment.xml
. And the file will be taken into account.
About Code Factorization
If you take a closer look at the NuxeoSampleWSImpl#createDocument
method, you can see that most of the code is not "business"-related and could be factorized.
Indeed, each time one makes a remote call to a Nuxeo WebService method, the following pattern must be applied:
- log in using the provided credentials,
- start a transaction,
- open a core session,
- do the job,
- manage transaction rollback if an exception occurs when doing the job,
- close the core session,
- commit or rollback the transaction,
- log out.
Which can be implemented as so:
// Login
LoginContext loginContext = Framework.login(username, password);
// Start transaction
TransactionHelper.startTransaction();
CoreSession session = null;
try {
// Open a core session
session = openSession();
// Do the job: create a doc with the given params
DocumentModel doc = session.createDocumentModel(parentPath, name,
type);
doc = session.createDocument(doc);
session.save();
return new DocumentDescriptor(doc);
} catch (ClientException ce) {
// Set transaction for rollback if an exception occurs
TransactionHelper.setTransactionRollbackOnly();
throw ce;
} finally {
// Close the core session
if (session != null) {
closeSession(session);
}
// Commit or rollback transaction
TransactionHelper.commitOrRollbackTransaction();
// Logout
loginContext.logout();
}
A way to solve this issue could be to define an abstract class holding this pattern in a generic method, which would call an abstract method responsible for the "business" part of the code, in the same way as for UnrestrictedSessionRunner
.
You can learn how to build a client-side SOAP based WebService in the Nuxeo Platform on the page Building a SOAP-Based WebService Client in Nuxeo.