|
 |
 |
 |
 |
 |
 |
Using Barracuda in an Enhydra super-servlet application |
 |
 |
 |
There
are a number of ways to take advantage of Barracuda in existing
Enhydra super-servlet/presentation object applications. These are
examined in the following document. (Note that techniques 2 and 4
listed below are used in the
BarracudaPODiscRack sample application in CVS if you learn better
by example.)
- Using a Separate Servlet
- Using Components and Forms Only
- Using full Barracuda without shared Enhydra session data
- Using full Barracuda with shared session data
Using a Separate Servlet If your environment and
clients can work with more than one base URL for the same
application, then you can put all your Barracuda specific code in
a separate servlet. The advantages of this are that you can use
the war format, the existing Barracuda Ant build setup and of
course, all the features of Barracuda. The disadvantages are that
the application is spread across multiple URLs, it is more
difficult to share data between the Enhydra PO and the Barracuda
servlet and you may have class loader problems if you need to use
the same jar files across each of the servlets.
Assuming
you have an existing PO application called MyFoo consisting of 3
po's (Foo1.po, Foo2.po, Foo3.po) and you want to migrate Foo3.po
to use Barracuda. The basic technique is as follows:
Create a new WAR app called MyFoo2
Make sure Barracuda is on your classpath (you could just
drop the barracuda-core.jar into the /WEB-INF/lib directory for
MyFoo2)
Create a servlet called Foo3.java that extends
ApplicationGateway and register this servlet in the web.xml file
so that you can execute it from a web browser (i.e.
http://blah.com/MyFoo2/Foo3 or something like that)
Within your servlet do whatever you need to to handle the
request and generate a page. Follow the normal Barracuda
procedure as per Bconfig.
Change any references to Foo3.po to instead refer to
/MyFoo2/Foo3
Using Components and Forms Only It is
possible to use Barracuda's component and form models inside an
existing PO instead of the lower level XMLC. The advantages of
this approach are that you use the new component and form models
while still using your existing PO knowledge and code as well as
taking advantage of the higher level of functionality offered by
Barracuda (imagine, no more coding cloned template rows for
tables!). The disadvantage is that you cannot use the event
models ability to decompose a system into events and event
handlers, making the system more modular and reusable. A simple
(components only, form not shown) example (from Login.java in
BarracudaPODiscRack) is as follows (refer to that file for the
complete implementation) :
/** *
display page using Barracuda * *
@param context, the error messages * @return html
document */ public XMLObject
showPageUsingBarracuda( StateMap context ) { LoginHTML
page
= (LoginHTML)myComms.xmlcFactory.create(LoginHTML.class);
//create
a template component and render it Node node =
page.getDocument().getElementById("LoginForm"); BTemplate
templateComp = new
Btemplate( new LoginModel(context), new
DefaultTemplateView(node));
try
{ templateComp.render( new
DefaultViewContext(new ViewCapabilities())); }
catch ( RenderException re ) { this.writeDebugMsg("Render
err:"+re); }
return
page; }
/** *
LoginModel */ class
LoginModel extends AbstractTemplateModel { LoginForm
fm = null; ValidationException
ve = null;
//constructor
(extract form, exception from context) public
LoginModel(StateMap context) { fm
= (LoginForm) context.getState(LOGIN_FORM); ve
= (ValidationException)
context.getState(LOGIN_ERR); }
//register
the model by name public
String getName() { return
"Login"; }
//provide
items by key public
Object getItem(String key, ViewContext vc) { if
( key.equals("Username") ) { return(fm!=null
? fm.getStringVal(LoginForm.USER,
"") : ""); }
else if ( key.equals("Password") )
{ return(fm!=null
? fm.getStringVal(LoginForm.PASSWORD,
"") : ""); }
else if ( key.equals("Errors") ) { if
( ve==null ) return ""; List
errlist = ve.getExceptionList(); StringBuffer
sb = new
StringBuffer(errlist.size()>1 ? "There
were several errors:" : ""); Iterator
it = errlist.iterator(); while
( it.hasNext() ) { sb.append("
"+ ((ValidationException)it.next()).getMessage()); } return
sb.toString(); }
else return super.getItem(key, vc); } }
Using full Barracuda without shared Enhydra session
data If you want to take
advantage of everything Barracuda has to offer, but you don't
want to run a separate servlet then you proceed as for case 1
above, except that you do not need to create a separate war app.
Simply create a new servlet class in your existing project that
extends from ApplicationGateway as you would for a standalone
Barracuda application. Now instead of the servlet container
loading and initialising an instance of this class, we need to
get Enhydra to do it. The best place to make this happen is in
the startup() method of your StandardApplication derived class as
follows (note that I also initialise a string containing the
event extension for later use as this can be modified from the
default '.event'):
private
MyAppGateway myGateway; private String
myGatewayExtension;
/* * Start
the application. * See StandardApplication for
more details. * @param appConfig
* Application configuration object.
* @exception ApplicationException * If
an error occurs starting the application.
*/ public void startup(Config appConfig) throws
ApplicationException { super.startup(appConfig); //
Here is where you would read application-specific settings //
from
your config file.
//
setup the gateway to barracuda try
{ // use the standard
config from multiserver/enhydra ServletConfig
servletConfig
= this.getHttpPresentationManager().getServlet().getServletConfig(); this.myGateway
= new MyAppGateway(); this.myGatewayExtension
= this.myGateway.getEventExtension(); this.myGateway.init(
servletConfig ); } catch (
ServletException ex ) { throw
( new
ApplicationException( "Error
initialising MyAppGateway", ex ) ); } }
You
also need to make sure that your Enhydra application can process
URL's that end with the event extension. Fortunately, this is
easy to do using a hook that the kind Enhydra engineers provided
for us in the Application class - servletRequestPreprocessor()
which "is called before the request/response pair is processed by
the HttpPresentationServlet. It gives the application a chance to
work with the raw request/response pair before they are wrapped
by HttpPresentationRequest and HttpPresentationResponse objects".
To use it you need to add the following method to your
StandardApplication derived class :
public
boolean
servletRequestPreprocessor( Servlet
servlet, ServletContext
context, HttpServletRequest
request, HttpServletResponse
response) throws ServletException, IOException
{ String
target = request.getRequestURI(); //
if has the event extension in the URI //then
treat it as a barracuda event if
( target.indexOf(this.myGatewayExtension) > -1 )
{ this.myGateway.handleDefault(
request, response ); return
true; } return
false; }
That's it - you can now use the full Barracuda power in your
application!
Note that the preceding example assumes
that you are NOT using the application assembler and will manually
specify the required event gateways in your application gateway
class' initializeLocal() method. To use the application assembler
refer to the full BarracudaPODiscRack code (specifically
DiscRack.java startup() ) where the equivalent to the web.xml
init-param values are specified in your Enhydra applications'
conf file using the following format
:
InitGateway.ApplicationAssembler=org.barracudamvc.experimental.assembler.DefaultApplicationAssembler
InitGateway.AssemblyDescriptor=/ex4.xml
InitGateway.SAXParser=org.apache.xerces.parsers.SAXParser
Using full Barracuda with
shared session data To
gain access to the session data maintained by the Enhydra
super-servlet for each PO in addition to using all of Barracudas'
functionality you basically precede as for the previous approach.
The difference is that you need to hook into the Enhydra
application object slightly differently in order to be passed the
HttpPresentationComms object, which is the key to all of the
Enhydra specific session data (among other things). Instead of
overriding the servletRequestPreprocessor() method you override
the requestPreprocessor() method which is the "request
preprocessing method. All requests for URLs associated with this
application go through this function. It may do any preprocessing
desired, including handling the request or generating a page
redirect. It can be used to force login, allocate session objects,
restrict access, etc'. It behaves in much the same way as the
previously mentioned servletRequestPreprocessor(), checking for
the event extension in the URI and processing it if required. The
variation is that it calls a different function in the
ApplicationGateway - handleDefaultExt() which takes an additional
parameter - Object contextObj - a generic object to put into the
context prior to calling the event broker. We pass the
HttpPresentationComms object into this function for later use by
our event listeners. In addition, in order to make sure that
Enhydra recognizes an event url as the equivalent of a PO and
associates a session with it, we need to override the
ensureSession() method of StandardApplication. After all this
your application class has the following functions in addition to
the startup() as in 3 above :
/**
* Default method used by requestPreprocessor * to
ensure that a session exists and initialize the *
session and sessionData fields * in the
HttpPresentationComms object. * New sessions are
only created on requests to presentation *
objects, not requests for other types of files (such as gifs).
* This avoids allocating multiple sessions when a browser
makes * multiple requests for HREFs. If the
session already exist, it * is alwasy set in
comms
* * This normally looks up the session using a
session key from * a cookie. *
* @param comms * Object containing
request, response and redirect objects. *
@exception ApplicationException * If
an error occurs setting up the session.
*/ protected void
ensureSession(HttpPresentationComms comms) throws
ApplicationException { try
{ boolean isOkForSession
= presentationManager.isPresentationRequest(comms.request)
|| comms.request.getRequestURI().indexOf( this.myGatewayExtension)
> -1; if
( isOkForSession ) { comms.session
= StandardAppUtil.getRequestSession(comms); } if
( comms.session != null &&
sessionManager.sessionExists(comms.session.getSessionKey()) )
{ /*
* InitializeNewSession() sets up comms.sessionData, so
* that it may be used if an application defines it's own
* version of that method (after calling the super
method, * it
has a normal comms object to work with).
* Therefore, if we did not call initializeNewSession(),
* we have to initialize comms.sessionData here.
*/ comms.sessionData
= comms.session.getSessionData(); }
else { if
( isOkForSession )
{ initializeNewSession(comms); } } }
catch ( HttpPresentationException except ) { throw
new ApplicationException(except); }
catch ( SessionException except ) { throw
new ApplicationException(except); } } public
boolean requestPreprocessor(HttpPresentationComms comms) throws
Exception{ boolean retVal =
super.requestPreprocessor(comms); if (
!retVal ) { String target
= comms.request.getRequestURI(); //
if has the event extension in the URI //
then treat it as a barracuda event if
( target.indexOf(this.myGatewayExtension) > -1 )
{ this.myGateway.handleDefaultExt( comms.request.getHttpServletRequest(), comms.response.getHttpServletResponse(), comms
); retVal =
true; } } return
retVal; }
In order to use the HttpPresentationComms object in your event
handlers there is one additional, but vital step that needs to be
completed - the object needs to be extracted from the context
passed to the event handler. This is done in BarracudaPODiscRack
using the initUsingContext() method provided by the common base
class BaseDiscRackEventGateway. Note that every listener must
call this function prior to using any function that access the
HttpPresentationComms object. This is illustrated below:
/**
* RenderLoginHandler -
*/
class RenderLoginHandler extends DefaultBaseEventListener {
public void handleViewEvent(ViewEventContext context)
throws EventException, ServletException, IOException {
// MUST DO FIRST - initialize things from the context
initUsingContext( context );
//unpack the necessary entities from the context
HttpServletRequest req = context.getRequest();
//get the XMLC object
LoginHTML page =
(LoginHTML) getComms().xmlcFactory.create(LoginHTML.class);
//create a template component and render it
if ( logger.isDebugEnabled() )
logger.debug("Creating template component");
Node node = page.getDocument().getElementById("LoginForm");
BTemplate templateComp =
new BTemplate(new LoginModel(context),
new DefaultTemplateView(node));
try {
templateComp.render(new DefaultViewContext(context));
} catch ( RenderException re ) {
logger.warn("Render err:"+re);
}
//now actually render the page (the DOMWriter
//will take care of actually setting the content type)
if ( logger.isDebugEnabled() ) logger.debug("Rendering document");
new DefaultDOMWriter().write(page, context.getResponse());
}
}
/**
* Initialise the Enhydra PO session data from the event context
*
* @param EventContext
*/
protected void initUsingContext( EventContext context )
throws EventException {
try {
this.myComms = (HttpPresentationComms)
context.getState( ApplicationGateway.EXTERNAL_CONTEXT_OBJ_NAME );
this.initSessionData(this.myComms);
} catch ( ClassCastException ex ) {
String errMsg =
"ERROR: External Context object is NOT
a HttpPresentationComms object. Cannot function";
throw ( new EventException( errMsg, ex ) );
} catch ( DiscRackPresentationException ex ) {
String errMsg =
"ERROR: Could not get session data from session:";
throw ( new EventException( errMsg, ex ) );
}
}
A small note on classpaths Make
sure that you add the Barracuda JAR to your applications
classpath as configured in its configuration file, not by
passing it to java. If you don't do this, you could have
problems with events being loaded by different class
loaders, and thus not being recognised as valid events by
Barracuda when fired. For example:
Server.ClassPath[]
= "/Barracuda-core.jar"
There you have it - it is (relatively) easy to use Barracuda in
an Enhydra PO and as is shown in the BarracudaPODiscRack example
it is also possible to share the session data between PO's and
Barracuda screens at runtime.
Enjoy!
|
|
 |
 |
 |
 |
 |
 |
For all the latest information on
Barracuda, please refer to http://barracudamvc.org
Questions, comments, feedback? Let
us know... |
|