Saturday, December 22, 2012

Plain-vanilla Java Servlets and Pretty URLs

I'm writing this because I was recently asked about the growing complexity of Java-based web MVC frameworks. It made me think about the kinds of web applications a Java developer can build with just the plain-vanilla Java Servlets.

As I was pondering on the thought, I could not help ask myself about the following:

  • What happens to pretty URLs?
  • How can we add page templates like those provided by SiteMesh and Apache Tiles?

Pretty URLs and Mapping Conventions

It's common nowadays to consider question mark (?) and ampersand (&) characters in the URL as being ugly. But are they really that bad? Do they really affect SEO in a negative way? The truth is... it is just a myth. Why would Google (and other search engines) put so much weight on the URL? Fact is... Google will (and should) do a good job by looking into the contents the URL is pointing to. It should not just be looking at the URL. If it did put so much weight on URLs, then we'll all be filled with spammed links by those who just want to be on top of SERPs.

If so, how do we now map URLs to Java servlets? Below show the typical URL mapping.

MethodURLAction
GET/magazinesindex (default)
GET/magazines/{id}show
GET/magazines/{id}/editeditRender HTML form for editing magazine with given {id}
PUT/magazines/{id}updateUpdates magazine with given {id}. Redirect to GET /magazines.
GET/magazines/newnewRender HTML form for new magazine.
POST/magazinessaveSave magazine ad and generate new unique ID. Redirect to GET /magazines.
DELETE/magazines/{id}deleteRemove magazine with given {id}

Given the above URL mappings, it's impossible to implement using a web.xml. So, are we really forced to use the above conventions? Are we really forced to use a single servlet, mapped to /app/*, and have it apply a regular expression to determine which controller should handle the incoming request?

IMO, we should not be forced to do so. Assuming you'd allow URLs that contain question marks (?) and ampersands (&), we can have the following mappings.

MethodURLAction
GET/magazinesindex (default)
GET/magazines?id={id}show
GET/magazines?id={id}&editeditRender HTML form for editing magazine with given {id}
PUT/magazines?id={id}updateUpdates magazine with given {id}. Redirect to GET /magazines.
GET/magazines?newnewRender HTML form for new magazine.
POST/magazinessaveSave magazine and generate new unique ID. Redirect to GET /magazines.
DELETE/magazines?id={id}deleteRemove magazine with given {id}

The above mappings are definitely possible to implement in a web.xml. It just needs a servlet mapped to /magazines/*. It will also need a filter to support the translation of PUT and DELETE methods (as most browsers only support GET and POST). As for nested resources/namespaces, a similar approach applies. It will need another servlet mapped to /magazines/ads/*.

MethodURLAction
GET/magazines/ads?m_id={magazine_id}index (default)List ads under the magazine with the given {magazine_id}.
GET/magazines/ads?m_id={magazine_id}&id={id}showShow magazine ad with given {id}.
GET/magazines/ads?m_id={magazine_id}&id={id}&editeditRender HTML form for editing magazine ad with given {id}
PUT/magazines/ads?m_id={magazine_id}&id={id}updateUpdates magazine ad with given {id}. Redirect to GET /magazines/ads.
GET/magazines/ads?m_id={magazine_id}&newnewRender HTML form for new magazine ad.
POST/magazines/ads?m_id={magazine_id}saveSave magazine ad and generate new unique ID. Redirect to GET /magazines/ads?m_id={magazine_id}.
DELETE/magazines/ads?m_id={magazine_id}&id={id}deleteRemove magazine ad with given {id}

To extend the conventions further, each servlet acting as controller (in MVC) should forward to a view (typically implemented as a JSP). These JSPs can be located under /WEB-INF/views/magazines and /WEB-INF/views/magazines/ads to match their servlet URL mappings.

Templating Frameworks

I find it more and more common to use SiteMesh or Tiles in developing Java-based web applications. In fact, it would be uncommon to see a Java web application that does not use SiteMesh or Tiles.

After doing some research, I found a lesser known feature of Java Server Pages (JSPs) 2.0: <include-prelude> and <include-coda>. These allow developers to have templates included in the beginning and end of all JSPs that matched the URL.

An example configuration in web.xml looks like this:

  <jsp-config>
    <jsp-property-group>
      <url-pattern>*.jsp</url-pattern>
      <include-prelude>/WEB-INF/includes/prelude.jspf</include-prelude>
      <include-coda>/WEB-INF/includes/coda.jspf</include-coda>
    </jsp-property-group>
  </jsp-config>
The *.jspf is just a common filename extension for JSP fragments.

If the page layout is simple enough to allow a header and footer implemented as a prelude and coda, then using this JSP 2.0 feature should do the trick.

Conclusion

So, is it really that bad? What do you think?

IMO, it's not that bad. There's definitely more to building web apps using just plain Java Servlets and JSPs. But these alone are quite powerful. And the MVC frameworks enhance it further.

That's all for now. Happy holidays!

2019 September Update

If you'd like to know more, checkout how a Java Servlet works. It even contains a cool trivia about the people who created the Java Servlet 1.0 specification! Happy reading!

Tuesday, November 13, 2012

Spring MVC Slow on Google App Engine for Java?

We previously tried deploying Spring-based Java web applications on Google's App Engine (GAE). And I remember abandoning it due to the time it takes to handle a loading request.

Now, I was able to get some time to create a minimal Spring MVC web application and see how it performs on GAE. Below is the result. The web.xml loads the ContextLoaderListener and DispatcherServlet. The application context is empty (i.e. no beans). The dispatcher-servlet.xml contains a <context:component-scan /> tag and a ViewResolver. It takes about 6.75 seconds for a loading request (approximately cpu_ms=3600).



6.75 seconds for a loading request is probably not much. But the 1100 cpu_ms by slim3 is sure enviable. I also found this article helpful in explaining the cause of increase in loading request time.

Here's some detailed log entries:

javax.servlet.ServletContext log: Initializing Spring root WebApplicationContext
…
org.springframework.web.context.ContextLoader initWebApplicationContext: Root WebApplicationContext:
initialization completed in 2420 ms
…
javax.servlet.ServletContext log: Initializing Spring FrameworkServlet 'dispatcher'
org.springframework.web.servlet.FrameworkServlet initServletBean: FrameworkServlet 'dispatcher': initialization started
…
org.springframework.web.servlet.FrameworkServlet initServletBean: FrameworkServlet 'dispatcher':
initialization completed in 2995 ms

Looks like the DispatcherServlet eats up some processing time. If we just use the ContextLoader, and plain-vanilla servlets, the loading request time should be tolerable.

Wednesday, November 7, 2012

Google Closure Compiler with Maven Builds

In my previous post, I showed how we used Closure Library with our Maven-based project. More specifically, we used the Jetty Maven plugin to expose the JS code, and make it available in raw form for a fast and convenient code-run-debug cycle. This time, let me show how we used the Closure Compiler to compile and generate the minified JS file, and how we used it in our builds.

Running Closure Compiler

Running the compiler was a bit tricky. Since we didn't have just one single JS file, we needed to provide the compiler a list of JS files in the correct order (where providing classes are declared before the ones that required them). Good thing the library comes with Python scripts that does just that.

<plugin>
 <groupId>org.codehaus.mojo</groupId>
 <artifactId>exec-maven-plugin</artifactId>
 <executions>
  <execution>
   <id>calculate-dependencies</id>
   <phase>process-sources</phase>
   <goals>
    <goal>exec</goal>
   </goals>
   <configuration>
    <executable>python</executable>
    <arguments>
     <argument>src/main/js/closure-library/closure/bin/build/depswriter.py</argument>
     <argument>--root_with_prefix</argument>
     <!-- prefix is relative to location of closure-library/closure/goog/base.js -->
     <argument>src/main/js/myapp/ ../../../myapp/</argument>
     <argument>--output_file</argument>
     <argument>src/main/js/myapp/deps.js</argument>
    </arguments>
   </configuration>
  </execution>
  <execution>
   <id>compile-javascript</id>
   <phase>prepare-package</phase>
   <goals>
    <goal>exec</goal>
   </goals>
   <configuration>
    <executable>python</executable>
    <arguments>
     <argument>src/main/js/closure-library/closure/bin/build/closurebuilder.py</argument>
     <argument>--root</argument>
     <argument>src/main/js/closure-library</argument>
     <argument>--root</argument>
     <argument>src/main/js/myapp</argument>
     <argument>--output_mode</argument>
     <argument>compiled</argument>
     <argument>--compiler_jar</argument>
     <argument>src/lib/closure-compiler-20120430.jar</argument>
     <!-- WHITESPACE_ONLY, SIMPLE_OPTIMIZATIONS, ADVANCED_OPTIMIZATIONS -->
     <argument>--compiler_flags=--compilation_level=ADVANCED_OPTIMIZATIONS</argument>
     <!-- QUIET, DEFAULT, VERBOSE -->
     <argument>--compiler_flags=--warning_level=VERBOSE</argument>
     <argument>--namespace</argument>
     <argument>myapp</argument>
     <argument>--output_file</argument>
     <argument>${project.build.directory}/replaced-webapp/scripts/myapp.min.js</argument>
    </arguments>
   </configuration>
  </execution>
 </executions>
</plugin>

I've included the portion that calculates dependencies (from my previous post). Note that use of src/lib/closure-compiler-yyyymmdd.jar. You can download it from here, and extract the compiler.jar file. We just renamed it so we can track which version we're using. And in case of updates, just update the pom.xml.

Note also that we invoke the compiler during the prepare-package phase. This means that when you run jetty:run, the compiler does not get invoked. This makes it faster (and easier to repeat). But when you need to run/debug with the compiled/minified form (with advanced optimization), you run jetty:run-war.

The astute reader would notice that the minified file is currently not being referenced in any web page. This shall be explained in the next section.

Replacing Tags in Web Pages

Since the output of the compiler was one single JS file, we needed to modify our web pages (e.g. JSPs) to refer to it (and not the multiple files). Obviously, we could manually modify the JSP files. But we needed something that would automatically replace them during the build. We decided to use maven-replacer-plugin.

<plugin>
  <groupId>com.google.code.maven-replacer-plugin</groupId>
  <artifactId>maven-replacer-plugin</artifactId>
  <version>1.4.1</version>
  <executions>
    <execution>
      <phase>prepare-package</phase>
      <goals>
        <goal>replace</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <ignoreMissingFile>true</ignoreMissingFile>
    <file>src/main/webapp/index.jsp</file>
    <outputFile>${project.build.directory}/replaced-webapp/index.jsp</outputFile>
    <regex>true</regex>
    <regexFlags>
      <regexFlag>MULTILINE</regexFlag>
      <regexFlag>DOTALL</regexFlag>
    </regexFlags>
    <replacements>
      <replacement>
        <token><!-- SCRIPTS.*END SCRIPTS --></token>
        <value><script src="<c:url value='/scripts/myapp.min.js' />" ></script></value>
      </replacement>
    </replacements>
  </configuration>
</plugin>

This effectively replaces the following:

<!-- SCRIPTS -->
<script src="/closure-library/closure/goog/base.js"></script>
<script src="/myapp/deps.js"></script>
<script src="/myapp/myapp.js"></script>
<!-- END SCRIPTS -->
<script>init();</script><!-- our JS code's entry point -->

with

<script src="<c:url value='/scripts/myapp.min.js' />"></script>
<script>init();</script><!-- our JS code's entry point -->

Note that the minified JS file is written to target/replaced-webapp/scripts. But this folder is never used to build the WAR. So, we need to modify our pom.xml to include this folder when building the WAR file.

<plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-war-plugin</artifactId>
 <configuration>
  <webResources>
   <resource>
    <directory>src/main/webapp</directory>
    <filtering>true</filtering>
   </resource>
   <resource>
    <directory>target/replaced-webapp</directory>
    <filtering>true</filtering>
    <includes>
     <include>**/*.jsp</include>
    </includes>
   </resource>
  </webResources>
 </configuration>
</plugin>

The above configuration effectively adds the target/replaced-webapp directory on to the WAR file. This adds a scripts folder with a minified JS file in it. It also contains the modified index.jsp file. When the web page references /scripts/myapp.min.js, it will find it.

Conclusion

It is great that we're able to debug in raw form. This provides an easier way to find which JS file/line is causing the error. We are also able to debug in minified form, and deploy the minified form. I hope this helps.

Sunday, November 4, 2012

Integrating Google Closure Tools with Maven Builds

We wanted to use Google Closure Tools (compiler, plus the library) in our Maven-based project. The current approaches posted here and here were very good. But didn't quite satisfy our needs. As our JavaScript code base grew in size, and in complexity, we needed to run/debug our web pages with the JavaScript code in their raw form, apart from the minified/compiled mode. So, here's how we did it.

Project Structure (Maven's Standard Directory Layout)

Our project structure is as follows:

  • src/main/java
  • src/main/js
  • src/main/js/closure-library
  • src/main/js/myapp
  • src/main/resources
  • src/test/java
  • src/test/resources
  • src/main/webapp

It follows the standard directory layout. We placed our JavaScript (JS) code under src/main/js. We also placed the Closure Library underneath it. This made it easy to expose as additional web app root directories via the Jetty Maven Plugin. As will be explained later, this was crucial in being able to run the our JS code in raw form, which required the inclusion of base.js and our app's deps.js.

In our Subversion code repository, we linked to the Closure Library's code repository. A similar approach can be applied using git-svn mirror when using Git.

$ svn propget svn:externals
src/main/js/closure-library
http://closure-library.googlecode.com/svn/trunk/

This external link provided src/main/js/closure-library/closure/goog/base.js to be part of our project structure.

Running/Debugging with Closure JavaScript in Raw Form

To have quick code-run-debug cycles, we couldn't afford to compile the JS code every time. So, we had to find a way to run our web pages with the following JS script tags.

<html>
<head>...</head>
<body>
<script src="closure-library/closure/goog/base.js"></script>
<script src="myapp/deps.js"></script>
<script src="myapp/main.js"></script>
<script>init();</script><!-- our JS code's entry point -->
</body>
</html>

To make this possible, we needed to expose the JS files on our servlet container. This was made easier by having our JS code and Closure Library under one folder.

<plugin>
  <groupId>org.mortbay.jetty</groupId>
  <artifactId>jetty-maven-plugin</artifactId>
  <configuration>
    <webAppConfig>
      <contextPath>/</contextPath>
      <baseResource 
          implementation="org.eclipse.jetty.util.resource.ResourceCollection">
        <resourcesAsCSV>src/main/webapp,src/main/js</resourcesAsCSV>
      </baseResource>
    </webAppConfig>
  </configuration>
</plugin>

Calculating Dependencies

Our JS code was not just one file. It had more than a dozen files. Just like the ones in the Closure Library, each class was placed in one file. Each had goog.provide and goog.require calls. For our JS classes to work, we needed to generate a dependency file. The Closure Library comes with its pre-generated deps.js file for its classes. We'll need one for our classes. We used the Python script (that comes with the Closure Library), closure/bin/build/depswriter.py. We chose to invoke it during the process-sources phase of the build. This made it easier when running the jetty:run command, as the dependency file will be re-generated before the servlet container is up and running.

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <executions>
    <execution>
      <id>calculate-dependencies</id>
      <phase>process-sources</phase>
      <goals>
        <goal>exec</goal>
      </goals>
      <configuration>
        <executable>python</executable>
        <arguments>
          <argument>src/main/js/closure-library/closure/bin/build/depswriter.py</argument>
          <argument>--root_with_prefix</argument>
          <!-- prefix is relative to location of closure-library/closure/goog/base.js -->
          <argument>src/main/js/myapp/ ../../../myapp/</argument>
          <argument>--output_file</argument>
          <argument>src/main/js/maypp/deps.js</argument>
        </arguments>
      </configuration>
    </execution>
  </executions>
</plugin>

If there are no changes to the dependencies, you can modify your JS code, and just refresh/reload the web page, and the JS code is also reloaded. This provides a very fast and convenient code-run-debug cycle.

The resulting HTML should have loaded the JS files in the correct order. It should look something like this when viewed using your browser's debug mode (e.g. Firebug, or Webkit's Web Inspector).

<script src="/closure-library/closure/goog/base.js"></script>
<script type="text/javascript" src="http://localhost:8080/closure-library/closure/goog/deps.js"></script>
<script src="/myapp/deps.js"></script>
<script src="/myapp/main.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/disposable/idisposable.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/disposable/disposable.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/debug/error.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/string/string.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/asserts/asserts.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/array/array.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/../../../myapp/finder.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/../../../myapp/model.js"></script>
<script type="text/javascript">init();</script><!-- our JS code's entry point -->

It's interesting to point out how the Closure Library adds its deps.js file, and how it adds the dependencies (e.g. idisposable.js, error.js, string.js). The dependencies are due to the JS code requiring these, and the deps.js file adds in the corresponding JS <script> tags. Also note how our app's JS files are referenced — relative to the location of base.js. Look at lines 17-20. This is why we had to use the --root_with_prefix argument when calling depswriter.py. Otherwise, our JS code wouldn't even be loaded.

I'll post another follow-up on this topic where I'll explain how we compile, minify, and test the minified JS code.

Thursday, May 24, 2012

Rappler.com Map of Corona Assets

After looking at Rappler.com's Map of Chief Justice Corona Assets, I thought I'd use this opportunity to build something using Google Maps and Visualization APIs.

Corona Assets Map (opens page in new window)

Tuesday, May 15, 2012

Decorating Error Pages with SiteMesh 2.4.x

I've encountered a problem when decorating error pages with SiteMesh 2.4.2. I thought that others may benefit from the fix that we've done. So, I'm explaining it here.

When using Jetty 7.x, and the following web.xml configuration, SiteMesh does not correctly decorate the HTTP 404 error page.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    version="2.5">

    <!-- Sitemesh filter -->
    <filter>
        <filter-name>sitemeshFilter</filter-name>
        <filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>sitemeshFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>

    <error-page>
        <error-code>404</error-code>
        <location>/WEB-INF/pages/not-found.jsp</location>
    </error-page>

</web-app>

I'm not sure if this problem occurs in Tomcat 6.x. From my tests, when a URL to a non-existent page is used (e.g. http://localhost/…/xxx), a 404 page is expected. This should be decorated by SiteMesh. But it's not.

After further investigation, when the SiteMesh filter configuration is limited to *.jsp (see below, line 12), all non-existing URLs that do not match *.jsp do get decorated. So, xxx does get decorated (as expected). But xxx.jsp does not! This inconsistency led to some work-arounds that included using "flash"-scoped variables and a HttpServletResponse#sendRedirect() call to redirect to the 404 page.

<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>

    <!-- Sitemesh filter -->
    <filter>
        <filter-name>sitemeshFilter</filter-name>
        <filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>sitemeshFilter</filter-name>
        <url-pattern>*.jsp</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>

    <error-page>
        <error-code>404</error-code>
        <location>/WEB-INF/pages/not-found.jsp</location>
    </error-page>

</web-app>

This led me to look further into SiteMesh 2.4.2 code. After adding some debugging statements, I can see that the wrapped response was not used by Jetty in its HttpServletResponse#sendError() implementation. Thus, there was no content in SiteMesh's PageResponseWrapper. It was written directly to the original response, and not the wrapped response. So, in SiteMesh's point of view, there was no content to be decorated. But there is!

Given this finding, I looked for work-arounds. The result was some minor modification to the SiteMeshFilter and the following web.xml configuration.

<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>

    <!-- Sitemesh filter -->
    <filter>
        <filter-name>sitemeshFilter</filter-name>
        <filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class>
    </filter>
    <filter>
        <filter-name>errorSitemeshFilter</filter-name>
        <filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>errorSitemeshFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>
    <filter-mapping>
        <filter-name>sitemeshFilter</filter-name>
        <url-pattern>*.jsp</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

    <error-page>
        <error-code>404</error-code>
        <location>/WEB-INF/pages/not-found.jsp</location>
    </error-page>

</web-app>

The changes to the SiteMeshFilter allowed multiple instances of the filter to co-exist and act separately. One instance handles ERROR pages (due to sendError() calls). The other instance handles non-error pages. I used the filter's name to differentiate the two instances. When a non-existent URL is encountered, the filter that handles ERROR pages decorates properly. When a non-existent URL that matches *.jsp is encountered, the "sitemeshFilter" gets to filter it. But since it is not existent, the servlet container ends up calling sendError(404). As expected, the "sitemeshFilter" could not handle this properly. The good thing is, we have another "errorSitemeshFilter" that handles this.

public class SiteMeshFilter implements Filter {

    private FilterConfig filterConfig;
    private ContainerTweaks containerTweaks;
    private static final String ALREADY_APPLIED_KEY = "com.opensymphony.sitemesh.APPLIED_ONCE";
    private String alreadyAppliedKey;

    public void init(FilterConfig filterConfig) {
        this.filterConfig = filterConfig;
        alreadyAppliedKey = ALREADY_APPLIED_KEY + "-" + this.filterConfig.getFilterName();
        containerTweaks = new ContainerTweaks();
    }

    public void destroy() {
        filterConfig = null;
        containerTweaks = null;
    }

    /**
     * Main method of the Filter.
     *
     * Checks if the Filter has been applied this request. If not, parses the page
     * and applies {@link com.opensymphony.module.sitemesh.Decorator} (if found).
     */
    public void doFilter(ServletRequest rq, ServletResponse rs, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) rq;
        HttpServletResponse response = (HttpServletResponse) rs;
        ServletContext servletContext = filterConfig.getServletContext();

        // no changes
        . . .

        try {

            Content content = obtainContent(contentProcessor, webAppContext, request, response, chain);

            if (content == null) {
                return;
            }

            Decorator decorator = decoratorSelector.selectDecorator(content, webAppContext);
            decorator.render(content, webAppContext);

        } catch (IllegalStateException e) {
            // Some containers (such as WebLogic) throw an IllegalStateException when an error page is served.
            // It may be ok to ignore this. However, for safety it is propegated if possible.
            if (!containerTweaks.shouldIgnoreIllegalStateExceptionOnErrorPage()) {
                throw e;
            }
        } catch (RuntimeException e) {
            if (containerTweaks.shouldLogUnhandledExceptions()) {
                // Some containers (such as Tomcat 4) swallow RuntimeExceptions in filters.
                servletContext.log("Unhandled exception occurred whilst decorating page", e);
            }
            throw e;
        } catch (ServletException e) {
            request.removeAttribute(alreadyAppliedKey);
            throw e;
        }

    }

    // no changes
    . . .

    private boolean filterAlreadyAppliedForRequest(HttpServletRequest request) {
        if (request.getAttribute(alreadyAppliedKey) == Boolean.TRUE) {
            return true;
        } else {
            request.setAttribute(alreadyAppliedKey, Boolean.TRUE);
            return false;
        }
    }

}

This should work for error pages other than 404.

That's all. I hope this helps. Let me know if it works for you.

UPDATE: This is related to http://jira.opensymphony.com/browse/SIM-168.

UPDATE (2013 July): Thanks to Jeremy for pointing out the use of alreadyAppliedKey instead of ALREADY_APPLIED_KEY in the doFilter() method.

Sunday, May 6, 2012

MVCObject using Closure Library

I've recently been spending more time with JavaScript. I thought I'd make something like google.maps.MVCObject (from Google Maps JavaScript API) as a way for me to learn more about JavaScript and the Closure Library. So, here's what I've got, so far.

Understanding MVCObject

First off, here's how MVCObject behaves (at least, this is my understanding).

  1. When a property 'foo' of objA is bound to a property 'foo' of objB, the following happens:
    • Setting value x as property 'foo' on objB, results to objA.get('foo') returning the same value x.
    • Calling objA.get('foo') is equivalent to calling objB.get('foo'). In fact, objA.get('foo') returns the same object returned by objB.get('foo').
    • Setting value x as property 'foo' on objA is actually setting value x as property 'foo' on objB.
    • Calling objB.set('foo', x) notifies objA (and all objects bound to its 'foo' property) about 'foo_changed'. This effectively makes listening to objB's 'foo_changed' event equivalent to listening to objA's 'foo_changed' event.
  2. When a property 'bar' of objA is bound to a property 'foo' of objB, and property 'foo' of objB is bound to a property 'foo' of objC, the following happens:
    • Calling objA.set('bar') is equivalent to calling objB.set('foo'), which, in turn, is equivalent to calling objC.set('foo')
    • Calling objA.get('bar') is equivalent to calling objB.get('foo'), which, in turn, is equivalent to calling objC.get('foo')
    • ...You get the point.

This binding technique can save memory and provide better performance, since the object (or property value) is not duplicated. When bounded, Objects A and B actually point to the same value for their property 'foo'. So, if the object value happens to be a large object with several properties, then there's significant memory to be saved (since they point to the same object, and it is not duplicated).

MVCObject Tests

Second, here's how I started with some unit tests to help guide me in writing the MVCObject class with the Closure Library.

goog.require('goog.events');
goog.require('goog.testing.jsunit');
goog.require('example.MVCObject');

function testSetAndGet() {
  var objA = new example.MVCObject();
  objA.set('foo', 2);
  assertEquals(2, objA.get('foo'));
  assertEquals(2, objA.foo);
}

function testBindObjectAToObjectB() {
  var objA = new example.MVCObject();
  var objB = new example.MVCObject();
  objA.bindTo('foo', objB);
  objB.set('foo', 2);
  assertEquals(2, objA.get('foo'));
  assertEquals(objB.get('foo'), objA.get('foo'));
}

function testDispatchesChangedEventWhenPropertyIsSet() {
  var objA = new example.MVCObject();
  var called = false;
  goog.events.listenOnce(objA, 'foo_changed', function(event) {
    called = true;
  });
  objA.set('foo', 2);
  assertTrue(called);
}

function testUnbind() {
  var objA = new example.MVCObject();
  var objB = new example.MVCObject();
  objA.bindTo('foo', objB);
  objB.set('foo', 2);
  objA.unbind('foo');
  objB.set('foo', 3);
  assertEquals(2, objA.get('foo'));
}

MVCObject Implementation

Lastly, here's my implementation of MVCObject.

goog.provide('example.MVCObject');


goog.require('goog.events');
goog.require('goog.events.EventTarget');


/**
 * Base class implementing KVO (key-value object).
 *
 * @constructor
 * @extends {goog.events.EventTarget}
 */
example.MVCObject = function() {
  // Call super constructor
  goog.events.EventTarget.call(this);
};
goog.inherits(example.MVCObject, goog.events.EventTarget);


/**
 * @type {string}
 * @private
 */
example.MVCObject.EVENT_SUFFIX_ = '_changed';


/**
 * Returns the value of a property.
 *
 * @param {string} key the name of the property.
 * @return {*} the value of a property.
 */
example.MVCObject.prototype.get = function(key) {
  var binding = this.getBindings_()[key];
  if (binding) {
    // Use binding to get bounded object and return its value.
    return binding.target.get(binding.targetKey);
  } else {
    return this[key];
  }
};


/**
 * Sets a property.
 *
 * @param {string} key the name of the property.
 * @param {*} value the new value of the property.
 */
example.MVCObject.prototype.set = function(key, value) {
  var bindings = this.getBindings_();
  if (key in bindings) {
    // Use bindings[key] to get bounded object and set its value.
    var binding = bindings[key];
    binding.target.set(binding.targetKey, value);
  } else {
    this[key] = value;
    this.notify(key);
  }
};


/**
 * Sets a collection of key-value pairs.
 * @param {Object} values a collection of key-value pairs.
 */
example.MVCObject.prototype.setValues = function(values) {
  for (var key in values) {
    this.set(key, values[key]);
  }
};


/**
 * Lazily initialize bindings map.
 * @return {Object} the bindings map.
 * @private
 */
example.MVCObject.prototype.getBindings_ = function() {
  if (!this.bindings_) {
    this.bindings_ = {};
  }
  return this.bindings_;
};


/**
 * Adds a binding between the given property and a target property.
 *
 * @param {string} key the name of the property.
 * @param {example.MVCObject} target the target object.
 * @param {string} targetKey the name of the property on the target object.
 * @param {boolean=} opt_noNotify optional flag to indicate that no
 *     *_changed call-back shall be called.
 * @private
 */
example.MVCObject.prototype.addBinding_ = function(
    key, target, targetKey, opt_noNotify) {
  this.getBindings_()[key] = {
    target: target,
    targetKey: targetKey
  };
  if (!opt_noNotify) {
    this.notify(key);
  }
};


/**
 * Lazily initialize listeners map.
 * @return {Object} the listeners map.
 * @private
 */
example.MVCObject.prototype.getListeners_ = function() {
  if (!this.listeners_) {
    this.listeners_ = {};
  }
  return this.listeners_;
};


/**
 * Binds the property identified by 'key' to the specified target.
 *
 * @param {string} key the name of the property to be bound.
 * @param {example.MVCObject} target the object to bind to.
 * @param {string=} opt_targetKey the optional name of the property on the
 *     target, if different from the name of the property on the observer.
 * @param {boolean=} opt_noNotify optional flag to indicate that *_changed
 *     call-back shall not be called upon binding.
 */
example.MVCObject.prototype.bindTo = function(
    key, target, opt_targetKey, opt_noNotify) {
  opt_targetKey = opt_targetKey || key;
  this.unbind(key);
  this.getListeners_()[key] =
    goog.events.listen(
        target, opt_targetKey + example.MVCObject.EVENT_SUFFIX_,
        function() { this.notify(key); }, undefined, this);
  this.addBinding_(key, target, opt_targetKey, opt_noNotify);
};


/**
 * Removes a binding of a property from its current target. Un-binding
 * will set the unbound property to the current value. The object will
 * not be notified, as the value has not changed.
 *
 * @param {string} key the name of the property to be un-bound.
 */
example.MVCObject.prototype.unbind = function(key) {
  var listeners = this.getListeners_();
  var listenerKey = listeners[key];
  if (listenerKey) {
    goog.events.unlistenByKey(listenerKey);
    delete listeners[key];
    this[key] = this.get(key);
    delete this.getBindings_()[key];
  }
};


/**
 * Removes all bindings.
 */
example.MVCObject.prototype.unbindAll = function() {
  var listeners = this.getListeners_();
  for (key in listeners) {
    this.unbind(key);
  }
};


/**
 * Notify all observers of a change on this property. This notifies both
 * objects that are bound to the object's property as well as the object
 * that it is bound to.
 *
 * @param {string} key the name of the property that was changed.
 */
example.MVCObject.prototype.notify = function(key) {
  this.changed(key);
  // Dispatch *_changed event to all listeners
  this.dispatchEvent(key + example.MVCObject.EVENT_SUFFIX_);
};


/**
 * Generic handler for state changes. Override this in derived classes to
 * handle arbitrary state changes.
 *
 * @param {string} key the name of the property that changed.
 */
example.MVCObject.prototype.changed = function(key) {
};


/**
 * @override
 */
example.MVCObject.prototype.disposeInternal = function() {
  example.MVCObject.superClass_.disposeInternal.call(this);
  this.unbindAll();
};

I find Closure's way of declaring classes and methods to be very close to Java. It's event management makes user-defined custom events, and DOM/browser events, the same. It also provides a JsUnit-based testing framework. It's very rich in features.

It's making me think (or re-think) if there is value in having all these features under one roof. I mean, when I'm considering building a single-page app, I'd go look at backbone.js, which uses underscore.js. This may also mean using jQuery for DOM manipulation. I find that most (if not all) of the features of these three (3) JavaScript libraries can be found in Google's Closure Library (and possibly more). Will there be significant code savings when using one common JavaScript library?

All in all, I'm liking Closure. I'll spend more time learning it.