First Look at JSR 371, MVC 1.0 Spesification and Ozark RI
The newly JSR 371 MVC 1.0 spesification is coming. This spesification will bring us an alternative way to apply MVC to Java EE ecosystem in action-based manner.
MVC 1.0 spesification grows up under Java EE 8 umbrella and it’s RI (Reference Implementation) is Ozark.
In this blog, i want to introduce to you current state of MVC 1.0 and Ozark, and want to give some examples.
There is a conversation about MVC 1.0 will be based on Servlet or Jax-RS ? In both way, there is advantages and disadvantages. If you follow up Servlet, you may develop and shape spec and ri apis in detail. If you follow up JaX-RS, you may use of its current features. For now, it seems Jax-RS way will win, but not certain.
If you already have practice with any action-based MVC frameworks (e.g. Spring MVC), you can easly adapt on MVC 1.0 and Ozark. I think following picture clears how MVC 1.0 and Ozark works.
- View
- View side can be any template kind. For example, JSP, Facelets, Freemarker, Handlerbars, Thymeleaf and so on. MVC 1.0 supports two view type: JSP and Facelts by default. But, you can use any View technology by developing your own ViewEngine.
- Models
- Models is an basically HashMap. You put your programming data to it. After that, ViewEngine will merge it with View.
public interface Models extends Map<String, Object>, Iterable<String> {
}
- ViewEngine
- Now, you have a View technology and Models data object. View document has some placeholder to demonstrate Model datas. For example;
<div>${person.name}</div>
<h:outputLabel value="#{person.name}"/>
<div>{{person.name}}</div>
<div th:text="${person.name}"></div>
Those are all basically does same thing. If you provide a Person
object has a name
property to those views, you can have a rendered view.
ViewEngine’s task is to merge Data and View. By default, Ozark contains two ViewEngine classes. JspViewEngine
and FaceletsViewEngine
. For example we can look at inside of ViewEngine and JspViewEngine.
public interface ViewEngine {
boolean supports(String view); (1)
void processView(ViewEngineContext context) throws ViewEngineException; (2)
}
1 | ViewEngine will support or not. |
2 | Render phase. |
@Priority(1000)
public class JspViewEngine implements ViewEngine {
private static final String VIEW_BASE = "/WEB-INF/"; (1)
@Inject
private ServletContext servletContext;
public boolean supports(String view) {
return view.endsWith("jsp") || view.endsWith("jspx"); (2)
}
public void processView(ViewEngineContext context) throws ViewEngineException {
Models models = context.getModels(); (3)
HttpServletRequest request = context.getRequest();
HttpServletResponse response = context.getResponse();
Iterator rd = models.iterator();
while(rd.hasNext()) {
String e = (String)rd.next();
request.setAttribute(e, models.get(e)); (4)
}
RequestDispatcher rd1 = this.servletContext.getRequestDispatcher("/WEB-INF/" + context.getView());
try {
rd1.forward(request, response); (5)
} catch (IOException | ServletException var7) {
throw new ViewEngineException(var7);
}
}
}
1 | JSP files location |
2 | This views supports files has jsp or jspx extension |
3 | Access Models object |
4 | Set each model values to current Request attribute list |
5 | Forward JSP view with their model attributes. |
Developing an ViewEngine
is easy as you see above.
- Controller
- We can then combine View, Models and ViewEngine parts with JaxRS Controller. There is a new
@javax.mvc.Controller
annotation that marks a JaxRS resource class/method to produce a view document.
@Path("/")
@Controller (1)
public class HomeController {
@Inject (2)
private Models models;
@GET
@Path("/home1")
public String home1() {
return "/index.jsp"; (3)
}
@GET
@Path("/home2")
@View("/index.xhtml") (4)
public void home2() {}
@GET
@Path("/home3")
public Viewable home3() {
return new Viewable("index.hbs"); (5)
}
@GET
@Path("/home4")
public Viewable home4() {
return new Viewable("index.html",HandlebarsViewEngine.class); (6)
}
@GET
@Path("/home5")
public Response home5() {
return Response
.ok()
.entity(new Viewable("index.thyme"))
.build(); (7)
}
}
1 | Marks this class as a MVC 1.0 Controller. All under methods will render a View document. |
2 | We can access a RequestScoped Models implementation via CDI (Context and Dependency Injection) |
3 | Renders index.jsp with current Models object. |
4 | Renders index.xhtml with current Models object. |
5 | Renders index.hbs with current Models object. |
6 | Renders index.html with current Models object and provided HandlebarsViewEngine class. |
7 | Renders index.thyme with current Models object. |
As you see above, there are many ways to declare which view document will be rendered. We did’t put any model value to View yet. Let’s do it.
Imagine we have a Person class like below.
public class Person {
private String name;
private String surname;
private Integer age;
// getters, setters and constructors.
}
We can visualize this kind of objects with the following Handlebars View.
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Person View</title>
</head>
<body>
<div>{{person.name}}</div>
<div>{{person.surname}}</div>
<div>{{person.age}}</div>
</body>
</html>
And we need to combine them;
@GET
@Path("/")
public class PersonController {
@Inject
private Models models;
@Path("/person")
@Controller
public String person() {
Person person =new Person(); (1)
person.setName("Hüseyin");
person.setSurname("Akdoğan");
person.setAge(35);
models.put("person",person); (2)
return "person.hbs"; (3)
}
}
1 | Create a new model object |
2 | Put it to global Models Map |
3 | Return view document name |
And then, we need write a ViewEngine implementation for Handlebars template system.
@Priority(Priorities.DEFAULT)
public class HandlebarsViewEngine implements ViewEngine {
@Inject
private ServletContext servletContext;
@Override
public boolean supports(String view) {
return view.endsWith(".hbs"); (1)
}
@Override
public void processView(ViewEngineContext context) throws ViewEngineException {
Models models = context.getModels();
String viewName = context.getView();
if (!viewName.startsWith("/"))
viewName = "/" + viewName;
try (PrintWriter writer = context.getResponse().getWriter();
InputStream rs = servletContext.getResourceAsStream(viewName);
InputStreamReader in = new InputStreamReader(rs, "UTF-8");
BufferedReader buf= new BufferedReader(in);) {
String viewContent = buf.lines().collect(Collectors.joining()); (2)
Handlebars handlebars = new Handlebars();
Template template = handlebars.compileInline(viewContent); (3)
template.apply(models, writer); (4)
} catch (IOException e) {
throw new ViewEngineException(e);
}
}
}
1 | Template name should end with .hbs extension |
2 | Read content of current view |
3 | Compile Handlebars template |
4 | Write rendered content to PrintWriter of HttpServletResponse |
Now, project structure of this project seems like below.
You can deploy this app to Glassfish v4.1 to test. After deployment follow the http://localhost:8080/mvc/app/person URL and see rendered Handlebars view.
You can download the Source Code here.
Hope to see you again.
1 Comment