The problem
There's is a good old application from the days when struts still had to be invented and framesets weren't considered evil. The problems associated with a FRAMESET are another topic, the fact is the application owner wants to get rid of the FRAMSET and asked me to look for a solution that would be unobtrusive and easy to implement, understand and maintain.
Current Situation
The application uses no web-app framework, each Use Case is implemented in a separate servlet, all of them neatly mapped in the web.xml. The FRAMESET setup is classic: apart from the static elements, the navigation happens in a 'body' and a 'menu' frame. The normal flow is to select a command from the menu frame, e.g. Place Order results in (<a href="/placeorder" target="'body"/>), then submit a few forms in the body frame (<form action="/placeorder" method="POST">...</form>). Without frameset, when a HTTP request for /placeorder reaches the application, we want the servlet response decorated with the other fragments: menu, header and the rest. The differences are essential: there is just one request/response and all page fragments have to share the same HEAD, CSS etc.
Introducing Apache Tiles 2
The good news is that version 2.x of Apache Tiles, which has started as struts extension, is now framework-independent. It offers support for JSP, Freemarker, and can be used directly from a servlet or filter. First of all, a template is needed to replace the frameset, e.g. main.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%> <html> <head> <link href="/online/theme/online.css" rel="stylesheet" type="text/css"> <script language="javascript" src="/online/js/algemeen.js"></script> </head> <body> <table border="1" cellspacing="0" cellpadding="0" style="width: 100%; height: 100%"> <tr> <td colspan="2"><tiles:insertAttribute name="top" /></td> </tr> <tr> <td><tiles:insertAttribute name="menu" /></td> <td><tiles:insertAttribute name="body" /></td> </tr> <tr> <td colspan="2"><tiles:insertAttribute name="bottom" /></td> </tr> </table> </body> </html>Based on that template a tile definition can render a decorated view of any servlet output (e.g. /placeorder). A definition per servlet is needed in tiles.xml:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN" "http://tiles.apache.org/dtds/tiles-config_2_0.dtd"> <tiles-definitions> <definition name="placeorder.tile" template="/layout/main.jsp"> <put-attribute name="top" value="/algemeen/onlinetop.html" /> <put-attribute name="menu" value="/besturing/menu.jsp" /> <put-attribute name="body" value="/placeorder" /> <put-attribute name="bottom" value="/algemeen/onlinebottom.html" /> </definition> </tiles-definitions>This definition still has to be rendered, e.g. from another JSP, let's call it placeholder_tiled.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %> <tiles:insertDefinition name="placeorder.tile" />
A separate tile definition and a JSP page for each servlet would be too much code repetition. Let's see how to the output of any servlet.
A View decorating servlet
I like the idea of having separate URLs for the servlet output and for a composite view of the same output. Thanks to Tiles 2 API, one servlet could provide decorated views of the output of all servlets. In tiles.xml, I added a tile definition with all attributes except body defined:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN" "http://tiles.apache.org/dtds/tiles-config_2_0.dtd"> <tiles-definitions> <definition name="main.tile" template="/layout/main.jsp"> <put-attribute name="top" value="/algemeen/onlinetop.html" /> <put-attribute name="menu" value="/besturing/menu.jsp" /> <!-- name="body" not defined --> <put-attribute name="bottom" value="/algemeen/onlinebottom.html" /> </definition> </tiles-definitions>
Let's define a servlet that will fill in the missing body attribute for each URL ending with .tiled:
<servlet> <display-name>TilesServlet</display-name> <servlet-name>TilesServlet</servlet-name> <servlet-class>com.assenkolov.common.web.TilesServlet</servlet-class> <init-param> <param-name>template.name</param-name><param-value>main.tile</param-value></init-param> <init-param> <param-name>attribute.name</param-name><param-value>body</param-value></init-param> </servlet> <servlet-mapping> <servlet-name>TilesServlet</servlet-name> <url-pattern>*.tiled</url-pattern> </servlet-mapping>
Finally, the servlet itself:
public class TilesServlet extends HttpServlet { private String templateName; private String attributeName; @Override public void init() throws ServletException { super.init(); templateName = getInitParameter("template.name"); attributeName = getInitParameter("attribute.name"); } @Override public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext servletContext = request.getSession().getServletContext(); TilesContainer container = ServletUtil.getContainer(servletContext); AttributeContext attributeContext = container.startContext(request, response); // Remove the ".tiled" suffix from the request path String requestPath = request.getServletPath().replace(".tiled", ""); Attribute attr = new Attribute(requestPath); Mapattrs = new HashMap (); attrs.put(attributeName, attr); attributeContext.addAll(attrs); container.render(templateName, request, response); container.endContext(request, response); } }
This servlet always renders the same tiles definition, providing an appropriate body. No separate tiles definition and JSP for each view.
What's next?
All the code above only provides an easy template-decorated view for each servlet output. There is sill a lot of work that has to be done manually:- move all separate styles, scripts etc. from the separate pages to the template
- requestDispacher.forward in a servlet closes the output stream while it is still needed for the tiles template; all forwards must become includes;
- all navigation javascript referring to top.menu ot top.body has to be refatored manually.