Nuxeo WebEngine is a web framework for JAX-RS applications based on a Nuxeo document repository. It provides a template mechanism to facilitate the dynamic generation of web views for JAX-RS resources.
Templating is based on the FreeMarker template engine.
Besides templating support, WebEngine provides an easy way to integrate native JAX-RS application on top of the Nuxeo platform - it provides all the glue and logic to deploy JAX-RS application on a Nuxeo server.
We will describe here the steps required to register a JAX-RS application, and how to use WebEngine templating to provide Web views for your resources.
This tutorial assumes you are already familiarized with JAX-RS. If not, please read first a JAX-RS tutorial or the specifications. For beginners, you can find a JAX-RS tutorial here.
This page presents the WebEngine concepts. For more details about using these concepts, see the WebEngine Tutorials. You can download the tutorials sources from .
Quick Checklist
These are the key points to keep in mind to have a running simple WebEngine application. They will be detailed further in this page.
- make your class extend
ModuleRoot
, - annotate your class with
@WebObject
,
Declaring a JAX-RS Application in Nuxeo
To deploy your JAX-RS application in Nuxeo you should create a JAX-RS application class (see specifications) and declare it inside the MANIFEST.MF
file of your Nuxeo bundle.
To define a JAX-RS application, you must write a Java class that extends the javax.ws.rs.core.Application
abstract class.
Then, you need to declare your application in your bundle MANIFEST.MF
file as following:
Nuxeo-WebModule: org.MyApplicationClass
where org.MyApplicationClass
is the full name of your JAX-RS application class.
Now you simply put your JAR in Nuxeo bundles directory (e.g. $NUXEO_HOME/nxserver/bundle
) and your Web Application will be deployed under the URL: http://NUXEO_SERVER/nuxeo
.
Example
Let's define a JAX-RS application as follows:
public class MyWebApp extends Application {
@Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> result = new HashSet<Class<?>>();
result.add(MyWebAppRoot.class);
return result;
}
}
where the MyWebAppRoot
class is the entry point of the application and is implemented as follows:
@Path("mysite")
public class MyWebAppRoot {
@GET
public Object doGet() {
return "Hello World!";
}
}
Lets say the full name of MyWebApp
is org.nuxeo.example.MyWebApp
. Now you should tell to Nuxeo WebEngine that you have a JAX-RS application in your bundle. To do this, add a line to your MANIFEST.MF
file as follows:
Manifest-Version: 1.0
...
Nuxeo-WebModule: org.nuxeo.example.MyWebApp
...
Build your application JAR and put it into your Nuxeo bundles directory. After starting the server you will have a new web page available at http://NUXEO_SERVER/nuxeo/site/mysite
.
Automatic Discovery of JAX-RS Resources at Runtime
If you don't want to explicitly declare your resources in a JAX-RS application object you can use a special application that will discover resources at runtime when it will be registered by the JAX-RS container.
For this you should use the org.nuxeo.ecm.webengine.jaxrs.scan.DynamicApplicationFactory
application in your manifest like this:
Nuxeo-WebModule: org.nuxeo.ecm.webengine.jaxrs.scan.DynamicApplicationFactory
When your JAX-RS module will be registered the bundle declaring the module will be scanned and any class annotated with @Provider
or @Path
will be added to the module.
If you want to avoid scanning the entire bundle you can use the attribute package
to perform scanning only inside the given package (and on its sub-packages). Example:
Nuxeo-WebModule: org.nuxeo.ecm.webengine.jaxrs.scan.DynamicApplicationFactory;package=org/my/root/package
The JAX-RS container used by Nuxeo is Jersey.
Declaring a WebEngine Application in Nuxeo
To declare a WebEngine Application you should create a new JAX-RS Application as in the previous section - but using the org.nuxeo.ecm.webengine.app.WebEngineModule
base class and declare any Web Object Types used inside your application (or use runtime type discovery).
You will learn more about Web Object Types in the following sections.
The simplest way to declare a WebEngine module is to add a line like the following one in your manifest:
Nuxeo-WebModule: org.nuxeo.ecm.webengine.app.WebEngineModule;name=myWebApp;extends=base;package=org/mywebapp
the name attribute is mandatory, extends and package are optional and are explained above. When declaring in that way a WebEngine module all the Web engine types and JAX-RS resources will be discovered at runtime - at each startup.
A WebEngine Application is a regular JAX-RS application plus an object model to help creating Nuxeo web front ends using FreeMarker as the templating system.
If you'd like your WEB module to be deployed in a distinct JAX-RS application than the default one (handling all WebEngine modules), you need to declare a host:
Nuxeo-WebModule: org.nuxeo.ecm.webengine.app.WebEngineModule;host=MyHost
and bind the servlet to this host in the deployment-fragment.xml file:
<extension target="web#SERVLET">
<servlet>
<servlet-name>My Application Servlet</servlet-name>
<servlet-class>
org.nuxeo.ecm.webengine.app.jersey.WebEngineServlet
</servlet-class>
<init-param>
<param-name>application.name</param-name>
<param-value>MyHost</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>My Application Servlet</servlet-name>
<url-pattern>/site/myapp/*</url-pattern>
</servlet-mapping>
</extension>
This will isolate your top level resources from the ones declared by other modules (like root resources, message body writer and readers, exception mapper, etc.). Example: this will make it possible, for instance, to use a distinct exception mapper in your application.
Example
To define a WebEngine Application you should override the org.nuxeo.ecm.webengine.app.WebEngineModule
class and declare any web types you are providing:
public class AdminApp extends WebEngineModule {
@Override
public Class<?>[] getWebTypes() {
return new Class<?>[] { Main.class, User.class, Group.class,
UserService.class, EngineService.class, Shell.class };
}
}
If you want automatic discovery of resources you can just use the WebEngineModule in your MANIFEST.MF - without extending it with your own application class.
Of course as for JAX-RS applications you should specify a Manifest header to declare your application like:
Nuxeo-WebModule: org.nuxeo.ecm.webengine.admin.AdminApp;name=admin;extends=base
You can see there are some additional attributes in the manifest header: 'name' for the module name and 'extends' if you want to extend another module. The 'name' attribute is mandatory. You can also optionally use the 'headless=true' attribute to avoid displaying your module in the module list on the root index.
If you want to customize how your module is listed in that module index you can define 'shortcuts' in the module.xml file. Like this:
<?xml version="1.0"?>
<module>
<shortcuts>
<shortcut href="/admin">
<title>Administration</title>
</shortcut>
<shortcut href="/shell">
<title>Shell</title>
</shortcut>
</shortcuts>
</module>
Note that the module.xml file is optional. You can use it if you want to make some customization like adding shortcuts or defining links.
To define a WebEngine Application root resource you should override the
org.nuxeo.ecm.webengine.model.impl.ModuleRoot
class:
@WebObject(type = "Admin", administrator = Access.GRANT)
@Produces("text/html;charset=UTF-8")
@Path("/admin")
public class Main extends ModuleRoot {
public Main(@Context UriInfo info, @Context HttpHeaders headers) {
super(info, headers, "Admin");
}
...
}
What Is WebEngine Good For?
We've seen that using WebEngine you can deploy your JAX-RS applications without many trouble. You don't need to care about servlet declaration etc. You simply need to declare your JAX-RS application class in the MANIFEST file. The JAX-RS servlet provided by WebEngine will be used to invoke your application when its URL is hit.
So for now, we've seen how to create a JAX-RS application and deploy it into a Nuxeo server. You can stop here if you just want to use JAX-RS and don't care about WebEngine templating and Web Views for your resources.
JAX-RS is a very good solution to build REST applications and even Web Sites. The problem is that JAX-RS focus more on REST applications and doesn't define a flexible way to build modular Web Sites on top of the JAX-RS resources.
This is where WebEngine is helping by providing Web Views for your JAX-RS resources.
I will first explain how you can do templating (using FreeMarker) for a regular JAX-RS resource. Then I will enter deeper into the WebEngine templating model.
JAX-RS Resource Templating
To create a FreeMarker template for your JAX-RS resource you need to put the template file (a FreeMarker template like index.ftl
) in your bundle so the template could be located using the Java class loader at runtime.
Example
Put a file with the following content in the src/main/resources/org/nuxeo/example/index.ftl
:
Hello ${Context.principal.name}!
And then modify the MyWebAppRoot
class as following:
public class MyWebAppRoot {
@GET
public Object doGet() {
return new TemplateView(this, "index.ftl");
}
}
If your are logged as Guest and you go to http://NUXEO_SERVER/nuxeo/site/mysite
you will see a message like: Hello Guest!
In WebEngine if you doesn't sign in as a real user you will be automatically considered a Guest user.
WebEngine Template Variables
Here is the list of all variables available in a template file:
Context
- the context object; seeorg.nuxeo.ecm.webengine.model.WebContext
for the provided API.This
- the target JAX-RS resource. (the object that returned the template)Root
- the first JAX-RS resource in the call chain. (the first JAX-RS resources that delegate the call to the leaf resource). This variable is not available for pure JAX-RS resources. You should use WebEngine objects to have it defined.Session
- the Nuxeo repository session. The session is always non null when the JAX-RS application is installed into a Nuxeo server.Document
- this object is equivalent to This when the current JAX-RS resource is wrapping a Nuxeo Document. Seeorg.nuxeo.ecm.platform.rendering.fm.adapters.DocumentTemplate
for the provided API. This variable is not set when using pure JAX-RS resources. You should use WebEngine objects to have it defined.Adapter
- the current WebEngine adapter - only set when using WebEngine objects and the current JAX-RS resource is an adapter.Module
- deprecated - this is the module instance (the root object) and is provided only for compatibility with previous WebEngine implementations.Engine
- this is the singleton WebEngine service; see theorg.nuxeo.ecm.webengine.WebEngine
interface for the provided API.basePath
- thecontextPath+"/"+servletPath
(seejavax.servlet
specifications)contextPath
- deprecated - special variable that identify the context path set using the runtime variableorg.nuxeo.ecm.contextPath
. Tis is useful for proxy redirections. See WebEngine Resources section for how to locate resources.skinPath
- deprecated - represent the path to the WebEngine module resources. Should no more be used since it is not safe when rewriting requests using a proxy HTTP server. See WebEngine Resources section for how to locate resources.
You notice that when using pure JAX-RS objects you only have the following built-in variables defined in the template context: This
, Context
, Engine
, basePath
, contextPath
.
Custom Template Variables
You can add your custom variables to the template context as follows:
public class MyWebAppRoot {
@GET
public Object doGet() {
return new TemplateView(this, "index.ftl").arg("country", "France").arg("city", "Paris");
}
}
You can now write a template file named index.ftl
that uses these variables to render the response:
Hello ${Context.principal.name}! Your country is ${country}, and your city is ${city}.
WebEngine Modules
The module declaration documented here is deprecated for Java based modules. See Declaring a WebEngine Application in Nuxeo for the new declaration. The module.xml
file is now optional - and should be used to declare module shortcuts or to describe links. Groovy based modules still use the old declaration method.
The problem with the templating described above is that template files are inside the application JAR and are located using the class loader. This make difficult to create web sites that are composed from tens of template files and images. A more flexible approach will be to put web files into directories on the file system. This way, the lookup is faster and you can easily modify web files without restarting the server when in development mode.
This is one of the reason the WebEngine module concept was introduced.
A module is a bundle (i.e. JAR file) that contains JAX-RS resources and web resources (such as images, HTML files or templates). The module is usually defining a JAX-RS application but it can also contribute resources to other applications. So a module is defined by:
- a module name - a unique key used to identify the module in the module registry.
- a module path - the path of the root resource in a module.
- a module entry point - a JAX-RS resource class that will be served when the module path matches a client request. The module entry point is used to directly send responses or to dispatch the request to other JAX-RS resources.
To define a module you need to create a module.xml
file and put it in the root of your JAR. Here is the minimal content of a module.xml
file:
<module name="Admin" root-type="Admin" path="/admin" />
This module file is declaring a module named Admin
with path /admin
. The module path is relative to the WebEngine servlet so the full URL of this module will be http://NUXEO_SERVER/nuxeo/site/admin
.
You notice there is a third required attribute root-type
. This attribute is used to locate the module entry point.
How the entry point is located will be discussed in the next section.
WebEngine Objects
A WebEngine module is made from web resources and web objects. Resources are usually HTML, JavaScript, CSS, images or template files and are used to create web views for the JAX-RS objects provided by the module.
To be able to bind views to your JAX-RS resources you must declare them as WebEngine objects. This is done by using the annotation: @WebObject
and extending the org.nuxeo.ecm.webengine.model.impl.DefaultObject
class. Example:
@WebObject(type = "User")
@Produces("text/html;charset=UTF-8")
public class User extends DefaultObject {
@GET
public Object doGet() {
return getView("index").arg("user", principal);
}
...
}
In the previous example we defined a WebObject of type User
. You notice the object is a JAX-RS resource and extends the DefaultObject
base class. The @WebObject
annotation is used to declare the JAX-RS resource as a WebObject.
There is a special WebObject - the entry point of a module. To define a module entry point you need to create a WebObject that extends the org.nuxeo.ecm.webengine.model.impl.ModuleRoot.ModuleRoot
class. Example:
@WebObject(type = "Admin", administrator=Access.GRANT)
@Produces("text/html;charset=UTF-8")
public class Main extends ModuleRoot {
@Path("users")
public Object getUserManagement() {
return newObject("UserManager");
}
@Path("engine")
public Object getEngine() {
return newObject("Engine");
}
...
}
As we've seen above when a module is loaded the entry point class is located using the root-type
attribute in module.xml. This attribute is pointing to the WebObject having the same type as the root_type
value. So in our case the root-type="Admin"
attribute is telling to WebEngine to use the the class Main
annotated with @WebObject(type = "Admin")
as the entry point JAX-RS resource.
In the example above we can see that WebObjects methods annotated with @GET
, @POST
etc. are used to return the response to the client. The right method is selected depending on the HTTP method that were used to make the request. @GET
methods are used to serve GET requests, @POST
methods are used to serve POST requests, etc. So the method:
@GET
public Object doGet() {
return getView("index").arg("user", principal);
}
will return a view (i.e. template) named "index" for the current object. The returned view will be processed and serialized as a HTML document and sent to the client.
We will see in next section how view templates are located on the file system.
The method:
@Path("users")
public Object getUserManagement() {
return newObject("UserManager");
}
delegates the request to the WebObject having the type UserManager
. This Web Object is a JAX-RS resource annotated with @WebObject(type="UserManager")
.
WebEngine Adapters
A WebAdapter is a special kind of Web Object that can be used to extend other Web Objects with new functionalities. To extend existing objects using adapters you don’t need to modify the extended object. This type of resource make the life easier when you need to add more API on an existing object but cannot modify it because for example it may be a third party web object or the new API is too specialized to be put directly on the object. In this cases you can create web adapters that adapts the target object to a new API.
To declare an adapter use the @WebAdapter
annotation and extend the DefaultAdapter
class:
@WebAdapter(name = "audits", type = "AuditService", targetType = "Document")
public class AuditService extends DefaultAdapter {
...
}
An adapter has a name
that will be used to select the adapter depending on the request path, and as any Web Object a type. An adapter also has a targetType
that represent the type name of the object to adapt.
See more on using adapters in Adapter Tutorial.
@Path and HTTP Method Annotations
Lets discuss now how JAX-RS annotations are used to match requests.
If a method is annotated using one of the HTTP method annotations (i.e. @GET
, @POST
, @PUT
, @DELETE
, etc.) then it will be invoked when the current object path matches the actual path in the user request. These methods must return the object that will be used as the response to be sent to the client. Regular Java objects as String, Integer etc. are automatically serialized by the JAX-RS engine and sent to the client. If you return other type of objects you must provide a writer that will handle the object serialization. See more about this in JAX-RS specifications.
Methods that are annotated with both @Path
and one of the HTTP method annotations are used in the same manner as the ones without a @Path
annotation. The @Path
annotation can be added if you want to match a sub-path of the current object path. @Path
annotations may contain regular expression patterns that should be enclosed in brackets {}.
For example, let's say the following object matches the /nuxeo/site/users
path:
@WebObject(type = "Users")
@Produces("text/html;charset=UTF-8")
public class Users extends DefaultObject {
@GET
public Object doGet() {
return getView("index");
}
@Path("{name}")
@GET
public Object getUser(@PathParam("name") String username) {
return getView("user").arg("user", username);
}
...
}
The doGet
method will be invoked when a request is exactly matching the /nuxeo/site/users
path and the HTTP method is GET.
The getUser
method will be invoked when a request is matching the path /nuxeo/site/users/{name
} where {name
} matches any path segment. So all requests on paths like /nuxeo/site/users/foo
, /nuxeo/site/users/bar
will match the getUser
method. Because the path contains a pattern variable, you can use the @PathParam
annotation to inject the actual value of that variable into the method argument.
You can also use a @Path
annotation to redirect calls to another JAX-RS resource. If you want this then you must not use use any HTTP method annotations in conjunction with @Path
- otherwise the method will be treated as a terminal method that is returning the response object to be sent to the client.
Example:
@WebObject(type = "Users")
@Produces("text/html;charset=UTF-8")
public class Users extends DefaultObject {
@Path("{name}")
public Object getUser(@PathParam("name") String username) {
return new User(username);
}
...
}
You can see in the example above the if the request matches a path like /nuxeo/site/users/{name
} then the Users
resource will dispatch the call to another JAX-RS resource (i.e. User
object) that will be used to handle the response to the user (or to dispatch further the handling to other JAX-RS resources).
Dispatching Requests to WebObject Sub-Resources
To dispatch the call to another WebObject instance you must use the newObject(String type)
method to instantiate the WebObject by specifying its type as the argument. Example:
@Path("users")
public Object getUserManagement() {
return newObject("UserManager");
}
This method will dispatch the requests to the UserManager
WebObject. The WebObject class is identified using the type value: "UserManager".
Module Deployment
At server startup JARs containing module.xml
files will be unzipped under install_dir/web/root.war/modules/module_dir
directory. The module_dir
name is the bundle symbolic ID of the unzipped JAR. This way you can find easily which bundle deployed which module.
The install_dir
is the installation directory for a standalone installation or the jboss/server/default/data/NXRuntime
for a JBoss installation.
To deploy a module as a directory and not as an OSGi bundle you can simply copy the module directory into install_dir/web/root.war/deploy
. If deploy directory doesn’t exists you can create it.
Note that when you deploy the module as an OSGi bundle the JAR will be unzipped only at first startup. If you update the JAR (the last modified time changes) then the JAR will be unzipped again and will override any existing files.
Module Structure
A web module can be deployed as a JAR file or as a directory. When deployed as a JAR file it will be unzipped in a directory at first startup.
The structure of the root of a deployed web module should follows this schema:
/module.xml
/i18n
/skin
/skin/resources
/skin/views
/META-INF
Every module must have a module.xml
descriptor in its root. This file is used by WebEngine to detect which bundles should be deployed as web modules.
The
/i18n
directory contains message bundle property files.The
/skin
directory should contain the templates used to generate web pages and all the client resources (e.g. images, style sheets, client side scripts). The content of this directory is inherited from the super module if your module extend another module. This means if a resource is not found in your module skin directory the super module will be asked for that resource and so on until the resource is found or no more super modules exists.The
/skin/resources
directories contains all client resources. Here you should put any image, style sheet or script you want to use on the client. The content of this directory is directly visible in your web server under the path: {base_path}/module_name/skin
(see Static Resources).The
/skin/views
directory should be used to store object views. An object view is usually a FreeMarker template file that will be rendered in the request context and served when necessarily by the web object.The
/META-INF
} directory is usually storing theMANIFEST.MF
and other configuration or generated files that are internally used by the server.
Be aware that the root directory of a module is added to the WebEngine classpath.
For that reason module classes or Groovy scripts must be put in well named packages and the package root must reside directly under the the module root. Also, avoid to put classes in script directory.
Look into an existing WebEngine module like admin, base or wiki for examples on how to classes are structured.
Web Object Views
We saw in the examples above that WebObjects can return views as a response to the client. Views are in fact template files bound to the object. To return a view from a WebObject you should do something like:
@GET
public Object doGet() {
return getView("my_view");
}
where my_view
is the view name. To bind a view to an object, you should put the view file in a folder named as the object type in the /skin/views
directory. The view file name should have the same name as the view + the .ftl
extension.
Example: Suppose we have an web object of type MyObject
. To define a view named myview
for this object, you should pit the view template file into /skin/views/MyObject/myview.ftl
. Doing this you can now use send the view to the client using a method like:
@WebObject(type = "MyObject")
@Produces("text/html;charset=UTF-8")
public class MyObject extends DefaultObject {
@GET
public Object doGet() {
return getView("myview");
}
}
If a view file is not found inside the module directory then all super types are asked for that view. If the view is not found then the super modules (if any) are asked for that view.
Extending Web Objects
You can extend an existing Web Object with your own object by defining the superType
attribute in the @WebObject
annotation. Example:
@WebObject(type = "JSONDocument", superType = "Document")
When extending an object you inherit all object views.
Extending Modules
When defining a new module you can extend existing modules by using the extends
attribute in your module.xml file:
<module name="Admin" root-type="Admin" path="/admin" extends="base" />
The extends
attribute is pointing to another module name that will become the base of the new module.
All WebObjects from a base module are visible in the derived modules (you can instantiate them using newObject("object_type") method)
.
Also, static resources and templates that are not found in a module will be searched into the base module if any until the resource is found or all module hierarchy is consumed.
You can use this feature to create a base module that is providing a common look and feel for your applications and then extending it in modules by overriding resources (or web page areas - see Template Model) that you need to change for your application.
Template Model
WebEngine defines also a template model that is used to build responses. The template engine is based on FreeMarker, plus some custom extensions like template blocks. Using blocks you can build your web site in a modular fashion. Blocks are dynamic template parts that can be extended or replaced using derived blocks. Using blocks, you can write a base template that may define the site layout (using blocks containing empty or generic content) and then write final skins for your layout by extending the base template and redefining blocks you are interested in.
Templates are stored as files in the module bundle under the skin directory. Templates are resolved in the context of the current module. This way, if a module is extending another module, a template will be first looked up into the derived module, then in its super modules until a template it’s found or no more parent modules exists.
There is a special type of templates that we call views. The difference between views and regular templates is that views are always attached to an Web Object Resource. This means, views are inherited from super types. Because of this the view file resolution is a bit different from templates.
Views are first searched in the current module, by iterating over all resource super types. If not found then the super module is searched (if any) and so on until a view file is found or no more parent modules exists.
Static Resources
Template files are usually referencing external resources like static CSS, JavaScript or image files.
To refer to this type of resources you must always use relative paths to the module root (the entry point object). By doing this you avoid problems generated by URL rewrite when putting your server behind a proxy.
Let's suppose we have a module entry point as follows:
@WebObject(type = "Admin", administrator=Access.GRANT)
@Produces("text/html;charset=UTF-8")
public class Main extends ModuleRoot {
public Object doGet() {
return getView("index");
}
...
}
Suppose the object is bound to the nuxeo/site/admin
path, and the index.ftl
view is referencing an image located in the module directory in skin/resources/images/myimage.jpg
. Then the image should be referenced using the following path:
<img src="skin/images/myimage.jpg" />
The path is relative to the current object so the image absolute path is /nuxeo/site/admin/skin/images/myimage.jpg
.
So all static resources in a Web module are accessible under the /module_path/skin/
path. You can get the module path from any view by using the variable ${Root.path
} or ${basePath}/module_name
.
Headless Modules
By default WebEngine modules are listed in the WebEngine home page (i.e. /nuxeo/site
). If you don't want to include your module in that list you can use the headless
attribute in your module.xml
file:
<module name="my_module" root-type="TheRoot" path="/my_module" extends="base" headless="true" />