July 21, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Struts in Action: Developing Applications with Tiles

  • April 16, 2003
  • By Manning Publications Co.
  • Send Email »
  • More Articles »
Extraction practices

Here are some caveats and practice notes regarding the extraction process. Since this will become a routine, it is important to have clear practices and to learn from past mistakes:

  • All custom tags used by the tile must be imported into the tile.
  • All custom tag elements must begin and end in the same tile.
  • Avoid carrying HTML elements over to another tile.
  • Mark up by contract.
  • Consider trade-offs.
  • Leverage technology.

All custom tags used by the tile must be imported into the tile. The tile will inherit HTML assets, like style sheets. It will not inherit references to JSP assets, like tag libraries. The web browser applies the style sheet when the page renders, but since each tile is in fact a stand-alone JSP servlet, it needs its own references to JSP resources.

All custom tag elements must begin and end in the same tile. If you use the <html:html> tag in your file, place the elements for this tag at the top and bottom of your layout (which is a single tile). This restriction does not apply to HTML elements. You can, for example, open the <BODY> element in a header tile and close the </BODY> element in a footer tile, but custom tags are validated when the tile is compiled. JSP tag elements must begin and end within the same tile.

Avoid carrying HTML elements over to another tile. In practice, you may decide that one tile should open an element, like a table; a second should provide the content, like the rows in a table; and a third should close the element. When this pattern is useful, you should use it but still try to begin and end as many elements as possible in the same tile. This makes finding markup errors much easier. Even if the middle tile only provides the rows for a table, it can do so as complete <TR>...</TR> rows.

Mark up by contract. When you decide to use something like Tiles, you are also deciding to treat markup like code. Accordingly, all the usual paradigms apply, like assertions and program by contract. When you design your tiles, think about the preconditions and postconditions for each tile, just as you would for a method in a class. Don't hesitate to document the conditions for each tile, just as you would for a method. A good approach is to just use standard JavaDoc conventions in a JSP comment, which avoids creating a new wheel:

<%--/** * Global page header, without automatic form select. * See headerForm.jsp for version with form select. * Imports global style sheet. * Opens HEAD, BODY, and TABLE. Another tile must close these. * @author Ted Husted * @license ASF 1.0 */--%>

For HTML tiles, just use HTML comment braces instead. Unlike JSP comments, HTML comments will be visible in the source of the page. You may wish to be brief and discreet when using HTML comments.

Consider trade-offs. Dissembling a page into tiles is much like normalizing a database. You can strip out absolutely all the redundant information, but if you do, some of the parts become so small that maintenance and performances issues can appear. In practice, you may have two or three varieties of header or footer files that may replicate markup. But if the markup changes, it is still easier to conform 3 files than 30 or 300. As with any programming task, the usual trade-offs apply.

Leverage technology. If you are also introducing style sheets and other markup changes at the same time, don't hesitate to create skeleton files and use editing macros with search-and-replace to automate some of the work. It's easy to forget how much time you can save with these old friends.

11.5.5 Extracting the tags into a Definition

After you have applied the process in section 11.5.3 to your starter page and have confirmed that it still works, you can finish the job by extracting the <tiles:insert> tags into an XML Definition. This is a four-step process:

  1. Move the page to the layouts folder.
  2. Rename the body tile.
  3. Convert the insert tag to a layout and Definition.
  4. Update the ActionForward.
Move the page to the layouts folder

Move the refactored page to the location you've chosen for layout tiles, under /tiles/layouts, for example. At this point, the refactored page should be a punch list of <tile:insert> tags, and the original content of the page should have been reduced to a set of tiles.

Rename the body tile

One of the extracted tiles probably represents the body, or "guts," of the original page. If so, consider renaming it as the original page you just moved. The implication is that the core content of the original page will still be where the original page stood. This helps to minimize change. If you need to edit the page's content, the content will still be where it always was. If you do this, update the <tiles:insert> tag after moving or renaming the file. Working from listing 11.16, we would change

<tiles:insert page="/tiles/view.jsp"/>

to

<tiles:insert page="/pages/view.jsp"/>
Convert the insert tag to a layout and Definition

Add a name property to each of the insert tags. This will often match the name of the JSP. The exception might be the tile representing the original body of your page. This will usually have a more generic name, like content.

Copy the <tiles:insert> statements from your refactored page into the starter tiles.xml configuration file that we set up at the beginning of this section and place them inside a <definition> element. If you used any <tiles:put> elements, you can promote those to top-level elements now:

<definition>  <tiles:insert put="title"     value ="Artimus - View Article"/>  <tiles:insert put="subtitle"  value ="View Article"/>  <tiles:insert name="header"   page="/tiles/header.jsp"/>  <tiles:insert name="message"  page="/tiles/message.jsp"/>  <tiles:insert name="content"  page="/pages/view.jsp"/>  <tiles:insert name="navbar"   page="/tiles/navbar.jsp"/></definition>

Then, rename the <tiles:insert> tags as <tiles:put> elements and the page attribute as a value attribute. To the <definition> element, add name and path properties. The path property should be to your layout page (see step 1). The name property can correspond to the name of your original page, but substitute dots for the slashes. Listing 11.19 shows our complete tiles.xml file.

Listing 11.19 A Tiles configuration file: /WEB-INF/conf/tiles.xml

<!DOCTYPE tiles-definitions PUBLIC    "-//Apache Software Foundation//DTD Tiles Configuration//EN"    "http://jakarta.apache.org/struts/dtds/tiles-config.dtd"><tiles-definitions>  <definition name=".article.view"              path="/pages/tiles/layouts/Base.jsp>    <tiles:put name="title"    value ="Artimus - View Article"/>    <tiles:put name="subtitle" value="View Article"/>    <tiles:put name="header"   value="/tiles/header.jsp"/>    <tiles:put name="message"  value="/tiles/message.jsp"/>    <tiles:put name="content"  value="/pages/view.jsp"/>    <tiles:put name="navbar"   value="/tiles/navbar.jsp"/>  </definition></tiles-definitions>

Now, in the layout page, change the <tiles:insert> tags to <tiles:get> tags and delete the page attribute (since that is now part of the Definition) Any <tiles:put> tags can be changed to <tiles:useAttribute> tags. Keep the name attribute, but delete the value attribute (since the value is part of the Definition now).

Listing 11.20 shows a complete layout JSP.

Listing 11.20 A Tiles layout page: /tiles/layouts/Base.jsp

<%@ taglib uri="/tags/tiles" prefix="tiles" %><tiles:useAttribute name="title"/><tiles:useAttribute name="subtitle"/><tiles:get name="header"><tiles:get name="message"/><tiles:get name="content"/><tiles:get name="navbar"/>
Update the ActionForward

Finally, replace the ActionForward that referred to the original JSP with a reference to the Tiles Definition:

<action        path="/article/View"        type="org.apache.scaffold.struts.ProcessAction"        parameter="org.apache.artimus.article.FindByArticle"        name="articleForm"        scope="request"        validate="false">       <forward            name="success"            path=".article.View"/></action>

As shown in figure 11.6, the Tiles ActionServlet will intercept the reference and apply the Definition to the layout page.

Figure 11.6 The Tiles ActionServlet uses the definition to help create the response.

If you were forwarding directly to the JSP before, you can use the standard Scaffold SuccessAction to route control through the controller so the Definition can be used to select and render the layout. You can use the Definition anywhere you were using a system path to the JSP. If there are enough references, you can even use the search-and-replace feature of your editor to change them all automatically.

At runtime, the Tiles ActionServlet will intercept the ActionForward and check its path against the Definitions in the Tiles configuration. If it finds a match, it includes each tile of the Definition in the response. The container then processes each included tile normally. The HTML tiles are rendered by the container's HTML service, and the JSP tiles are rendered by the container's JSP service.

If the ActionForward path is not the name of a Definition, the ActionServlet handles it as a normal URI, same as always.

NOTES     If your refactored page does not display the same as the original, first make sure that you have imported any needed tag libraries in the tiles. If the taglib is not included, the tag will not be rendered and will be ignored by the browser (and you will see it in the HTML page source). If that is not the problem, create a new page and start reassembling the page by hand to uncover the error. Most often, you will find that a tile broke its "API contract" by opening or closing an element improperly. Another API contract to check is the path for the input property of the ActionMapping. This should also point to the Definition name rather than the physical JSP page.

If you expose the Path must be absolute error after switching over to Tiles, it means that you've tried to use a Definition as the path for a forward but it was not found in the Tiles configuration. After checking for a Definition, Tiles passes the path up to the super class method, and Struts treats it like a system path. Our leading dot is being interpreted as a relative reference to a directory, hence the Path must be absolute advice. The bottom line is there is usually a typo in either the Struts or Tiles configuration file.

To test your changes, be sure to reload the application so that the current Struts and Tiles configurations are loaded into memory. If you've been following along, you can try this now.

Once you have gone through the process of refactoring a page with <tiles:insert> and then converting it to a Definition, you may wish to convert other pages directly to a Definition. To do this, you:

  • Copy an existing Definition using the same layout and give it a new name.
  • Clip out and save the content segment of the page you are refactoring.
  • Change the new Definition to refer to the segment you just saved and converted.
  • Test and repeat.
NOTE     When you first switch a block of pages over to Tiles, it may take a little extra time for the pages to render at first. This is because the JSPs for the new tiles are being created in addition to the usual one you need for the content. Once the JSP for each tile is created, it will not be re-created until it is changed, and everything goes back to normal. In the future, if you edit the content tile, only that one JSP will be recompiled.

11.5.6 Normalizing your base layout

Back in listing 11.20, we showed the layout file as a series of <tiles:insert> tags. If you like, you can also use regular HTML and JSP code on your layout page. This is a good place to put the topmost tags, like <HTML> or <html:html>, so that these do not need to be put inside other tiles, which might really be intended to do other things.

Listing 11.21 shows a revised base layout page that extracts the topmost elements from the header and navbar tiles and puts them on the base layout. We've also renamed it from Base.jsp to Article.jsp to indicate its role as the Article layout tile. Other pages in the application may need to use a different layout.

Listing 11.21 Revised layout tile (/tiles/layouts/Article.jsp)

<%@ taglib uri="/tags/tiles" prefix="tiles" %><%@ taglib uri="/tags/struts-html" prefix="html" %><html:html><HEAD><html:base/><LINK rel="stylesheet" type="text/css" href="<html:rewrite      forward='baseStyle'/>"><TITLE>Artimus - <bean:write name="title"/></TITLE></HEAD><tiles:useAttribute name="title"/><tiles:get name="header"/><tiles:get name="message"/><tiles:get name="content"/><tiles:get name="navbar"/></BODY></html:html>

11.5.7 Refining your Definitions into base and extended classes

As you commute your pages to layouts and Definitions, it's easy to end up with sets like this:

<definition name=".article.View" path="/tiles/layouts/Article.jsp">  <put name="title"       value="View Article" />  <put name="header"      value="/tiles/header.jsp" />  <put name="messages"    value="/tiles/messages.jsp" />  <put name="content"     value="/pages/articles/view.jsp" />  <put name="navbar"      value="/tiles/navbar.jsp" /></definition><definition name=".article.Result"            path="/tiles/layouts/Article.jsp">  <put name="title"       value="Search Result" />  <put name="header"      value="/tiles/header.jsp" />  <put name="messages"    value="/tiles/messages.jsp" />  <put name="content"     value="/pages/articles/result.jsp" />  <put name="navbar"      value="/tiles/navbar.jsp" /></definition>

If you look closely, you'll see that the first and third items differ but the others are the same. A better way to write them, and any like them, is to create a base Definition. The Tiles Definition supports an extends property, which lets a Definition inherit attributes and overload only what needs to be changed. Here, we extend .article.Base and overload title and content:

<definition name=".article.Base" path="/tiles/layouts/Article.jsp">    <put name="title" value="${title}"/>    <put name="header" value="/tiles/header.jsp"/>    <put name="message" value="/tiles/message.jsp"/>    <put name="content" value="${content}"/>    <put name="navbar" value="/tiles/navbar.jsp"/>  </definition><definition name=".article.View" extends=".article.Base">    <put name="title" value="View Article"/>    <put name="content" value="/pages/article/view.jsp"/>  </definition><definition name=".article.Result" extends=".article.Base">    <put name="title" value ="Article Search Result"/>    <put name="content" value="/pages/article/result.jsp"/>  </definition>

With the base Definition in place, we now have to supply only two lines for each of our subDefinitions. The other settings fall through and do not need to be specified. If there are attributes that will be used throughout your site, you can put those in a base Definition and extend everything else from that. Then, if any of the base attributes change, you need to make the change in only one place.

As a convention, we put markers in for the values of the first and third items (title and content) to indicate that these are extension points that subDefinitions need to override. If the base Definition were used directly, then these markers would just print out as literals. The ${} markers have no special meaning to Tiles.

Another convention shown here is to use an initial capital letter for the layout JSP but an initial lowercase letter for the tile JSPs. This is to indicate that the layout page can be called directly, because it is a fully formed JSP class. The tile pages are like methods invoked by the layout JSP, and so use the same naming convention as a method. But this is only a convention; any other consistent naming scheme would work just as well.

11.5.8 Developing a routine

After the first few pages, you should be able to develop a routine that goes something like this:

  1. Create a new Definition (in tag.xml), often by copying a similar one.
  2. Update the Definition with the path to the existing page, page title, and any other custom information.
  3. Open the existing page.
  4. Delete the top and bottom, leaving the core content and tag import statements.
  5. Review and revise the core content to ensure that the markup keeps its API contract with the tiles before and after it in the Definition. One tile may need to open an element, like a <TABLE>, and another tile may need to close it. Remove any unneeded tag import statements. Optionally, add a comment block.
  6. Update the paths in the Struts configuration (struts-config.xml) to reference the new Definition, including any input properties.
  7. Reload the tag configuration and the Struts configuration.
  8. Review the page.
  9. Rinse and repeat.

At first, you will probably start with pages as they appear in your application's flow. Once you have the procedure down, it is not difficult to step through the page tree and refactor each page in turn. This will ensure that you don't miss any. It may also uncover some obsolete pages left over from prior development efforts.

DEFINITIONS     A popular way to view a method's signature is as a contract between the method and its caller. The caller agrees to provide certain parameters and the method agrees to provide a certain result based on those parameters. Since an API is a collection of method signatures, the paradigm of seeing interactions between components as a binding agreement is generally referred to an API contract.

API is an acronym for application programming interface, any set of routines generally available for use by programmers. The operating system, for example, has APIs for a variety of disk/file-handling tasks. APIs are written to provide portable code. The programmer only has to worry about the call and its parameters and not the details of implementation, which may vary from system to system. [CKNOW]

11.5.9 Managing the migration

Moving an application over to Tiles is not difficult but neither is it trivial. Be sure to schedule enough time for the project. The first few pages may take several hours, but once the pattern is established, additional pages may take only a few minutes each.

If you also need to apply a new look to the site, it may be best to first convert it to Tiles and then apply the new design so that you are not doing two new things at once. Once the layout is migrated to Tiles, bringing up the new design will go more quickly.

A good time to schedule a migration is when you know a visual redesign is coming but don't have the new design in hand yet. If you can have the application migrated to Tiles while the redesign is being finalized, applying the changes will be a smoother process. Doing both at once is not recommended—especially for your first migration.

So what's the bottom line of a migration? Your mileage will vary, but a small application with 25 presentation pages consuming about 140,000 kbytes of markup code was migrated to 55 tiles of about 120,000 kbytes—the 15% difference being redundant markup that was removed.

Moving forward, new pages for the application can now be created much more quickly and will be more consistent with existing pages. To change the overall layout, you can edit the layout or a few individual tiles instead of every page on the site.





Page 8 of 9



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel