Introduction to Maverick
This article will cover the code and experience in building an application using the Maverick framework.
Introduction
Other Articles in the Framework Series |
Building with WebWork2 Introducing the Spring Framework Keel: The Next Generation Meta-Framework |
If you cut the fat on all the web presentation frameworks out there, what do you have left over? Maverick. I originally got into Maverick as I was researching other web frameworks for my user group, www.frameworks-boulder.org. What caught my attention was how Maverick claims that it is "Lighter than Struts". Having a strong Expresso background, I had always considered Struts to be the Duplo blocks of frameworks,that is, until I started building the Wafer example with Maverick.
Maverick is a lightweight, simple to use web presentation framework based on the MVC design principle but offers a few variations of that design as well. It is agnostic about view technologies and can support JSP (JSTL), Velocity, XSLT and is operated by a simple XML file. As a member of the Wafer Project, I built the Wafer example demo weblog application (http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/wafer/examples/maverick/weblog/). There you can also find a .war file to see it in action. This article will cover the code and experience in building that application using the Maverick framework.
This article is for those either getting started on Maverick or who need a quick understanding of how it operates; or those wondering why one would even use this framework. There are a few comparisons to other frameworks, mainly Struts.
The Features
So what makes Maverick unique? Below are some of the key features of the framework:
- Light weight both in API and actual footprint
- Support for multiple templating options
- Controlled by a simple XML file
- Support for Internationalization through the use of Shunts
- Stable development life cycle
- Extensibility and Pluggability
- Performance
Getting Started
For nearly every framework the heart of it is its controlling mechanism. In Maverick's case it is the maverick.xml file. The file layout is simple and doesn't have a DTD to annoy you.
<maverick version="2.0" default-view-type="document" default-transform-type="document"> <views> <view id="loginRequired" path="loginRequired.jsp"> <transform path="trimOutside.jsp"/> </view> <view id="loginFailed" path="loginFailed.jsp"> <transform path="trimOutside.jsp"/> </view> </views> <commands> <command name="welcome"> <view name="success" path="weblog/welcome.jsp"> <transform path="weblog/trimOutside.jsp"/> </view> <view name="success" mode="en" path="weblog/welcome.jsp"> <transform path="weblog/trimOutside.jsp"/> </view> <view name="success" mode="de" path="weblog/welcome_de.jsp"> <transform path="weblog/trimOutside.jsp"/> </view> </command> <command name="weblog"> <controller class="org.wafer.ctl.WebLog"/> <view name="loginRequired" ref="loginRequired"/> <view name="loginFailed" ref="loginFailed"/> <view name="success" path="weblog.jsp"> <transform path="trimInside.jsp"/> </view> </command> </commands>
As you can see from the above snippet, there are two basic nodes to the XML file: <commands> and <views>. These aren't the only ones but are the most important. A command can have no more then one controller associated with it and that controller may be reused in another command. Each command has a name, controller class (optional) and 1 to many view definitions. Each view element in the command node represents a "flow" option for that command. As shown in the weblog command example above, some views are defined inline (inside the command element like success and some are defined globally like loginRequired. These are simply name representations and you are free to name them whatever you like.
The <views>, as mentioned earlier, are a way to globally define a flow for your site which is useful if you have common paths that most, if not all, <command> elements must follow such as those in the loginRequired example above. For Struts users these are similar to <global forwards>. Those views that are either unique or sparsely used can be defined inline with the <command>. When defining a <view> you have the option to specify a transform element. The transform element is similar to a tile ,but not as featureful; however, it is easier to use. The way it reads is the transform element wraps the view JSP. By inspecting the trimInside.jsp file you will notice the following JSTL call,
<c:out value="${wrapped}" escapeXml="false"/>
This basically takes the view's JSP, converts it into a String, and places it in the request attributes with the key of "wrapped". So in the maverick.xml example above, notice the command called weblog and inside it, the <view> called success. Here the weblog.jsp will be turned into a String and placed inside the trimInside.jsp file where the above JSTL call for value{$wrapped}" is found. You can have as many transforms as you like but if you need more then 2 or 3 then you might want to consider using Tiles, or SiteMesh.
Controller
There are 4 types of Controllers you can extend:
- ThrowawayBean2: Easiest of them to use. Uses controller-as-model pattern.
- FormBeanUser: Similar to the Struts Action. Allows external beans to be populated as the model instead of the controller itself.
- ThrowawayFormBeanUser: Similar to the ThrowawayBean2 but allows external beans to be populated.
- ControllerWithParams: Allows you to pass parameters into the controller via the maverick.xml file.
ThrowawayBean2 is the controller I choose in the Wafer project and is also the Controller used in the friendbook example that comes with Maverick. The ThrowawayBean2 controller follows the Controller-as-Model pattern in which the controller acts both as an action class and as the model. This is achieved by placing getters and setters in the controller class which, after execution, places the entire controller in the request with the key of "model". With the help of the Apache BeanUtil package, it automatically populates the setters from the request in the controller.
Typically, you have a separate class or set of classes to represent the model. In the example below, you will notice the properties that would typically be associated with a Comment object; however, those properties are a part of the ViewComment controller . In one class you have the logic to retrieve the comment and store the values in itself, instead of storing the values in a separate object such as Comment. This approach makes it very easy to build simple applications once you get the hang of the Controller-as-Model pattern.
public class ViewComment extends ControllerProtected { protected String subject = ""; protected String body = ""; protected Collection comments; protected Story story; public String getBody() { return body; } public String getSubject() { return subject; } public Collection getComments() { return this.comments; } public Story getStory() { return this.story; } public String securePerform() throws Exception { HttpServletRequest request = this. // Story id String key = (String)request.getParameter("key"); if(key == null || key.equals("")) addError("gen_error", "Invalid key"); if(!this.hasErrors()) { WebLogStory weblogStory = WebLogStory.getWebLogStories(); Story story = weblogStory.getStory(key); this.story = story; this.comments = story.findAllComments(); } if (this.hasErrors()) { return ERROR; } else { return SUCCESS; } }
Note: The above class extends ControllerProtected which itself extends a few other classes (see below) which are very helpful since they provide a lot of fundamental features used on a website, like error handling and authorization. While not part of the core Maverick API, they are a part of the friendbook example bundled with Maverick.
ControllerProtected → ControllerAuth → ControllerErrorable → ThrowawayBean2
The above controller, ViewComment, is a presentation state which means its primary purpose is to setup the next state/page for the user. In the Wafer weblog example the user clicked on the link to view the comments for a story, passing along one variable called key which has the storyId in it.
The perform method will load the story and then the comments into the controller. If any errors exist then the method returns the String of "ERROR". This is very similar to the code in Struts: return (mapping.findForward("error")). Just make sure that the String you are returning is either defined as a global view or is defined as an inline view for that <command> in the maverick.xml file. Behind the scenes, in the ThrowawayBean2 class, ViewComment, is being placed in the request under the key name of model.
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %> <b><c:out value="${model.story.subject}"/></b> <br /> <br /> <c:out value="${model.story.body}"/> <br /> <br /> <a href="addComment.m?key=<c:out value="${model.story.storyId}"/>">Add Comment</a> </br> <hr/> <br /> <c:forEach var="comment" items="${model.comments}"> <b><c:out value="${comment.subject}"/></b> <br /> <br /> <c:out value="${comment.body}"/><br/> <br /> <br /> <a href="addComment.m?key=<c:out value="${model.story.storyId}"/>">Reply</a> <br/> <br/> </c:forEach>
This snippet (above) is from viewComments.jsp. Notice how the controller-as-model is used on the JSP side. Here the variable called model is the ViewComment.java file and has getters for comments and story.
Since we are using the ThrowawayBean2 Controller, which uses the controller-as-model pattern, any values you need to display on the JSP page will need to have getters associated with them such as Story and Comments (see the above JSP snippet). Depending on your needs this can be a great strength or weakness. If this page and controller were associated with a process state (i.e., submit of a form) then the controller would need to have setter methods for any input values that you would want to collect and process in the perform method. See the example AddCommentSubmit.java
FormBeanUser is different from ThrowawayBean2 in that this does not follow the controller-as-model pattern. Here you pass to the controller the class that will be used to represent the model. This is helpful if you want to persist the model in the session or simply don't like the clutter of the controller-as-model pattern. ViewComments2 has the same functionality of ViewComment except it extends FormBeanUser. This Controller is similar to how Struts operates.
public class ViewComments2 extends FormBeanUser { public String perform(Object form, ControllerContext cctx) throws Exception { HttpServletRequest request = cctx.getRequest(); MyFormBean2 formBean = (MyFormBean2)form; // Story id String key = (String)request.getParameter("key"); if(key == null || key.equals("")) { return ERROR; } WebLogStory weblogStory = WebLogStory.getWebLogStories(); Story story = weblogStory.getStory(key); formBean.setStory(story) ; formBean.setComments(story.findAllComments()); return SUCCESS; } public Object makeFormBean(ControllerContext cctx) { HttpServletRequest request = cctx.getRequest(); MyFormBean2 form = new MyFormBean2(); return form; }
The big difference in the JSP would be the call to the form after the model.
<c:out value="${model.form.story.subject}"/>
MyFormBean2 has the getters and setters for the properties of the page.
ThrowawayFormBeanUser is a hybrid between ThrowawayBean2 and FormBeanUser in that it is instantiated like the ThrowawayBean2 controller but allows a separate class to act as the model instead of the controller.
ControllerWithParams is a controller which can have parameters passed to it. For example you might need to do the following;
<controller class="Login"> <param name="secure" value="true"/> </controller>
Internationalization
Maverick has chosen a very different route from the other frameworks to tackle this problem: they call it shunting. Shunts can be best viewed as switches, that switch the view depending on the locale set in the browser. These switches are referenced as modes. Below, I took the welcome command and added multiple views based on the mode set. If your browser is set to German then welcome_de.jsp will be displayed to the user.
<command name="welcome"> <view name="success" mode="en" path="welcome.jsp"> <transform path="trimOutside.jsp"/> </view> <view name="success" mode="de" path="welcome_de.jsp"> <transform path="trimOutside.jsp"/> </view> </command>
You still have the problem of error messages being displayed in a single language and now you have multiple pages to keep in sync when changes occur to the presentation; however, for simple solutions this is very easy. See the JavaDocs for all of the possible modes.
Maverick Options
While Maverick as a presentation framework provides all the basic needs to build web sites, it also has many optional features (available from their download page) that you can add to your project depending on your needs. This is where the extensibility of Maverick really shines through. Below is a list of each option:
- Velocity - Provides Velocity support and examples
- FOP - Create PDF files as your views
- Domify - Originally created as part of Maverick but has since been separated out into a separate project In a nutshell, this allows you to turn you model into a DOM object and then use XSL to display it to a view
- Betwixt - alternative to Domify
- Perl - Run Perl through a Maverick transform type
- Struts - provides tools to help migrate a Struts application to Maverick.
Many of these packages include the friendbook example to help you get a head start on using it. Given the scope of this article, I won't go into much detail on every option but I thought that going over the Velocity and Domify options would be helpful.
Velocity
Velocity is a Java templating engine used by many frameworks like Turbine, WebWork, Jpublish, and with a few configuration changes from the default setting can be easily used by Maverick too. According to the Maverick manual, you do not need the velocity-opt, but this is simply not true. Download the velocity-opt package and copy the velocity-tools-view-0.6.jar and velocity-1.3-rc1.jar into your lib directory. Next, modify the web.xml file by adding the VelocityViewServlet and the .vm mapping definition.
<servlet> <servlet-name>velocity</servlet-name> <servlet-class> org.apache.velocity.tools.view.servlet.VelocityViewServlet </servlet-class> <load-on-startup>10</load-on-startup> </servlet> <servlet-mapping> <servlet-name>velocity</servlet-name> <url-pattern>*.vm</url-pattern> </servlet-mapping>
Lastly modify your maverick.xml as in the following snippet and you're ready to roll:
<command name="welcome"> <view path="welcome.vm"> <transform path="trimOutside.vm"/> </view> </command>
The opt-velocity download also provides a good friendbook example implemented with Velocity and the TransformFactory for the experimental DVSL technology that is developed by the Velocity team.
Domify
Domify is a "cheap" way to implement XSLT. Domify started out as part of the Maverick project but later broke away into its own project, http://domify.sourceforge.net/. In a nutshell, Domfiy allows you to take the Maverick model object and turn it into a DOM object that can be used on the presentation side via XSLT. The opt-domify package has the friendbook example done in Domify for further inspection.
Conclusion
Having worked on many web projects myself and with many web frameworks I will consider Maverick to be a "keeper" in my toolbox of Java apps. Its features of being easy to use, easy to extend, and quick to build with make it my number one choice for simple apps much like the Wafer weblog application was. Besides Maverick's key features, I was also very impressed with the quality of the Maverick code itself. This stuff was not created by any amateurs, which is important if you want to extend it. While the community may not have been as large as with other frameworks, it is large enough to get help from the online community.
Does Maverick mean an end to Struts?
Absolutely not. Struts has many more features and capabilities than Maverick does; however, those features might not be of any interest to you. If you want to learn only one framework then maybe Struts is your solution but remember when holding a hammer not everything is a nail.
Author
Kris Thompson is the lead of a local java user group, www.frameworks-boulder.org in Boulder Colorado that focuses solely on web frameworks and is also a contributor on the Expresso framework and Wafer project. Email Kris at [email protected].