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)
Thoughts on software development and life in general
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)
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.
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.
First off, here's how MVCObject behaves (at least, this is my understanding).
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).
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')); }
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.