Module definition
module.xml
<?xml version="1.0"?>
<module name="sample3" root-type="sample3" path="/sample3">
<nature>groovy</nature>
</module>
JAX-RS resources
Sample3.groovy
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import org.nuxeo.ecm.webengine.model.impl.*;
import org.nuxeo.ecm.webengine.model.*;
/**
* WebEngine Object Model.
*
* This sample is explaining the basics of Nuxeo WebEngine Object Model.
*
<p>
*
*
<h3>Resource Model</h3>
* Resources are objects used to serve the request. WebEngine Resources are always stateless (a new instance is created on each request).
* There are three type of resources defined by WebEngine:
*
<ul>
*
<li> Module Resource - this is the Web Module entry point as we've seen in sample3.
* This is the root resource. The other type of resources are JAX-RS sub-resources.
* A WebModule entry point is a special kind of WebObject having as type name the module name.
*
<li> Web Object - this represents an object that can be requested via HTTP methods.
* This resource is usually wrapping some internal object to expose it as a JAX-RS resource.
*
<li> Web Adapter - this is a special kind of resource that can be used to adapt Web Objects
* to application specific needs.
* These adapters are useful to add new functionalities on Web Objects without breaking application modularity
* or adding new methods on resources.
* This is helping in creating extensible applications, in keeping the code cleaner and in focusing better on the REST approach
* of the application.
* For example let say you defined a DocumentObject which will expose documents as JAX-RS resources.
* A JAX-RS resources will be able to respond to any HTTP method like GET, POST, PUT, DELETE.
* So let say we use:
*
<ul>
*
<li> <code>GET</code> to get a view on the DocumentObject
*
<li> <code>POST</code> to create a DocumentObject
*
<li> <code>PUT</code> to update a document object
*
<li> <code>DELETE</code> to delete a DocumentObject.
* </ul>
* But what if I want to put a lock on the document? Or to query the lock state? or to remove the lock?
* Or more, to create a document version? or to get a document version?
* A simple way is to add new methods on the DocumentObject resource that will handle requests top lock, unlock, version etc.
* Somethig like <code>@GET @Path("lock") getLock()</code> or <code>@POST @Path("lock") postLock()</code>.
* But this approach is not flexible because you cannot easily add new fonctionalities on existing resources in a dynamic way.
* And also, doing so, you will end up with a cluttered code, having many methods for each new aspect of the Web Object you need to handle.
* To solve this problem, WebEngine is defining Web Adapters, so that they can be used to add new fonctionality on existing objects.
* For example, to handle the lock actions on an Web Object we will define a new class LockAdapter which will implement
* the <code>GET</code>, <code>POST</code>, <code>DELETE</code> methods to manage the lock fonctionality on the target Web Object.
* Adapters are specified using an '@' prefix on the segment in an HTTP request path. This is needed by WebEngine to differentiate
* Web Objects from Web Adapters.
* Thus in our lock example to request the lock adapter on an object you will use a request path of like the following:
* <code>GET /my/document/@lock</code> or <code>POST /my/document/@lock</code> etc.
*
<p>
* When defining a Web Adapter you can specify on which type of Web Object it may be used. (this is done using annotations)
* </ul>
* All WebEngine resources have a type, a super type, an optional set of facets and an optional guard (these are declared using annotations)
* By using types and super types you can create hierarchies or resources, so that derived resource types will inherit attributes of the super types.
*
<p>
*
* There is a builtin adapter that is managing Web Objects views. The adapter name is <code>@views</code>.
* You will see in the view model an example on how to use it.
*
<p>
*
* Thus, request paths will be resolved to a resource chain usually of the form: WebModule -> WebObject -> ... -> WebObject [ -> WebAdapter ].
* <br>
* Each of these resource objects will be <i>served</i> using the <i>sub-resource</i> mechanism of JAX-RS until the last resource is reached.
* The last resource will usually return a view to be rendered or a redirection response.
* The request resource chain is exposed by the WebContext object, so that one can programatically retrieve any resource from the chain.
* In a given resource chain there will be always 2 special resources: a <b>root</b> and a <b>target</b> resource
* The root resource is exposed in templates as the <code>Root</code> object and the target one as the contextual object: <code>This</code>.
* <br>
* <b>Note</b> that the root resource is not necessarily the first one, and the target resource is not necessarily the last one!
* More, the root and the target resources are never WebAdapters. They can be only WebObjects or WebModule entry points
* (that are aspecial kind of WebObjects).
*
<p>
* The root resource is by default the module entry point (i.e. the first resource in the chain) but can be programatically set to point to any other
* WebObject from the chain.
*
<p>
* The target resource will be always the last WebObject resource from the chain.(so any trailing WebAdapters are excluded).
* This means in the chain: <code>/my/space/doc/@lock</code>, the root will be by default <code>my</code> which is the module entry point,
* and the target resource will be <code>doc</doc>. So it means that the <code>$This</code> object exposed to templates (and/or views) will
* never points to the adapter <code>@lock</code> - but to the last WebObject in the chain.
* So when an adapter view is rendered the <code>$This</code> variable will point to the adapted WebObject and not to the adapter itself.
* In that case you can retrieve the adapter using <code>${This.activeAdapter}</code>.
* This is an important aspect in order to correctly understand the behavior of the <code>$This</code> object exposed in templates.
*
<p>
*
<p>
*
<h3>View Model</h3>
* The view model is an extension of the template model we discussed in the previous sample.
* The difference between views and templates is that views are always attached to an Web Object. Also, the view file resolution is
* a bit different from template files. Templates are all living in <code>skin</skin> directory. Views may live in two places:
*
<ul>
*
<li> in the skin/views/${type-name} folders where type-name is the resource type name the view is applying on.
* This location will be consulted first when a view file is resolved, so it can be used by derived modules to replace views on already defined objects.
*
<li> in the same folder (e.g. java package) as the resource class.
* This location is useful to defining views inside JARs along with resource classes.
* </ul>
* Another specific property of views is that they are inherited from resource super types.
* For example if you have a resource of type <code>Base</code> and a resource of type <code>Derived</code> then all views
* defined on type <code>Base</code> apply on type <code>Dervied</code> too.
* You may override these views by redefining them on type <code>Derived</code>
* <br>
* Another difference between templates and views is that views may vary depending on the response media-type.
* A view is identified by an ID. The view file name is computed as follow:
*
<pre>
* view_id + [-media_type_id] + ".ftl"
* </pre>
* The <code>media_type_id</code> is optional and will be empty for media-types not explicitely bound to an ID in modules.xml configuration file.
* For example, to dynamically change the view file corresponding to a view
* having the ID <code>index</code> when the response media-type is <code>application/atom+xml</code>
* you can define a mapping of this media type to the media_type_id <code>atom</code> and then you can use the file name
* <code>index-atom.ftl</code> to specify a specific index view when <code>atom</code> output is required.
*
* @author <a href="mailto:[email protected]">Bogdan Stefanescu</a>
*/
@WebObject(type="sample3")
@Produces(["text/html"])
public class Sample3 extends ModuleRoot {
/**
* Get the index view. The view file name is computed as follows: index[-media_type_id].ftl
* First the skin/views/sample4 is searched for that file then the current directory.
* (The type of a module is the same as its name)
*/
@GET
public Object doGet() {
return getView("index");
}
/**
* Get the WebObject (i.e. a JAX-RS sub-resource) bound to "users".
* Look into "users" directory for the UserManager WebObject. The location of WebObjects is not explictely specified by the programmer.
* The module directory will be automatically scanned for WebObject and WebAdapters.
*/
@Path("users")
public Object getUserManager() {
// create a new instance of an WebObject which type is "UserManager" and push this object on the request chain
return newObject("UserManager");
}
}
users/UserManager.groovy
package users;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import org.nuxeo.ecm.webengine.model.impl.*;
import org.nuxeo.ecm.webengine.model.*;
/**
* UserManager object.
* You can see the @WebObject annotation that is defining a WebObject of type "UserManager"
* @author <a href="mailto:[email protected]">Bogdan Stefanescu</a>
*/
@WebObject(type="UserManager")
@Produces(["text/html", "*/*"])
public class UserManager extends DefaultObject {
/**
* Get the index view. The view file name is computed as follows: index[-media_type_id].ftl
* First the skin/views/UserManager is searched for that file then the current directory.
* (The type of a module is the same as its name)
*/
@GET
public Object doGet() {
return getView("index");
}
/**
* A hack to accept users as user?name=xxx query parameters
*/
@GET
@Path("user")
public Object getUserByQueryString(@QueryParam("name") String name) {
if (name == null) {
return doGet();
} else {
return redirect(getPath()+"/user/"+name);
}
}
/**
* Get the user JAX-RS resource given the user name
*/
@Path("user/{name}")
public Object getUser(@PathParam("name") String name) {
// create a new instance of a WebObject which type is "User" and push this object on the request chain
// the User object is intialized with the String "Username: name"
return newObject("User", "Username: "+name);
}
}
users/User.groovy
package users;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import org.nuxeo.ecm.webengine.model.impl.*;
import org.nuxeo.ecm.webengine.model.*;
/**
* User object.
* You can see the @WebObject annotation that is defining a WebObject of type "User"
* @author <a href="mailto:[email protected]">Bogdan Stefanescu</a>
*/
@WebObject(type="User")
@Produces(["text/html", "*/*"])
public class User extends DefaultObject {
String displayName;
/**
* Initialize the object.
* args values are the one passed to the method newObject(String type, Object ... args)
*/
protected void initialize(Object... args) {
displayName = args[0];
}
/**
* Getter the variable displayName. Would be accessible from views with ${This.displayName}
*/
public String getDisplayName() {
return displayName;
}
/**
* Get the index view of the User object.
* The view file is located in <code>skin/views/User</code> so that it can be easily extended
* by a derived module. See extensibility sample.
*/
@GET
public Object doGet() {
return getView("index");
}
/**
* This method is not implemented but demonstrates how DELETE requests can be used
*/
@DELETE
public Object doRemove(@PathParam("name") String name) {
//TODO ... remove user here ...
// redirect to the UserManager (the previous object in the request chain)
return redirect(getPrevious().getPath());
}
/**
* This method is not implemented but demonstrates how PUT requests can be used
*/
@PUT
public Object doPut(@PathParam("name") String name) {
//TODO ... update user here ...
// redirect to myself
return redirect(getPath());
}
}
users/UserBuddies.groovy
package users;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import org.nuxeo.ecm.webengine.model.impl.*;
import org.nuxeo.ecm.webengine.model.*;
/**
* UserBuddies object.
* You can see the @WebAdapter annotation that is defining a WebAdapter of type "UserBuddies" that applies to any User WebObject.
* The name used to access this adapter is the adapter name prefixed with a '@' character: <code>@buddies</code>
*
* @author <a href="mailto:[email protected]">Bogdan Stefanescu</a>
*/
@WebAdapter(name="buddies", type="UserBuddies", targetType="User")
@Produces(["text/html", "*/*"])
public class UserBuddies extends DefaultAdapter {
/**
* Get the index view. The view file name is computed as follows: index[-media_type_id].ftl
* First the skin/views/UserBuddies is searched for that file then the current directory.
* (The type of a module is the same as its name)
*/
@GET
public Object doGet() {
return getView("index");
}
}
Object views
skin/views/sample3/index.ftl
<html>
<head>
<title>Sample3</title>
</head>
<body>
<h3>Sample3 Index View.</h3>
<p><a href="${This.path}/users">User Management</a></p>
</body>
</html>
skin/views/UserManager/index.ftl
<html>
<head>
<title>Sample3</title>
</head>
<body>
<h3>UserManager Index</h3>
<form method="GET" action="${This.path}/user" onSubmit="">
Enter a fictive User name: <input type="text" name="name" value=""/>
</form>
</body>
</html>
skin/views/User/index.ftl
<html>
<head>
<title>Sample3</title>
</head>
<body>
<h3>${This.displayName}</h3>
View my <a href="${This.path}/@buddies">buddies</a>
</body>
</html>
skin/views/UserBuddies/index.ftl
<html>
<head>
<title>Sample3 - Adapter example</title>
</head>
<body>
<#-- Look here how $This is used to access current user and not Buddies adapter -->
<h3>Buddies for user ${This.name}!</h3>
<#-- Look here how to access the adapter instance: ${This.activeAdapter} -->
This is an adapter named ${This.activeAdapter.name}
<ul>
Buddies:
<li><a href="${This.previous.path}/user/Tom">Tom</li>
<li><a href="${This.previous.path}/user/Jerry">Jerry</li>
</ul>
</body>
</html>