No XML, please!

A small, flexible, and easy MVC Web framework with no XML

Some people like olives. Some don't. I don't like olives. I don't like XML either. Unfortunately, for me, all major Web frameworks rely on XML to do basically everything: setup, configuration, form handling, validation, etc. Welcome to the world of XML "programming."

Another problem for me is that I like to do things really fast. That's why I had a feeling something was wrong with C++. To do a project with C++, medium programmers end up spending more than half their time fighting with the language syntax, pointers, handlers, memory management, unbound arrays, etc., and very little time doing what they're paid to do: the project. The Java language comes to the rescue with an abstraction power that makes everything easy. Whatever pops into your head—Java can do it! You don't have to care how, you just have to use the APIs.

Unfortunately, the Web frameworks out there are not for the medium guys. They require time to master and can scare rookies with XML all over the place and little tricks they are expected to know and get away with. It is like showing a Hello World EJB example to someone that has never seen an Enterprise JavaBeans component before. The result is shock and fear.

Mentawai, an open source framework, tries to break free from this scenario by implementing a small, simple, flexible, idiot-proof MVC (Model-View-Controller) Web framework with no XML at all. Instead of XML, we can use Java code to achieve the same results, and guess what: Java code has Javadocs! You can even compile Java code to catch errors! Java code is object-oriented programming. How about XML?

The most common reply I get from people when I defend this point is: "Hey, you don't have to compile and restart your Web application with XML!" I may be losing something deeper here, but, if you change your XML, you do have to restart your Web context for most of the Web frameworks I have seen. Plus compiling is definitely not a bad thing. It is a good thing! If you could compile XML to quickly discover a wrong attribute, our debbuging time would decrease.

Now that I have argued my point, let's check what Mentawai can offer us as an alternative.

Setup

Let's begin by setting up our Web application to use the Mentawai framework. First, place the mentawai.jar file (downloadable from Resources) inside your application's /WEB-INF/lib directory and declare in your web.xml file that you want to use the Mentawai controller:

 

<!-- The Mentawai controller --> <servlet> <servlet-name>Controller</servlet-name> <servlet-class>org.mentawai.core.Controller</servlet-class> <load-on-startup>1</load-on-startup> </servlet>

<!-- You must choose an extension to indicate a mentawai action --> <servlet-mapping> <servlet-name>Controller</servlet-name> <url-pattern>*.mtw</url-pattern> </servlet-mapping>

The action paradigm

Mentawai uses the action paradigm. The main characteristics of a Mentawai action are:

  • An action has an input (org.mentawai.core.Input) and an output (org.mentawai.core.Output).
  • An action generates a result (java.lang.String) after it executes. The result is usually SUCCESS or ERROR, but you can create your own.
  • For each result, there is a consequence (org.mentawai.core.Consequence). Consequences for a Web application are usually FORWARD or REDIRECT, but you can create your own.
  • An action has access to contexts (org.mentawai.core.Context). Contexts for a Web application are usually a session context (org.mentawai.core.SessionContext) and an application context (org.mentawai.core.ApplicationContext), but you can create your own.

Let's see an example Mentawai action:

 

package examples.hello;

import org.mentawai.core.*;

public class HelloMentawai extends BaseAction {

public String execute() throws ActionException { String username = input.getStringValue("username"); if (username == null || username.trim().equals("")) { return ERROR; } output.setValue("username", username.toUpperCase()); return SUCCESS; } }

This action looks for a username input parameter and outputs it in uppercase. If it does not find the username parameter, the result is ERROR, otherwise SUCCESS. HelloMentawai inherits from BaseAction, which is a useful implementation of the Action interface. BaseAction provides, through protected data members, access to input, output, session context, application context, and more.

For each possible result, we must associate a consequence. Instead of using a boring XML file, Mentawai let's you do that with Java code by creating an ApplicationManager class that extends org.mentawai.core.ApplicationManager:

 

// No package (root package)

public class ApplicationManager extends org.mentawai.core.ApplicationManager {

public void loadActions() { ActionConfig ac = new ActionConfig("/HelloWorld", HelloMentawai.class); ac.addConsequence(HelloMentawai.SUCCESS, new Forward("/hello.jsp")); ac.addConsequence(HelloMentawai.ERROR, new Forward("/username.jsp")); addActionConfig(ac);

// TIP: you can also use this less error-prone syntax /* addActionConfig(new ActionConfig("/HelloWorld", HelloMentawai.class) .addConsequence(HelloMentawai.SUCCESS, new Forward("/hello.jsp")) .addConsequence(HelloMentawai.ERROR, new Forward("/username.jsp"))); */ } }

The consequence for a SUCCESS is a forward to /hello.jsp. The consequence for an ERROR is a forward to /username.jsp. When the container starts, the Mentawai controller calls the loadActions() method. Through the ActionConfig class, you can define different names and consequences for the same action implementation. The Mentawai controller finds actions by name, so "/HelloWorld" will be called by http://www.myapp.org/HelloWorld.mtw, and "/customers/ListCustomers" will be called by http://www.myapp.org/customers/ListCustomers.mtw. You can put your ApplicationManager class in any package, but, by default, Mentawai tries to load it from the root package.

Below is the code for the username.jsp page:

 <html>
<body>
<h1>Hello Metawai!</h1>
<form action="HelloWorld.mtw" method="post">
Type an username: <input name="username" size="25" />
<input type="submit" value="Send">
</form>
</body>
</html>

Below is the code for the hello.jsp page:

 <%@ taglib uri="/WEB-INF/lib/mentawai.jar" prefix="mtw" %>
<html>
<body>
<h3>Hello <mtw:out value="username" /> from Mentawai!</h3>
</body>
</html>

Notice the use of a Mentawai custom tag to display the action output. Mentawai provides many useful and simple tags for displaying action output. It also provides a base custom tag implementation from where you can easily create your own Mentawai tags. Mentawai also supports JSTL (JavaServer Pages Standard Tag Libraries), expression language, and Velocity for the view layer.

To test the above example, place the mentawai.jar in your classpath, compile the above classes, create the hello.jsp and username.jsp pages and set up everything in your Web application. (If you want, download the HelloWorld.war archive with all the files for this example.) Access the first page with http://www.yourapp.org/yourcontext/username.jsp. Type a username, and you should see the username in uppercase. Congratulations! You have run your first Mentawai Web application!

Best practices

Notice that through the ActionConfig class, you can reuse the same action implementation in different situations. For example, you may create two ActionConfigs with the same action implementation, but with different consequences that lead to different JSP (JavaServer Pages) pages. In other words, the same model can have different views, which is what the MVC pattern dictates.

Mentawai filters

A filter intercepts an action so it can modify its input and output, before and after the action executes. A filter is Mentawai's most important concept, and most of the framework's features are implemented as transparent filters. To learn about filters, let's code a generic filter that tries to populate a value object from the action input parameters. But, before we dive into the code, let's understand the org.mentawai.core.Filter interface:

 

package org.mentawai.core;

public interface Filter {

public String filter(InvocationChain chain) throws FilterException, ActionException;

}

An action may have more than one filter, so for each action, an InvocationChain class is created, which has two important methods:

  • getAction(): Returns the invocation chain's action.
  • invoke(): Executes the next step in the invocation chain—in other words, the next filter or the action. The action is the last step in an InvocationChain.

It is important to understand that a filter can alter an action before and after it executes. Check the code below for an example:

 

import org.mentawai.core.*;

public class MyFilter implements Filter {

public String filter(InvocationChain chain) throws FilterException, ActionException { Action action = chain.getAction(); Output output = action.getOutput(); output.setValue("before", "doing something before!"); String result = chain.invoke(); output.setValue("after", "doing something after!"); return result; } }

Now, let's code our generic filter.

The VOFilter

The org.mentawai.filter.VOFilter comes with Mentawai and proves useful for populating a value object with form parameters. For example, the simple class User.java has two attributes: username and password. We can use the VOFilter to populate a user bean before an action executes. Let's first check VOFilter's source code:

 

package org.mentawai.filter;

import java.lang.reflect.*; import java.util.*;

import org.mentawai.core.*;

public class VOFilter implements Filter { private Class klass; public VOFilter(Class klass) {

this.klass = klass; } private Object createObject() { try { return klass.newInstance(); } catch(Exception e) { e.printStackTrace(); } return null; } /* * Use reflection to set a property in the bean */ private void setValue(Object bean, String name, Object value) { try { StringBuffer sb = new StringBuffer(30); sb.append("set"); sb.append(name.substring(0,1).toUpperCase()); sb.append(name.substring(1)); Method m = klass.getMethod(sb.toString(), new Class[] { value.getClass() }); if (m != null) { m.invoke(bean, new Object[] { value }); } } catch(Exception e) { e.printStackTrace(); } } public String filter(InvocationChain chain) throws FilterException, ActionException { Action action = chain.getAction(); Input input = action.getInput(); Object bean = createObject(); if (bean != null) { Iterator iter = input.keys(); while(iter.hasNext()) { String name = (String) iter.next(); Object value = input.getValue(name); setValue(bean, name, value); } // Set the bean in the action input input.setValue(klass.getName(), bean); } return chain.invoke(); } }

Our user bean:

 

package examples.vofilter;

public class User { private String username; private String password; public User() { } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public String getUsername() { return username; } public String getPassword() { return password; } }

The VOFilter loops through every action input parameter and tries to inject these values in the class passed to the filter's constructor. It uses the class name to place the new object in the input so the action can access it.

The code for a simple action that might use this filter is:

 

package examples.vofilter;

import java.util.*;

import org.mentawai.core.*;

public class HelloVOFilter extends BaseAction {

public String execute() throws ActionException { User user = (User) input.getValue("examples.vofilter.User"); if (user == null) return ERROR; // Do something with the User object

output.setValue("user", user); return SUCCESS; } }

Setting up the VOFilter to filter our action is easy:

 

// No package (root package)!

import org.mentawai.core.*; import org.mentawai.filter.*; import examples.vofilter.*;

public class ApplicationManager extends org.mentawai.core.ApplicationManager {

public void loadActions() {

ActionConfig ac = new ActionConfig("/HelloVOFilter", HelloVOFilter.class); ac.addConsequence(HelloVOFilter.SUCCESS, new Forward("/hello.jsp")); addActionConfig(ac); // Add a VOFilter to load a User object... ac.addFilter(new VOFilter(User.class)); } }

Through the method addFilter(), you can add filters to your actions. Notice that an action may have more than one filter, and you can also set global filters that intercept all actions with the addGlobalFilter() method.

Refer to Resources to download the complete VOFilter example as a war archive.

1 2 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more