Facade - Tutorial
Author: Frederik Dahlke, dahlke@tivano.de
$Id: Tutorial.html,v 1.6 2002/05/15 12:24:49 dahlke Exp $
1. Quickstart
You need:
- JDK >= 1.3 (installed).
- A servlet engine (installed).
- Ant (installed).
- The Facade
demo application.
- You might want to read the
SiTE
documentation because we will be using SiTE in this tutorial.
(It is safe to start without reading SiTE docs.)
The Facade full distribution contains a JspViewHandler example.
1.1 Unpack demo application and install as web application
- Unpack the demo application whereever you want to put it.
- Goto the top level application directory.
- Run 'ant compile'.
- Install the 'webapp/' directory as a web application in your servlet
engine. Using Tomcat, create a Context in your server.xml file:
<Context path="/demo" docBase="/path/to/webapp" debug="0"
privileged="true" reloadable="true"/>
Note: your context path must not be set to 'facade'!
- Start the servlet engine.
- Access http://yourserver/demo/facade/en/Index.html . You should se a
webpage displaying time and date in english.
- Access http://yourserver/demo/facade/de/Index.html . You should se a
webpage displaying time and date in german.
1.2 What is happening?
1.2.1 Request processing
Take a look at your WEB-INF/web.xml. As you can see the class
de.tivano.facade.multiskin.MultiskinServlet is installed as a servlet and mapped
to the url /facade/*. The MultiskinServlet enables the use of multiple skins in
your webapplication, a german and an english one in the demo. Of course you could
also decide to create a skin for kids, one for adults for example. Or one
in your one CD and another one in the CD of your affiliate partner.
The MultiskinServlet maps the request URI to an event URI which is handled
by Facade. It looks for the first occurance of the servlet's name in the URI. The servlet's
name is "facade", as defined in web.xml. The path element following the servlet's name is
considered to be the skin's name (de or en in this case). The rest of the URI is the event
URI.
Example:
| URI: | /demo/facade/en/Index.html |
| skin name: | en |
| event URI: | Index.html |
The MultiskinServlet now fires the "Index.html" event using the ControlEventDispatcher.
The ControlEventDispatcher tries to find a Controller listening to "Index.html". The
event mappings (event listeners) are defined in "WEB-INF/sitefacade.properties". Open
this file now.
The following shows up at the top of the file:
# the packagename where the controllers are located
de.tivano.facade.framework.FacadeServlet.controllerPackage
= de.tivano.facadedemo.controller
This denotes that Facade will try to find all your controllers in the package
de.tivano.facadedemo.controller.
Contoller mappings and requirements are defined below:
# controller requirements
# a controller can require other controllers
# syntax: Controller.<controller class name>.requires
= <required controller class name>
# controller mappings
# syntax: ControllerMapping.<eventurl>.listeners = <listener class>, ...
# ControllerMapping.prefix/.listeners = my.controllers.PrefixController
# ControllerMapping.event/url.listeners = my.controllers.OneController
# ControllerMapping.*suffix.listeners = my.controllers.SuffixController
As you can see nothing is mapped to an event url and no controller requires another
controller. We do not need this right now and will get to it later.
The javadoc of the EventDispatcher class tells us:
The handler classes are called in the following order:
- Handlers mapped to event url prefix
- Handlers mapped to event url
- Default handlers
- Handlers mapped to event url suffix
As there is no controller mapped to the event URI prefix and no controller mapped
to the event URI "Index.html" Facade now tries to find the default controller (default handler).
The default controller class name is computed from the event url. The .html suffix is removed
and "Controller" is appended. So the default controller class name computed from the event url "Index.html"
is "IndexController". The controller package is "de.tivano.facadedemo.controller" so the FQN is
"de.tivano.facadedemo.controller.IndexController".
As there is no IndexController class and there is no controller mapped to the ".html" suffix no
controller is called and the request is passed to the ViewEventDispatcher.
Note: If you want to make sure that a request is handled by a controller you can define
an explicit mapping.
1.2.2 Response componsing
The MultiskinServlet fires the "Index.html" event using the ViewEventDispatcher. Let's take another
look at WEB-INF/sitefacade.properties:
# the packagename where the view handlers are located
de.tivano.facade.framework.FacadeServlet.viewHandlerPackage = de.tivano.facadedemo.view
# define fallback handlers for view handler classes
# syntax: ViewHandler.<view handler class name>.fallback = <fallback handler class name>
# view handler mappings
# syntax: ViewHandlerMapping.<eventurl>.listeners = <listener class>, ...
# ViewHandlerMapping.prefix/.listeners = my.view.PrefixViewHandler
# ViewHandlerMapping.event/url.listeners = my.handler.OneViewHandler
# ViewHandlerMapping.*suffix.listeners = my.view.SuffixViewHandler
ViewHandlerMapping.*html.listeners = de.tivano.facade.multiskin.MultiskinViewHandler
There is no prefix or event URI mapping for "Index.html". Next, the ViewEventDispatcher tries to
find the default handler class using the FQN de.tivano.facadedemo.view.IndexViewHandler.
As there is such a class it will be called. Take a look at the source code in
src/de/tivano/facadedemo/view/IndexViewHandler.java .
IndexViewHandler extends MultiskinViewHandler. The MultiskinViewHandler extends
SiteViewHandler. The SiteViewHandler takes care of automactially loading the correct
template, filling the template with values and sending a response back to the client.
By extending the SiteViewHandler and overriding one method, one can concentrate on
providing the values which have to be inserted in the template.
The SiteViewHandler and hence the MultiskinViewHandler
provides two hooks for extending subclasses, the preProcess and postProcess methods.
We choose to override the preProcess method:
/**
* Hook for subclasses, is called before the template is processed.
*/
protected void preProcess(Node rootNode,
FacadeContext context, String eventUrl)
throws EventHandlerException {
Locale locale;
if (getSkinName(context).equals("de")) {
locale = Locale.GERMAN;
} else {
locale = Locale.ENGLISH;
}
DateFormat df = DateFormat.getDateTimeInstance(DateFormat.FULL,
DateFormat.FULL,
locale);
context.put("daytime", df.format(new Date()));
}
You can see what the code does. The only thing mentionable: all event handlers have access to
the FacadeContext, which is basically a HashMap. The SiteViewHandler looks for a value in the
context for every variable found in the template. If it finds something, it sets the contents to
the given value. As you can see the code above puts date and time using the correct
locale in the context, where the SiteViewHandler will find it.
Lets take a look at the template. The event URI is "Index.html" and the skin name is "en".
The SiteViewHandler hence parses the template "en/Index.html" found in the templates directory.
The template directory is configured in sitefacade.properties, relative to the web application root:
de.tivano.site.generic.ServletSourceRepository.BasePath=/sitetemplates
Use your browser and open the file webapp/sitetemplates/en/Index.html . As you can see it looks like
a normal HTML page except that the time and date are not set correctly. Note: every SiTE template
can be displayed in a browser and can be used as mockup (unlike JSP or other templates...). It
should be totally possible to use any nondestructive HTML authoring tool with SiTE templates.
Open the template's source code:
<title SNAME="daytime">Ho</title>
The title tag contains the special attribute SNAME (by the way, you can configure SiTE
to use any other attribute name, like ID or anything else, if you do not like SNAME, by
setting the property de.tivano.site.generic.SimpleTextParser.NameAttribute). SiTE's
ConfigurableHtmlParser recognizes any HTML tag containing the SNAME attribute
and hence the title tag.
The SiteViewHandler subsitutes the tag's
contents with the value stored in the FacadeContext using the key "daytime". This kind of
syntax works for any *ML tag, unless the tag contains other nested tags. If a *ML tag
contains nested tags, you will only have access to the *ML tag's attributes, not
it's contents. This makes it possible to process every HTML page, even if it is
not wellformed. You have to use the <!-- SITE SNAME="name" --> - syntax if
you need to mark areas containing serveral *ML tags. The
SiTE documentation explains this in detail.
Date and time: <!--SITE SNAME="daytime"-->25.04.2002 10:33<!--/SITE-->
The syntax above is another possibility of marking an area where text should be
replaced. The text between the two SITE comments is replaced. Of course you could also
rewrite this row:
Date and time: <span SNAME="daytime">25.04.2002 10:33</span>
The SiteViewHandler processes the template, inserts the "daytime" value found in
the context and sends the final HTML page back to the client.
2. Advanced use, Controllers and the SiteViewHandler
Let's say your marketing manger thinks showing the time to anybody without
knowing their name is very impersonal. He wants you to create a "personalized" version
of the time display page and query the name of every
user before displaying the time to them. Additionally, only names longer than
three characters should be accepted. (Marketing mangers always have ideas like
that.. ;-).
2.1 Page components: the use of the FACADEEVENT attribute
First we need a page that queries the user's name. The template templates/en/QueryName.html
does this. Open the file in your browser and then take a look at the source.
As you expected there is an input form. The interesting part of the page is the error message:
<!--SITE SNAME="username" FACADEEVENT="ShowOnError"-->
<p><font color="red">Your name must be longer
than three characters.</font></p>
<!--/SITE-->
If you define an additional FACADEEVENT attribute inside a SiTE-tag, the
value of the FACADEEVENT attribute is treated as an event URL. This event URL is
fired using the ViewEventDispatcher as soon as the SiteViewHandler processes the node.
In this case the event URL is "ShowOnError". Take a look at the
de.tivano.facadedemo.view.ShowOnErrorViewHandler
class which handles this event by default.
public void process(FacadeContext context, String eventUrl)
throws EventHandlerException {
Node parent = getParentNode(context);
Node node = getNode(context);
String nodeName = node.getName();
HashMap errorCodes =
(HashMap) context.get(ERROR_CODES_KEY);
if ((errorCodes == null) || (!errorCodes.containsKey(nodeName))) {
parent.removeNode(node);
}
}
A SiTE-template is represented as a
tree, containing a node for every tag with the SNAME-attribute.
The eventhandler itself can either access and use the node which triggered the event using the
getNode() method or load another template itself. The template manipulation
is SiTE-specific and has nothing to do with Facade. The ShowOnErrorViewHandler deletes
the node in the template, if no matching errorcode is present in the errorcodes map.
You can easily reuse the ShowOnErrorViewHandler by attaching it to arbitrary nodes
using the FACADEVENT attribute. This technique becomes very powerful as soon as you
start to think of list filling, automatic form filling, content syndication etc.. As long
as you use SiTE your view handler code (Java) is 100% sperated from presentation code (HTML in this example),
easing reuse of handler classes.
Using the SiteViewHandler this code could be reduced if the ShowOnErrorViewHandler
would be an extension of the MultiskinViewHandler and override preProcess(). You would then
put a Boolean value into the context and leave the rest up to the MultiskinViewHandler.
2.2 Using a controller
The target of the QueryName.html form is the PersonalTime.html page. Before we display
the page, we need to check if the user has entered a correct username. If not, the QueryName.html
page should be displayed again, showing an error message. Generally speaking, depending on the user's
input a different view has to be displayed. This is where a controller comes into play.
2.2.1 Controller mapping
First we map the controller on the "PersonalTime.html" event URI, making sure that every
request to "PersonalTime.html" is checked by the controller first:
# controller mappings
# syntax: ControllerMapping.<eventurl>.listeners = <listener class>, ...
# ControllerMapping.prefix/.listeners = my.controllers.PrefixController
# ControllerMapping.event/url.listeners = my.controllers.OneController
# ControllerMapping.*suffix.listeners = my.controllers.SuffixController
ControllerMapping.PersonalTime.listeners
= de.tivano.facadedemo.controller.TellYourNameController
If you plan to provide more than one "personalized" page it would be a good idea to move
all pages into a subdirectory and map the TellYouNameController to that subdirectory prefix.
Le'ts say all your personalized pages are in the "personal" subdirectory. So a request URL to a
personalized page would be something like "http://yourserver/demo/facade/en/personal/PersonalPage.html".
You then map the TellYourNameController to the "personal" prefix:
ControllerMapping.personal/.listeners
= de.tivano.facadedemo.controller.TellYourNameController
2.2.2 Current mapping limitations
- No wildcards allowed or regexps supported.
- Prefix mappings must start with the top level event URL pathelement.
- A prefix mapping must end with a slash (/).
- Facade assumes the suffixes are separated from the rest of the request URL by a dot (.) .
2.2.3 Controller implementation
Open the source code of de.tivano.facadedemo.controller.TellYourNameController. The controller implements
the EventHandler interface and hence implements a process method which is called by the
ControlEventDispatcher. There are two interesting things in the body of the process method:
- There is no need to call a view handler manually if the user is succesfully identified.
As the controller is mapped to the PersonalTime.html event, the appropriate view handler
will be called automatically. After all the process methods of all controllers are called,
the MultiskinServlet dispatches the "PersonalTime.html" event URI using the ViewEventDispatcher.
- The statement
context.getViewEventDispatcher().dispatch("QueryName.html");
shows how to dispatch a new view event. As a result the QueryName.html page will be
redisplayed to the user, showing error codes if the user has entered an illegal username.
public void process(FacadeContext context, String str)
throws EventHandlerException {
boolean firstTime = false;
// fetch session
HttpSession httpSession = context.getHttpServletRequest().getSession(false);
if (httpSession == null) {
firstTime = true;
httpSession = context.getHttpServletRequest().getSession(true);
}
String username = (String) httpSession.getAttribute(NAME_KEY);
if (username != null) {
// put username in context, it will be automatically displayed by
// the MutliskinViewHandler
context.put(NAME_KEY, username);
// ok, go ahed
return;
}
// unidentified user
username = context.getHttpServletRequest().getParameter(NAME_KEY);
if ((username != null) && (username.length() > 2)) {
// successfully validated username
httpSession.setAttribute(NAME_KEY, username);
// put username in context, it will be automatically displayed by
// the MutliskinViewHandler
context.put(NAME_KEY, username);
// ok, go ahed
return;
}
// display QueryName again and set error.
if (!firstTime) {
// errorCodes are used by the ShowOnErrorViewHandler
HashMap errorCodes = new HashMap();
errorCodes.put(NAME_KEY, "dummy");
context.put(ShowOnErrorViewHandler.ERROR_CODES_KEY, errorCodes);
}
context.getViewEventDispatcher().dispatch("QueryName.html");
}
2.3 ViewHandler mapping
There are two things left to do. First, we need a template for the PersonalTime.html page,
which additionally contains the username. You can find an example in the
"webapp/sitetemplate/en/" directory. Second we need to fill in the appropriate values.
As we already have the IndexViewHandler which places the correct time in the Facade
context, we can easily reuse it. The username is already put in the context by the
TellYourNameController. So all we need to do is to map the IndexViewHandler on the
"PersonalTime.html" event URL:
# view handler mappings
# syntax: ViewHandlerMapping.<eventurl>.listeners = <listener class>, ...
# ViewHandlerMapping.*suffix.listeners = my.view.SuffixViewHandler
# ViewHandlerMapping.prefix/.listeners = my.view.PrefixViewHandler
# ViewHandlerMapping.event/url.listeners = my.handler.OneViewHandler, my.Handler
ViewHandlerMapping.*html.listeners
= de.tivano.facade.multiskin.MultiskinViewHandler
ViewHandlerMapping.PersonalTime.listeners
= de.tivano.facadedemo.view.IndexViewHandler
2.4 Automatic form filling
In your web.xml change the Facade configuration file to "controllerex.properties" and
restart your webapplication. Access "http://yourserver/demo/facade/PersonalTime.html" and
input a username of no more than two characters. The result will be a redisplay of the
QueryName.html page showing the error message. Note: the input field still contains the username
you entered. How comes? You did not write any code to put it there. The answer: the SiteViewHandler
performs this automatically.
You can experiment with this feature. Open the QueryName.html page, extend the form
with some input fields at will and hit reload. As long as you input a username shorter than three characters
the QueryName.html page will be redisplayed. Read the javadocs of
de.tivano.facade.viewhandler.site.SiteFormViewHandler and
de.tivano.site.util.FormFiller for more information.
2.5 Automatic URL rewriting
As long as you use the SiteViewHandler all application local links are automatically
appended with the session id, if the user does not accept cookies.
3. Examples
Download the Facade source distribution.
The tests directory contains some examples making use of controller requirements and
fallback handlers for view handlers. The corresponding Facade configuration file is
in webapp/facade.properties.
4. Bugs
As this is an early version of Facade, there will be bugs. Please report them to
rt-facade-request@bugs.tivano.net.
[ t]ivano software gmbh www.tivano.de
|