3.3 Building a Central Controller

As an architecture, MVC is a good start and suitable for many applications. But sometimes more advanced processing is called for, so it's time to start filling in the holes in the MVC architecture, starting with the controller. Not only is the controller the first point of contact for requests, it is also the place where we have the most programming and design freedom. Unlike the model, which is shaped by the underlying data, the controller is designed from start to finish as a part of the presentation tier. And unlike the view, which is stateless and generally focused on presentation, we are free to perform as much complicated logic as we like.

The MVC pattern does not specify how many controllers there should be. In our previous example, we built a controller that handled the input from a single screen. In order to extend our application along those same lines, we would have to add new screens and an equal number of new controllers. Alternatively, we could build a single, omniscient controller. This controller would know about every possible request and how to generate a response to each.

Since we are interested in building extensible software, it's obvious that neither of these solutions is exactly right. Building an entire new controller for each screen does not just mean lots of classes, it makes the application harder to extend. If we wanted to build a logging mechanism, for example, we would have to add logging code to each controller separately. In fact, there are lots of things we might need to do for every request: implement navigation, maintain session information, and gather statistics, to name a few. Adding each of these functions to a mix of static HTML, JSP, and servlets is a time-consuming and error-prone process.

We encounter similar problems when using a single controller. While it would be easy to add common functions, the controller must also contain the specific functions for each page. This situation is not very extensible. To add new functionality, we probably need to recompile and redeploy the entire controller (and retest, and redocument . . . ).

A pair of patterns helps balance these two approaches. The Front Controller pattern advocates building a single controller that performs common functions on each request, but delegates other functions to a page-specific controller. The Decorator pattern then shows how to dynamically expand the front controller.

3.3.1 The Front Controller Pattern

The front controller provides a single location that encapsulates common request processing. Figure 3-4 shows an overview of the Front Controller pattern, which looks very similar to our example from the Model-View-Controller pattern.

Figure 3-4. The Front Controller pattern
figs/j2ee_0304.gif

The main participant in the Front Controller pattern is the controller itself. Its job is fairly simple: perform common tasks, and then forward control on to a page-specific controller. While the front controller processes every request, it does not contain the code needed to handle all cases. The specific functions of updating the model and choosing the view are delegated to a page-specific controller.

The page controller performs the model updates and view selection exactly like the controller in the Model-View-Controller pattern described previously. Page controllers may be entire separate servlets, but often they are implemented as much simpler classes based on the GoF Command pattern (see the sidebar, "The Command Pattern"). In this design, many different kinds of page controllers share a simple common interface, and are often referred to as "actions."[2]

[2] Developers familiar with the Struts framework will recognize this concept of actions as similar, if not identical, to the Struts concept of an action. Actions are also similar to Java Server Faces event listeners.

The Command Pattern

Actions are a good example of one of the most useful GoF patterns, the Command pattern. Each command is a single object that encapsulates some sort of request for example, adding a row to a database, or saving a file to disk. Within a given context, many commands share a common, simple interface. Each command might consist of a constructor and a performUpdate( ) method. Internally, the command object stores arguments passed into the constructor, and then applies the actual change when performUpdate( ) method is called.

Commands are powerful for three main reasons. The first is their inherent reusability: a command that performs a specific update based on a simple interface can be reused any time that update needs to be made. For instance, if two applications both need to add users to the same database, the same action can be used in both places.

Commands can also be stored. Since a command encapsulates all the data for a given request, it can be created and initialized at one point and applied at another. For example, a web application might store all of its database updates and then send them to the server as a big group.

Commands can also support undoable operations. Since commands store all the data necessary for a particular request, it is often possible to add an undo method to the interface. If we get halfway through our database update and realize something is wrong, we can easily go through our commands backwards, with each command undoing whatever changes it made.

Since the front controller chooses the page controller, it is ultimately responsible for choosing the correct actions and views. In the Service to Worker pattern in Chapter 4, we will build a complicated controller that handles navigation, taking lots of different information the request, user name, etc. into account when choosing actions and views. A simple front controller, however, only intercepts requests, sending them to their original destination after performing common actions.

Using a front controller incurs certain costs. Since its code is executed in every single request, it goes without saying that the implementation must be as efficient as possible. In most systems, the web server or other framework provides optimized methods of performing certain functions, such as logging and user management. Using the built in versions is usually more efficient than if we implemented them ourselves, but the tradeoff, of course, is in functionality. Often a mixed strategy, like a very simple front controller that leverages or adds to built-in functionality, is the best approach.

3.3.2 The Front Controller Servlet

In a web application, the front controller is almost always implemented as a servlet. While it is technically possible to use a JSP page, it's usually a bad idea JSP pages should not be used to implement complicated logic. A second, more compelling option is to use a servlet filter as the front controller. We discuss this option below.

In this example, we extend our MVC example with a simple front controller to provide security management through a login function. Our front controller will be implemented as a request-intercepting servlet. If the servlet determines that a user has not logged in, it forwards the user to a login page. Once the user has logged in, requests are forwarded to the elements we developed in the MVC example, which act as page controllers. The interactions between these components are shown in Figure 3-5.

Figure 3-5. Front controller interactions
figs/j2ee_0305.gif

Our front controller servlet actually looks almost exactly like the ListController we built in Example 3-3. Like the list controller, it accesses a JavaBean in this case, the UserBean, which stores username and password information:

public interface UserBean {     // the usename field     public String getUsername(  );     public void setUsername(String username);          // the password field     public String getPassword(  );     public void setPassword(String password);          // business method to perform login     public boolean doLogin(  );     public boolean isLoggedIn(  );    }

The front controller also chooses between views. If the user has not yet logged in, login.jsp, shown in Example 3-5, is displayed. If the user has logged in, the request is forwarded to the original target.

Example 3-5. login.jsp
<%@page contentType="text/html"%> <jsp:useBean  scope="session"                /> <html> <head><title>Login</title></head> <body> <br><br> <form action="/pages/subscribe.html" method="get"> Username: <input type="text" name="username"                  value=<jsp:getProperty name="userbean"                                          property="username"/>> <br> Password: <input type="text" name="password"><br> <input type="submit" value="Log In"> </form> </body> </html>

Example 3-6 shows the front controller itself. Note how it uses the session scope to store the user's login information, so the information persists across all requests from the user.

Example 3-6. The FrontController class
import javax.servlet.*; import javax.servlet.http.*; public class FrontController extends HttpServlet {     public static final String USERNAME_PARAM = "username";     public static final String PASSWORD_PARAM = "password";     public static final String USERBEAN_ATTR = "userbean";     public static final String CONTROLLER_PREFIX = "/pages";          public void init(ServletConfig config)     throws ServletException {         super.init(config);             }          public void destroy(  ) {     }         protected void doGet(HttpServletRequest request,                          HttpServletResponse response)     throws ServletException, java.io.IOException {         // the default next page         String nextPage = request.getRequestURI(  );                  // strip off the prefix         nextPage =              nextPage.substring(CONTROLLER_PREFIX.length(  ));                 // find userbean from session         HttpSession session = request.getSession(true);         UserBean userBean =             (UserBean)session.getAttribute(USERBEAN_ATTR);                  if (userBean == null || !userBean.isLoggedIn(  )) {             // read request parameters             String username =                  request.getParameter(USERNAME_PARAM);             String password =                 request.getParameter(PASSWORD_PARAM);                      // if it doesn't exist, create it             if (userBean == null) {                 userBean = UserBeanFactory.newInstance(  );                 session.setAttribute(USERBEAN_ATTR, userBean);             }                      // record username and password values             userBean.setUsername(username);             userBean.setPassword(password);                  // atttempt to login             boolean result = userBean.doLogin(  );                      if (!result)                 nextPage = "/login.jsp";         }                  // transfer control to the selected page controller         RequestDispatcher dispatcher =             getServletContext(  ).getRequestDispatcher(nextPage);         dispatcher.forward(request, response);             }     }

If the user is already logged in, the request is forwarded immediately to the requested URL but with the prefix stripped off. We'll discuss why this is necessary in a minute.

3.3.2.1 Deploying the front controller

Since we are creating an intercepting filter, we want all requests whether for static HTML pages, JSPs, or servlets to go to our controller when they first come into the system. There is one gotcha here: requests generated with the forward( ) method of the RequestDispatcher must not go to the front controller. If they do, we have created an infinite loop.

Unfortunately, avoiding this loop is not as straightforward as you might think. The RequestDispatcher generates requests exactly as if they came from outside the system. If we simply deploy our controller to intercept all requests, it will not work; it will cause a loop. Instead, we must create a convention: any URL beginning with /pages will invoke the controller. When the controller does a forward, it simply removes the prefix, meaning that any page can be accessed by simply putting the prefix in front of it. In Example 3-5, the form action is /pages/subscribe.html. Therefore, if the login is successful, the response will be generated by /subscribe.html. We can achieve the same effect by choosing an extension instead of a prefix. In that case, /subscribe.html.act might map to /subscribe.html.

The final step in deploying the controller is to edit the server's web.xml file to make sure the controller is called for any request beginning with /pages. We can accomplish this with the deployment section shown in Example 3-7.

Example 3-7. Front controller deployment information
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app     PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"     "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app>   ...   <servlet>     <servlet-name>FrontController</servlet-name>     <servlet-class>FrontController</servlet-class>   </servlet>   <servlet-mapping>     <servlet-name>FrontController</servlet-name>     <url-pattern>/pages/*</url-pattern>   </servlet-mapping> ... </web-app>

This method is fairly complicated and somewhat kludgey. Fortunately, a more elegant solution is in the works. Instead of using a servlet as a front controller, a servlet filter can intercept requests and invoke actions in much the same way as a servlet. We talk more about filters in the next section, but for now it is sufficient to note that one of the new features in the Servlet API Version 2.4 extends the functionality of filters to allow fine-grained control of request interception. Using the REQUEST context allows us to filter only the original request, not forwards and includes meaning we could use "*" as a URL pattern without worrying about infinite loops.

3.3.3 The Decorator Pattern

The Decorator pattern combines multiple small components into a single piece. Decorator is a term used by the GoF to describe what is basically a wrapper: a class that contains a single child and presents the same interface as that child.

The decorator "decorates," or adds a piece of functionality, to its child. When a method is called on the decorator, it does its own preprocessing, then calls the corresponding method on the child. The result is an extended response, just as if the original component had the decorator code built into it. Since the decorator presents the same interface as the child, code that relies on the child does not even need to know that it is using a decorator and not the child itself.

Decorators contain only a single child, but they can be chained. A chain of decorators consists of multiple decorators, each decorating the original object at the end of the chain. It is the responsibility of each decorator to call the methods of the next object in the chain. In this way, multiple levels of decorators can be added to the same target object. Figure 3-6 shows a chain containing two decorators. Note that both decorators present the same interface, Component, as the target.

Figure 3-6. The Decorator pattern
figs/j2ee_0306.gif

The major restriction on a decorator is that it only decorate a single component. This detail is important, since it guarantees that decorators may be configured into chains of arbitrary length.

While it is convenient to dynamically add and remove decorators, doing this too much can significantly increase complexity. Decorators are designed to be independent of each other and of the target, but in practice this is rarely the case. Dependencies and ordering assumptions can cause difficult bugs when a decorator is missing, or if decorators are added in the wrong order.

3.3.4 Decorating the Front Controller

In J2EE, decorators have many uses. In the business tier, wrappers help us access beans remotely and implement security, among other things. In the presentation tier, they help us dynamically extend both views and controllers. We are going to look at one key application of decorators: dynamically extending the front controller.

The problem with front controllers is that they tend to get complicated. Imagine we have a front controller that controls logging, the LoggingController. If we want to add optional debugging code, we could modify the servlet's doGet( ). We would then have a second controller, the DebuggingLoggingController. If we wanted logging only in certain cases, we might need four different controllers. Building, deploying, and testing all these combinations can become a nightmare.

By chaining different combinations of decorators on the front controller, we can quickly add and remove features from the controller. And by using decorators, we can effectively decouple all these different functions.

3.3.4.1 Implementing a decorating filter

J2EE supports decorators natively: the Java Servlet API, from Version 2.3, provides a powerful filter mechanism. These filters decorate requests to the J2EE server, allowing preprocessing of the request and postprocessing of the result. Filters can also be chained arbitrarily. Most importantly, they can be added and removed from any page or set of pages at runtime.

A filter decorates a request made to the servlet container. Decorating a request is actually quite simple: instead of calling a servlet's doGet( ) or doPost( ) method, the servlet container calls the filter's doFilter( ) method. The filter is passed the request, the response, and an object of type FilterChain, which can be used to call the next filter in the chain. A given filter reads whatever part of the request it wants, generates whatever output it wants, and uses the FilterChain's doFilter( ) method to invoke the next filter in the chain. If there are no more filters, the target servlet's doGet( ) or doPost( ) method is called.

The filter may preprocess the request before calling the next element in the chain, or postprocess data that has been returned by filters later in the chain. This kind of processing is useful for tasks such as decrypting a request as it is received and then encrypting the response before it is sent out. The filter may also choose not to run the next filter in the chain (for example, in order to create a security filter that refuses access to some resources). Figure 3-7 shows the interactions that occur when two decorating filters are chained together.

Figure 3-7. Using a decorating filter
figs/j2ee_0307.gif

Let's look at a simple filter that prints out some debugging information about the request it receives. The RequestInfoFilter is shown in Example 3-8. For each request, this filter prints out information about the desired URL and all the request parameters.

Example 3-8. A simple debugging filter
public class RequestInfoFilter implements Filter {   // describes the filter configuration   private FilterConfig filterConfig = null;        // nothing to do in the constructor    public RequestInfoFilter(  ) {}        // just store the FilterConfig   public void init(FilterConfig filterConfig) {     this.filterConfig = filterConfig;   }        public void doFilter(ServletRequest request,                         ServletResponse response,                        FilterChain chain)   throws IOException, ServletException {     ServletContext sc = filterConfig.getServletContext(  );       // preprocess the request     HttpServletRequest hrs = (HttpServletRequest)request;     sc.log("Request Attributes:");     sc.log("Method: " + hrs.getMethod(  ));     sc.log("QueryString: " + hrs.getQueryString(  ));     sc.log("RequestURL: " + hrs.getRequestURL(  ));     sc.log("RequestURI: " + hrs.getRequestURI(  ));     sc.log("PathInfo: " + hrs.getPathInfo(  ));     sc.log("Parameters:");         // enumerate all request parameters     for (Enumeration e = request.getParameterNames(  );          e.hasMoreElements(  ); ) {       String name = (String)e.nextElement(  );       String vals[] = request.getParameterValues(name);           StringBuffer out = new StringBuffer(  );       out.append(name + "=");              // each parameter may have multiple values       for(int i=0; i < vals.length; i++) {         out.append(values[i]);         out.append(",");       }              // remove the trailing comma       sc.log(out.substring(0, out.length(  ) - 1);     }     // invoke the next filter in the chain     chain.doFilter(request, response);   }   // called when the filter is deactivated   public void destroy(  ) {} }

We can use a different kind of decorator to allow more advanced filters. The FilterChain object's doFilter( ) method takes an instance of the ServletRequest and ServletResponse classes. In our filter, we can decorate the request and response objects, providing versions to extend the functionality of the core request and response, and pass those decorated requests and responses into the doFilter( ) method. Later filters and the eventual target treat the decorated request and response as they would the original.

Version 2.3 of the Servlet API provides classes for decorating the request and response: the ServletRequestWrapper, ServletResponseWrapper, and corresponding HttpServletRequestWrapper and HttpServletResponseWrapper. The constructors for these classes take an instance of the request or response class in their constructor. They present the full request or response interface, implementing all methods by forwarding to the original request or response. By subclassing these objects, we can easily modify their behavior to store and modify data generated later in the request processing.

Wrapping the request and response allows us to build a variety of powerful filters. To compress the output of a page, a filter can wrap the response with a ServletResponseWrapper that compresses its ServletOutputStream. Encrypting filters can be built the same way, along with decrypting filters that decrypt the request on the fly. Example 3-9 shows a filter that translates pages with the header variable "XML-Encoded" from XML to HTML using a predefined XSLT Transformation file.

Example 3-9. An XSLT filter
public class XSLTFilter implements Filter {  private FilterConfig filterConfig = null;    public XSLTFilter(  ) {}    public void init(FilterConfig filterConfig) {   this.filterConfig = filterConfig;  }    public void doFilter(ServletRequest request,                        ServletResponse response,                       FilterChain chain)   throws IOException, ServletException {      ServletContext sc = getFilterConfig().getServletContext(  );   sc.log("XSLTFilter:doFilter(  ):start");     // wrap response         XSLTResponseWrapper xsltResponse =     new XSLTResponseWrapper((HttpServletResponse)response, "/SimpleTransform.xsl");   HttpServletRequest httpRequest = (HttpServletRequest)request;      // forward to next filter         chain.doFilter(httpRequest, xsltResponse);      // write actual response         xsltResponse.writeResponse(  );   sc.log("XSLTFilter:doFilter(  ):end");  }    public void destroy(  ) {  }  }

The filter itself is simple. It wraps the original response with an instance of the XSLTResponseWrapper and passes it on to the rest of the chain. When the chain returns, it calls the writeResponse( ) method to postprocess the result.

The complexity of this filter lies in the response wrapper. By default, the response may be sent over the network to the client incrementally, as it is generated. Since postprocessing does not do us much good if the response has already been sent, we are left with two options: manipulating the response as it is generated, or collecting and storing the data in the stream and processing it all at once.

Whenever possible, it is better to manipulate the stream as it comes in, saving the overhead of storing the data. This approach is ideal for applications that only require sequential access to the data: compression, encoding, and other techniques that are designed to work on streams of data.

Unfortunately, XML processing requires random access to the data, so we will have to store it ourselves. The CacheOutputStream, shown in Example 3-10, performs this function. Once all the data has been written to the CacheOutputStream, it can be retrieved as an InputStream.

Example 3-10. The CacheOutputStream
// a Utility class to adapt a ByteArrayOutputStream // to be a subclass of ServletOutputStream   class CacheOutputStream extends ServletOutputStream {   private ByteArrayOutputStream bos;            public CacheOutputStream(  ) {     bos = new ByteArrayOutputStream(  );   }            public void write( int b ) {     bos.write( b );   }            public void write( byte[] b, int offset, int len) {     bos.write( b, offset, len );   }        public byte[] toByteArray(  ) {     return bos.toByteArray(  );   } }

The response wrapper overrides the getOutputStream( ) and getWriter( ) methods of the original to return a CacheOutputStream. Example 3-11 shows the actual wrapper.

Example 3-11. The XSLTResponseWrapper
public class XSLTResponseWrapper extends HttpServletResponseWrapper {   // the original response   private HttpServletResponse orig;         private PrintWriter writer;   private ServletOutputStream stream;     // the name of the XML transform filter   private String transform;     // whether the output page is XML   private boolean isXML = false;            // store the transform file and original request   public XSLTResponseWrapper(HttpServletResponse response,                              String transform)   {     super(response);     orig = response;     this.transform = transform;   }     // create the output stream - if the response is   // encoded, store it for transforming later, otherwise   // just use the default stream        public ServletOutputStream createOutputStream(  )    throws IOException   {     if( this.containsHeader("XML-Encoded") ) {       isXML = true;       return new CacheOutputStream(  );     } else {       return orig.getOutputStream(  );     }   }       // return the output stream - fail if getWriter(  ) has   // been called previously        public ServletOutputStream getOutputStream(  )    throws IOException   {     if( stream != null ) return stream;     if( writer != null )       throw new IOException("getWriter(  ) already called");                 stream = createOutputStream(  );     return stream;   }     // return a Writer - fail if getOutputStream(  ) has    // been called previously          public PrintWriter getWriter(  ) throws IOException {     if( writer != null ) return writer;                  if( stream != null )       throw new IOException("getOutputStream(  ) already called");     writer =  new PrintWriter(                  new OutputStreamWriter(createOutputStream(  )));     return writer;   }            // called by the filter to do the actual XML transformation   // returns immediately if the data was not XML   public void writeResponse(  ) throws IOException {     if( !isXML ) return;                  ServletContext sc = filterConfig.getServletContext(  );     sc.log("XSLTFilter:writeXML");                  if( stream == null )       throw new IOException("No stream to commit");             InputStream is =       ((CacheOutputStream)stream).getInputStream(  );        // do the actual transform               try {       DocumentBuilderFactory dbf =            DocumentBuilderFactory.newInstance(  );       DocumentBuilder db = dbf.newDocumentBuilder(  );           // read cached response data                Document doc = db.parse(is);           // apply the transform              TransformerFactory tf = TransformerFactory.newInstance(  );       StreamSource stylesheet =           new StreamSource(sc.getResourceAsStream(transform));       Transformer transformer = tf.newTransformer(stylesheet);        DOMSource source = new DOMSource(doc);       // send results to the original output stream       StreamResult result =           new StreamResult(orig.getOutputStream(  ));                        transformer.transform(source, result);     } catch( Exception ex ) {       sc.log("Error Transforming", ex);     }    } }

The code must enforce that only one of those methods may be called for each response. Also, this wrapper chooses at runtime whether to store the data. If the response is not XML encoded, the data is passed directly to the client. Therefore, we must set the XML-Encoded response header before generating any output.[3]

[3] This isn't a standard HTTP header, but one we've defined for our own application.

3.3.4.2 Deploying decorating filters

Decorators can be dynamically added and removed. Like servlets, filters are configured in the web.xml file. They are deployed against a target, which may contain wild cards. As we mentioned earlier, unlike servlets, filter patterns are not looked at in include and forward calls, so there is no problem of recursion. If we have a global filter we can simply deploy it for all requests, with the following entry in web.xml:

<filter>  <filter-name>RequestInfoFilter</filter-name>  <filter-class>RequestInfoFilter</filter-class>  </filter> <filter> <filter-mapping>  <filter-name>RequestInfoFilter</filter-name>  <url-pattern>/*</url-pattern> </filter-mapping>

By adding multiple entries in this way, we can easily chain filters. The filter chain is built dynamically at request time. By changing the target URL say, to apply to only /page/* we can have filters on just the front controller. We can even put filters on individual pages by specifying the URL of the page controller say, /page/XMLPage. Since all filters that match are composed into a chain, a different chain would be built for XMLPage than the front controller itself.

3.3.4.3 Other decorators

Of course, filters are not the only kind of decorators in J2EE. In implementing our filters, we used wrappers for the servlet request and response, which are another example of decorators. Other kinds of decorators can be found in every layer of J2EE.

While decorators represent a fairly simple way to extending an object's functionality, aspect-oriented programming (AOP) is a programming methodology centered around dynamically extending objects. An aspect in AOP represents a common concept that cuts across multiple classes, like the idea of logging. An aspect encapsulates the code to perform an action (like logging) as well as the targets it applies to (the front controller) and the conditions under which it should apply (whenever a get request is received), all in one construct. Whenever its conditions are met, the code in an aspect is executed. In simple cases, like logging, an aspect works a lot like a decorator. But aspects provide many sophisticated ways of specifiying conditions, such as determining when one method is called from within another, which make them far more powerful (and complicated) than decorators.

In this chapter, we looked at three design patterns that help create an extensible presentation tier. The Model-View-Controller pattern gives an architecture for enterprise applications that separates the application into functional pieces with clear interfaces. The Front Controller pattern extends this architecture, describing a central controller that performs common functions on each request. Finally, the Decorator pattern shows how to dynamically add functionality to the front controller. Used together, these patterns create an extensible foundation that we will expand on throughout the rest of this book.



J2EE Design Patterns
J2EE Design Patterns
ISBN: 0596004273
EAN: 2147483647
Year: 2006
Pages: 113

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net