java-sblayout
This package contains Java libraries that make my life a bit easier while developing Java web applications. I have observed that for many web applications that I have developed in the past, all pages all more or less look and behave in a quite similar way.
As a consequence, I have found myself writing lots of boiler plate code that had to be repeated for each additional page I implement. Furthermore, it also turned maintenance of pages into quite a tedious problem.
The libraries in this package allow someone to define a web application as a set of pages that refer to other sub pages. Developers only have to capture the common aspects, such as the sections and style of the entire web application, once and only need to provide the individual characteristics of every additional sub page.
The libraries automatically compose the corresponding pages, and ensures a number of non-functional quality attributes, such as a mechanism allowing end users to always know where they are in the navigation structure of the application.
Moreover, it also automatically hides sub pages in the menu sections that are not accessible.
Building the libraries
The libraries in this package can be built using
Apache Ant. The model package resides in the
LayoutModel/
folder and can be built by running:
$ cd LayoutModel
$ ant generate.library.jar
After Ant has finished building, a JAR file named LayoutModel.jar
has been
generated.
The view package resides in the LayoutView/
folder and can be built by
running:
$ cd LayoutView
$ ant generate library.jar
After Ant has completed the above task, a JAR file named LayoutView.jar
has
been generated.
Usage
The libraries can be used in a straight forward way. To get a web application working we have to remember three things.
First, we must create an object instance of the Application
class that serves
as the model of the application -- it captures common properties such as the
sections, style settings, and all the sub pages of the application.
An application model can be displayed by creating a view page that includes
taglibs displaying an index page or sections of an index page. This package
contains a trivial index taglib index.tld
that generates a page from an
application model.
Finally, we always use one single address (invoking the view page) that handles all requests to every sub page. The path components that are appended to its URL serve as selectors for the sub pages of the application. For example:
http://localhost/index.wss
refers to the entry page of the web applicationhttp://localhost/index.wss/a
refers to a sub page reachable from the entry pagehttp://localhost/index.wss/a/b
refers to a sub page reachable from the previous sub page
Implementing a very trivial web application
To create a very trivial web application displaying one page, we must first create an index servlet composing a simple application model and dispatches a view page:
package test;
import io.github.svanderburg.layout.model.*;
import io.github.svanderburg.layout.model.page.*;
import io.github.svanderburg.layout.model.page.content.*;
import io.github.svanderburg.layout.model.section.*;
public class IndexServlet extends io.github.svanderburg.layout.view.IndexServlet
{
private static final Application application = new Application(
/* Title */
"Trivial web application",
/* CSS stylesheets */
new String[] { "default.css" },
/* Pages */
new StaticContentPage("Fruit", new Contents("fruit.jsp"))
)
/* Sections */
.addSection("header", new StaticSection("header.jsp"))
.addSection("contents", new ContentsSection(true))
.addSection("footer", new StaticSection("footer.jsp"));
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
dispatchLayoutView(application, req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
dispatchLayoutView(application, req, resp);
}
}
In the above code fragment, we compose an application model in which every sub
page consists of three sections. The header
and footer
always display the
same code fragment. The contents
section is filled with variable text that
makes every page unique.
Every sub page has Trivial web application
in the title and use the style
settings from the default.css
stylesheet.
The view page should reside in WEB-INF/index.jsp
. A simple variant can be
written as follows:
<%@ page language="java"
contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"
import="io.github.svanderburg.layout.model.*,io.github.svanderburg.layout.model.page.*, test.*"
trimDirectiveWhitespaces="true"
%>
<%
Application app = (Application)request.getAttribute("app");
Route route = (Route)request.getAttribute("route");
%>
<%@ taglib uri="http://svanderburg.github.io" prefix="layout" %>
<layout:index app="<%= app %>" route="<%= route %>" />
The above code fragment retrieves the application model and requested page. Then
it includes the index
taglib to compose a page with a trivial structure from
it.
Each section in the model translates to div
elements with their id
attribute
set to their corresponding array key in the model).
We need the create the following web.xml
file to make sure that the
IndexServlet
can be used and that the error pages are mapped accordingly:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>index</servlet-name>
<servlet-class>test.IndexServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>indexjsp</servlet-name>
<jsp-file>/WEB-INF/index.jsp</jsp-file>
</servlet>
<servlet-mapping>
<servlet-name>index</servlet-name>
<url-pattern>/index.wss/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.wss</welcome-file>
</welcome-file-list>
<error-page>
<error-code>403</error-code>
<location>/index.wss/403</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/index.wss/404</location>
</error-page>
</web-app>
After creating the model and view, we must implement the code for the static sections and sub pages. The above model expects a WAR file with the following directory structure that looks as follows:
styles/
default.css
WEB-INF/
classes/
test/
IndexServlet.class
lib/
LayoutModel.jar
LayoutView.jar
sections/
header.jsp
footer.jsp
contents/
fruit.jsp
index.jsp
web.xml
The files to which the StaticSection
objects refer should reside in
WEB-INF/sections/
, stylesheets should reside in styles/
and the contents of every sub
page should reside in WEB-INF/contents/
.
Besides the pages and stylesheets, we must also bundle the libraries and the compiled servlet class. To conveniently compose a WAR file, we can use the following Apache Ant recipe as a template:
<project name="Fruit" basedir="." default="generate.war">
<property name="build.dir" value="build" />
<property name="deploybuild.dir" value="${build.dir}/classes" />
<!-- Imports environment variables as properties -->
<property environment="env" />
<condition property="TOMCAT_LIB" value="${env.TOMCAT_LIB}" else="../apache-tomcat">
<isset property="env.TOMCAT_LIB" />
</condition>
<condition property="LAYOUT_MODEL_LIB" value="${env.LAYOUT_MODEL_LIB}" else="../LayoutModel">
<isset property="env.LAYOUT_MODEL_LIB" />
</condition>
<condition property="LAYOUT_VIEW_LIB" value="${env.LAYOUT_VIEW_LIB}" else="../LayoutView">
<isset property="env.LAYOUT_VIEW_LIB" />
</condition>
<target name="copy.libraries">
<copy toDir="${basedir}/WebContent/WEB-INF/lib">
<fileset dir="${LAYOUT_MODEL_LIB}" includes="*.jar" />
</copy>
<copy toDir="${basedir}/WebContent/WEB-INF/lib">
<fileset dir="${LAYOUT_VIEW_LIB}" includes="*.jar" />
</copy>
</target>
<!-- Sets the classpath which is used by the Java compiler -->
<path id="service.classpath">
<fileset dir="${TOMCAT_LIB}">
<include name="*.jar" />
</fileset>
<fileset dir="${LAYOUT_MODEL_LIB}">
<include name="*.jar" />
</fileset>
<fileset dir="${LAYOUT_VIEW_LIB}">
<include name="*.jar" />
</fileset>
</path>
<target name="compile" depends="copy.libraries">
<mkdir dir="${deploybuild.dir}" />
<javac debug="on"
fork="true"
destdir="${deploybuild.dir}"
srcdir="${basedir}/src"
classpathref="service.classpath"/>
</target>
<target name="generate.war" depends="compile">
<war destfile="Fruit.war" basedir="${basedir}">
<classes dir="build/classes" includes="**" />
<fileset dir="WebContent" includes="**" />
</war>
</target>
<target name="clean">
<delete file="${basedir}/Fruit.war" />
<delete>
<fileset dir="${basedir}/WebContent/WEB-INF/lib" includes="*.jar" />
</delete>
<delete dir="${deploybuild.dir}" quiet="true" />
<delete dir="${build.dir}" quiet="true" />
</target>
</project>
The above Ant file defines several targets:
-
copy.libraries
copies the libraries to the working directory so that they can be found during building and packaged into the WAR file. TheLAYOUT_MODEL_LIB
andLAYOUT_VIEW_LIB
environment variables can be used to configure their exact locations -
The
compile
target is responsible for compiling all Java source files -
The
generate.war
target is the default target and assembles a Web application ARchive (WAR) from all artifacts required to run the web application. -
The
clean
target removes all artifacts that are produced by the build targets.
Running the following command-line instruction:
$ ant generate.war
produces the WAR file that we can deploy to a Servlet container, such as Apache Tomcat.
Implementing a web application with sub pages
We can adapt the page parameter (in the application model shown earlier) to refer to a collection of sub pages by adding an additional parameter to the constructor. Each element in the array represents a sub page displaying a specific kind of fruit:
/* Pages */
new StaticContentPage("Fruit", new Contents("fruit.jsp"))
.addSubPage("apples", new StaticContentPage("Apples", new Contents("fruit/apples.jsp")))
.addSubPage("pears", new StaticContentPage("Pears", new Contents("fruit/pears.jsp")))
.addSubPage("oranges", new StaticContentPage("Oranges", new Contents("fruit/oranges.jsp"))),
By adding a menu section, we can automatically show a menu section on every page that displays links to their sub pages and marks the link that is currently selected as such. Do that we must change the section operations to include a menu section:
/* Sections */
.addSection("header", new StaticSection("header.jsp"))
.addSection("menu", new MenuSection(0))
.addSection("contents", new ContentsSection(true))
.addSection("footer", new StaticSection("footer.jsp"));
We must also add a couple of additional files that display the contents of each sub page:
WEB-INF/
contents/
fruit/
apples.jsp
pears.jsp
oranges.jsp
After making these modifications, each page shows a menu section that displays the fruit kinds. Clicking on a link will redirect us to the page displaying it.
Moreover, the URL component that comes after index.wss
also allows us to
navigate to every fruit flavour. For example, the following URL redirects us to
the oranges sub page: http://localhost/index.wss/oranges
Implementing more complex navigation structures
It is also possible to have multiple levels of sub pages. For example, we can
also add sub pages to sub pages and an additional menu section (submenu
)
displaying the available sub sub pages per sub page:
/* Sections */
.addSection("header", new StaticSection("header.jsp"))
.addSection("menu", new MenuSection(0))
.addSection("submenu", new MenuSection(1))
.addSection("contents", new ContentsSection(true))
.addSection("footer", new StaticSection("footer.jsp"));
and modify the sub page to include sub sub pages:
/* Pages */
new StaticContentPage("Fruit", new Contents("fruit.jsp"))
.addSubPage("apples", new StaticContentPage("Apples", new Contents("fruit/apples.jsp"))
.addSubPage("red", StaticContentPage("Red", new Contents("fruit/apples/red.jsp")))
.addSubPage("green", StaticContentPage("Green", new Contents("fruit/apples/green.jsp"))))
.addSubPage("pears", new StaticContentPage("Pears", new Contents("fruit/pears.jsp"))
.addSubPage("yellow", StaticContentPage("Yellow", new Contents("fruit/pears/yellow.jsp")))
.addSubPage("green", StaticContentPage("Green", new Contents("fruit/pears/green.jsp"))))
.addSubPage("oranges", new StaticContentPage("Oranges", new Contents("fruit/oranges.jsp"))
.addSubPage("orange", StaticContentPage("Orange", new Contents("fruit/oranges/orange.jsp")))
.addSubPage("yellow", StaticContentPage("Yellow", new Contents("fruit/oranges/yellow.jsp")))),
Similar to the previous example, a submenu
section displays the sub pages of a
particular fruit kind.
We can also use the URL to get to a specific sub sub page. For example, the
following URL shows the red apple sub sub page:
http://localhost/index.wss/apples/red
.
You can nest sub pages as deep as you want, but for the sake of usability this is not recommended in most cases.
Creating compound sections
As explained in the first example, sections normally translate to div
elements inside the body
element. For the implementation of more advanced
layouts, it may also be desired to nest div
s.
It is also possible to nest sections inside CompoundSection
objects to
generate nested div
s:
/* Sections */
.addSection("header", new StaticSection("header.jsp"))
.addSection("menu", new MenuSection(0))
.addSection("container", new CompoundSection()
.addSection("submenu", new MenuSection(1))
.addSection("contents", new ContentsSection(true)))
.addSection("footer", new StaticSection("footer.jsp"));
In the above example, we have added a compound section named: container
.
Inside the container
we have embedded the submenu
and contents
sections.
The above organization can be useful to, for example, vertically position the
header
, menu
, container
and footer
sections and horizontally align the
submenu
and contents
sections. The CSS properties of the container
section
can be used to change the positioning.
Error pages
It may also happen that some error occurs while trying to display a page. For
example, trying to access a sub page that does not exists (e.g.
http://localhost/index.wss/oranges/purple
) should display a 404 error page.
Moreover, pages that are inaccessible should display a 403 error page and pages
that fail to process input parameters should return a 400 error page.
These error pages can be defined by adding them as a sub page to the entry page
with keys 400
, 403
and 404
:
/* Pages */
new StaticContentPage("Fruit", new Contents("fruit.jsp"))
.addSubPage("400", new HiddenStaticContentPage("Bad request", new Contents("error/400.jsp")))
.addSubPage("403", new HiddenStaticContentPage("Forbidden", new Contents("error/403.jsp")))
.addSubPage("404", new HiddenStaticContentPage("Page not found", new Contents("error/404.jsp")))
...
Security handling
If it is desired to secure a page from unauthorized access, you can implement
your own class that inherits from Page
which overrides the
checkAccessibility()
method. This function should return true
if and only if
an end user is authorized to view it.
For example, the following class implements a page displaying content that denies access to everyone:
package test;
import io.github.svanderburg.layout.model.page.*;
import io.github.svanderburg.layout.model.page.content.*;
public class InaccessibleContentPage extends ContentPage
{
public InaccessibleContentPage(String title, Contents contents)
{
super(title, contents);
}
@Override
public boolean checkAccessibility()
{
return false;
}
}
You can do in the body of checkAccessibility()
whatever you want. For example,
you can also change it to take some cookie values containing a username and
password that gets verified against something that is stored in a database.
By adding an object that is in instance of our custom class to a sub page of the entry page, we can secure it.
Implementing more complex dynamic layouts
We can also support more complex dynamic layouts. In our previous example with fruit kinds, we only defined one content section in which details about the fruit kind is displayed.
We can also change the application model to have two dynamic content sections
(or even more). By removing the default string parameter to the Contents
constructor and using fluent interfaces, we can specify the contents of each
content section of page.
The following model makes the header as well as the contents sections dynamic for each sub page with the following sections:
/* Sections */
.addSection("header", new StaticSection("header.jsp"))
.addSection("menu", new MenuSection(0))
.addSection("contents", new ContentsSection(true))
.addSection("footer", new StaticSection("footer.jsp"));
To make more than one section dynamic, we can define the pages as follows:
/* Pages */
new StaticContentPage("Fruit", new Contents()
.addSection("header", "fruit.jsp")
.addSection("contents", "fruit.jsp"))
.addSubPage("apples", new StaticContentPage("Apples", new Contents()
.addSection("header", "fruit/apples.jsp")
.addSection("contents", "fruit/apples.jsp")))
.addSubPage("pears", new StaticContentPage("Pears", new Contents()
.addSection("header", "fruit/pears.jsp")
.addSection("contents", "fruit/pears.jsp")))
.addSubPage("oranges", new StaticContentPage("Oranges", new Contents()
.addSection("header", "fruit/oranges.jsp")
.addSection("contents", "fruit/oranges.jsp")))
The above model also requires a few additional files that should reside in the
header
subdirectory inside WEB-INF
:
WEB-INF/
header/
fruit.jsp
fruit/
apples.jsp
pears.jsp
oranges.jsp
The above files should display the header for each fruit kind.
Rendering custom menu links
By default, MenuSection
s are automatically populated with hyperlinks only
containing page titles. This kind of presentation is often flexible enough,
because hyperlinks can be styled in all kinds of interesting ways with CSS.
In some occasions, it may also be desirable to present a link to a page in a
completely different way. A custom renderer can be specified with an additional
parameter to the constructor of a Page
object:
new StaticContentPage("Apple", new Contents("apple.jsp"), "applemenuitem.jsp")
In the above code fragement, the last parameter (the menuItem
parameter)
specifies the JSP or Servlet that decides how it should be rendered in a
MenuSection
.
We can use the custom renderer file (applemenuitem.jsp
) to present the menu
link in a different way, such as an item that includes an icon:
<%@ page language="java"
import="io.github.svanderburg.layout.model.page.*"
trimDirectiveWhitespaces="true"
%>
<span>
<%
boolean active = (Boolean)request.getAttribute("active");
String url = (String)request.getAttribute("url");
Page subPage = (Page)request.getAttribute("subPage");
if(active)
{
%>
<a class="active" href="<%= url %>">
<img src="<%= getServletContext().getContextPath() %>/image/menu/apple.png" alt="Apple icon">
<strong><%= subPage.getTitle() %></strong>
</a>
<%
}
else
{
%>
<a href="<%= url %>">
<img src="<%= getServletContext().getContextPath() %>/image/menu/apple.png" alt="Apple icon">
<%= subPage.getTitle() %>
</a>
<%
}
%>
</span>
Every included page that renders a menu item accepts three parameters: active
indicates whether the link is active, url
contains the URL of the link and
subPage
is the sub page that the link refers to.
In the above code fragment, each hyperlink embeds an apple icon. When the menu item link is active, the text is also emphasized.
The file shown above should reside in the menuitems/
folder of a project.
Handling GET or POST parameters
Sometimes it may also be required to process GET or POST parameters, if a sub page (for example) contains a form.
The contents object can also take a controller parameter that invokes a page that is processed before any HTML output is rendered:
/* Pages */
new StaticContentPage("Fruit", new Contents("fruit.jsp"))
...
.addSubPage("question", new StaticContentPage("Question", new Contents("question.jsp", "/question.wss"))),
...
)
The above code fragment adds a sub page that displays a form asking the user a question what his/her favorite fruit kind is. After a user submits his answer through the form the same page is displayed. Instead of showing the form the answer is displayed.
The page provided as second parameter to the Contents
constructor takes
care of processing the POST parameter. It can both be a Servlet or a JSP page.
In addition to processing the input parameters, controllers can also throw
exceptions that are instances of the PageException
class. For example, you
may want to check the provided user input for the presence of an answer.
If none was provided, you may want to throw a BadRequestException
with a
message that explains to the user that a mandatory input parameter is missing.
Since it is not possible to throw any other exceptions than a ServletException
or IOException
from a servlet, an error must be propagated by adding an ex
request attribute referring to the exception that needs to be thrown:
req.setAttribute("ex", new BadRequestException("A parameter was invalid!"));
Using path components as parameters
Instead of using the path components in a URL to address sub pages, we may also
want to use path components as parameters instead. To use path components as
parameters, we can use objects that are instance of DynamicContentPage
.
The following code fragments adds a sub page having a sub page that interprets a path component:
/* Pages */
new StaticContentPage("Fruit", new Contents("fruit.jsp"))
...
.addSubPage("fruitname", new DynamicContentPage("Display fruit name", "fruitname", new Contents("fruitname.jsp")))
...
)
The first parameter of the constructor contains the title, the second the name of the variable that will be set when the sub page is processed, and the third parameter configures the content sections that are supposed to interpret the variable.
We can implement the fruitname.jsp
to simply display the parameter:
<%@ page import="java.util.*"
trimDirectiveWhitespaces="true"
%>
<%
HashMap<String, String> query = (HashMap<String, String>)request.getAttribute("query");
%>
<%= query.get("fruitname") %>
If we address the page with: http://localhost/index.wss/fruitname/apples
we
should see:
apples
and if we address the page with: http://localhost/index.wss/fruitname/bananas
we should see:
bananas
The DynamicContentPage
constructor also has an optional fourth parameter to
define additional sub pages or to interpret multiple parameters.
Implementing an internationalised web application
Another use case is implementing internationalised web applications. By creating
a page that is an instance of a LocalizedContentPage
we can easily support
the same page in multiple languages:
/* Pages */
new LocalizedContentPage()
.addSubPage("nl", new StaticContentPage("Nederlands", new Contents("nl.jsp")))
.addSubPage("en-us", new StaticContentPage("American", new Contents("en-us.jsp")))
.addSubPage("en-gb", new StaticContentPage("British", new Contents("en-gb.jsp")))
.addSubPage("fr", new StaticContentPage("Français", new Contents("fr.jsp")))
.addSubPage("de", new StaticContentPage("Deutsch", new Contents("de.jsp")))
The above code fragment defines a page with translations into Dutch, American, British, French and German.
Any user can retrieve a particular translation of a page (such as German) by using the following URL:
http://localhost/index.wss/de
If the root of this URL is used:
http://localhost/index.wss
Then the preferred language will be derived from the Accept-Language
parameter
in the HTTP header that is sent by the user agent.
If a particular variant of language is not supported (e.g. the Belgian variant of
Dutch: nl-be
) then the detection algorithm will automatically do a fallback to
the generic variant: nl
.
If none of the preferred languages is supported, the first option in the array
will be taken (which is nl
in our example).
More use cases
There are also facilities to include application wide and per-page stylesheets
and script includes. We can also make pages invisible from menu sections by
instantiating pages that are prefixed with Hidden*
.
This framework also offers specialized features through the following taglibs:
- A site map can be generated with:
sitemap.tag
- Bread crumbs, that show the path to the currently displayed page, can be
generated with:
breadcrumbs.tag
- It is also possible to embed a menu section in a content page (rather than
declaring a menu section) with:
embeddedsitemap.tag
Consult the API documentation for more information.
Examples
This package includes three example web applications that can be found in the
examples/
folder:
- The
Simple
web application demonstrates simple sub pages, inaccessible sub pages, dynamic sub pages and a page handling POST requests - The
I18N
web application demonstrates an internationalised web page displaying the same page in multiple languages - The
Advanced
web applications demonstrates more advanced sub pages with multiple content sections. It also demonstrates style and script variability.
API documentation
This package includes API documentation, which can be generated with Javadoc. The
Ant file in both library projects package contain a target named doc
target and
produce the corresponding HTML files in a folder called doc/
:
$ ant doc
License
The contents of this package is available under the Apache Software License version 2.0