CXF RequestDispatcherProvider: a taste of MVC specification today
MVC specification will bring a JAX-RS based MVC server framework with JavaEE 8 but if you already use CXF you can do more or less the same based on RequestDispatcherProvider class.
It is a @Provider (MessageBodyWriter actually) you can use to route a JAX-RS endpoint to a view. The nice thing is you have some actual flexibility:
- configure the associated view (it supports multiple views and if the condition is not enough you can use multiple instance of RequestDispatcherProvider)
- configure the associated name of the model (the name in the template of the returned data)
- base the view on an enum
- ...
Let's see a simple example and how you can use it just after having read this post!
The JAX-RS endpoint
First we need a JAX-RS endpoint with a model, let's take this trivial one:
@Path("hello")
@ApplicationScoped
public class HelloController {
@GET
@Produces(MediaType.TEXT_HTML)
public World hi(@QueryParam("name") @DefaultValue("world") final String name) {
return new World(name);
}
public static class World {
private String name; // getter
}
}
Really a simple endpoint creating a "name holder" (World model) which can vary depending the query parameter name.
The view
Then we want to show some nice HTML with our model so we'll use a JSP (most servers will support it but you can use really any web framework you want until you can access the request attributes):
<html>
<head>
<title>Demo</title>
</head>
<body>
<p>name: ${world.name}</p>
</body>
</html>
Nothing crazy there, just a plain HTML page accessing the request attribute ${world.name} which is supposed to be based on our World model.
The Controller-View glue: RequestDispatcherProvider
Now we need to ensure our controller (JAX-RS endpoint) and our view can be linked together. This is the role of the provider CXF gives you for free.
To configure and activate it, we'll put it in singletons of the JAX-RS application we'll use:
@Dependent
@ApplicationPath("rs")
public class HelloApplication extends Application {
private final Set<Class<?>> classes = singleton(HelloController.class);
private final Set<Object> singletons = singleton(newDispatcher());
private RequestDispatcherProvider newDispatcher() {
final RequestDispatcherProvider dispatcherProvider = new RequestDispatcherProvider();
dispatcherProvider.setClassResources(new HashMap<String, String>() {{ // allow to handle view per type
put(HelloController.World.class.getName(), "/hello.jsp");
}});
dispatcherProvider.setBeanNames(new HashMap<String, String>() {{
put(HelloController.World.class.getName(), "world");
}});
dispatcherProvider.setLogRedirects(Boolean.FALSE.toString());
return dispatcherProvider;
}
@Override
public Set<Class<?>> getClasses() {
return classes;
}
@Override
public Set<Object> getSingletons() {
return singletons;
}
}
The configuration can be extracted (opeenjb-jar.xml for TomEE typically) or done this way but the important parts to keep are:
- log redirects: this option allows to log each time you use that provider, personally I prefer to not be polluted by logs for normal cases so i switch it off
- bean names: this is how CXF will set the request attribute holding the value returned by your endpoint, there are multiple strategy but default one is to use the class simple name. Setting a map fully qualified class name -> attribute name allows to have nicer names in the view.
- class resources: this is the mapping between classes and views. Concretely you associated to a class a view.
There are really a lot of configuration you can desire like:
- include resource: do you use an include or forward to build the response (= how the view is handled)
- scope: where the attribute is set, ie the request or session (default to request)
- and a lot more parameters to lookup differently the resources
The previous snippet is more than enough for our us, we can even make it simpler since we have a single model and view and CXF has shortcuts for that case.
Run it
I bypassed some part of the packaging in previous parts but depending which server you use it can be different but overall points are:
- the views will be in webapp/ (as a web resource or in WEB-INF) if you use a .war or in META-INF/resources if you use a fatjar or servlet 3.0 features
- if you run a very lightweight container like Apache Meecrowave you will not get jsp by default and need to add tomcat-jasper dependency and for version 0.2.0 (not next ones) this ServletContainerInitializer (but then you run the main org.apache.meecrowave.runner.Cli directly without any other packaging):
// don't forget the META-INF/services/javax.servlet.ServletContainerInitializer file
public class Customizer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) {
servletContext.addServlet("jsp", JspServlet.class).addMapping("*.jsp");
}
}
Conclusion
Today's world is really about single page applications but for rather backend applications with small frontend needs it is still overkill and requires some knowledge you don't want to invest any time in.
When these applications are JAX-RS based, it is very very handy to use this kind of solution which allows to get a ui based on its standard architecture in less than 5mn!
From the same author:
In the same category: