Barracuda DiscRack - Overview
barracuda.gif (11456 bytes)This document provides a brief overview of the Barracuda Disc Rack application.

Similarities & Differences

Barracuda Disc Rack is similar in many ways to the original Disc Rack application. This is intentional, to make it as easy as possible for developers already familiar with the Enhydra Base Presentation methodology to understand the Barracuda paradigm. Similarities include:

  • we maintained the basic biz, data, and presentation package separation
  • the classes in the biz and data layers are unchanged from the traditional DiscRack application
  • we were able to reuse much of the XMLC code that generated the actual HTML pages

Of course there are also differences:

  • we no longer use the Enhydra PO structure
    • we have instead adopted the Servlet 2.2 WAR format, with basic system definition occuring in the web.xml file
    • we use the Barracuda ApplicationGateway as the starting point to the system
    • we do still instantiate an Enhydra Application object behind the scenes, since the DODS generated data packages rely on this
  • the presentation package has been broken into 3 distinct subpackages:
    • events - events that define the actions this system is capable of responding to (NOTE: as of the upgrade to Barracuda 1.0 Final, classes in this package are autogenerated using Barracuda's EventBuilder taskdef)
    • screens - the code responsible for handling events and generating HTML
    • services - miscellaneous classes used by the various screens
  • actions within the HTML pages are now mapped to real event objects
  • we have added a forms / validation package to make it easier to parse and validate forms

Application Structure

Note that the Barracuda Disc Rack application is structured something like this:

  • <root> - root content directory
    • docs - documentation directory
    • images - images that are used by the doc files
    • javadocs - source code documentation (generated from Ant)
    • src - contains all the Java source files, plus the necessary ant scripts to build the system
    • src_mockups - contains the markup files that act as a mockup of the application
    • Web-inf - the Servlet 2.2 application directory
      • classes - contains the actual classes used by the application
      • db - contains the IDB database. This is where your driver must point
      • jars - contains 3rd party .jar files that are needed at compile time but not runtime (ie. because they are included in the appserver)
      • lib - contains 3rd party .jar files to be loaded by the servlet container, including barracuda_disc_rack.jar
      • tmp$ - a temp directory that is created during the build process. Various Barracuda taskdefs will use this directory as a place to store generated source.

Application Flow

By by building on the Servlet API, we no longer need much of the Enhydra Application functionality, since most of these features (eg. lifecycle services) are now provided by the Servlet container. We do need to tell the Servlet container about the application, however. Here's how the whole thing works.

  1. Setup - the first thing we have to do is tell the Servlet container how to identify URLs that represent events and route them to the Barracuda event dispatching package. This happens by making an entry in the web.xml file that looks something like this:

    <servlet>
            <servlet-name>
    ApplicationGateway</servlet-name>
            <servlet-class>org.enhydra.barracuda.discRack.DiscRackGateway</servlet-class>
            <init-param>
                    <param-name>
    ConfFile</param-name>
                    <param-value>/WEB-INF/discRack.conf</param-value>
            </init-param>
            <load-on-startup>
    1</load-on-startup>
    </servlet>
    <servlet-mapping>
            <servlet-name>
    ApplicationGateway</servlet-name>
            <url-pattern>
    *.event</url-pattern>
    </servlet-mapping>

    This causes 3 things to occur:

    1. DiscRackGateway is registered as a valid Servlet
    2. discRack.conf is passed to the servlet as a configuration parameter (for talking to the database)
    3. all URLs that end in .event are mapped to this servlet

  2. Initialization - When the Servlet container starts, it will load the DiscRackGateway and initialize it. When this occurs we do 2 things:

    1. Instantiate the Enhydra Application object (again, this is necessary because the DODS generated DiscRack classes depend on this class)

    2. We initialize all known Event Gateways. An event gateway is just a class that handles events. In the case of the BarracudaDiscRack example, there are 3 such classes:

      1. LoginScreen
      2. RegistrationScreen
      3. DiscScreen

      Registering event gateways gives each class a chance to specify which events it is capable of handling.

  3. Startup - When the user first goes to the BarracudaDiscRack application, the URL they enter in the browser might look something like this:

    http://localhost:8010/BarracudaDiscRack

    This maps the default request to index.html in the main content directory, which simply does an HTML redirect, firing GetLogin.event.

  4. Event Processing - When an event like GetLogin.event is fired, the DiscRackGateway receives the HTTP Request, maps it to the appropriate event object, and then delivers it to the specified listener, which in this case happens to be implemented in the LoginScreen class. The event handler that processes this event does one of two things: it checks to see if the user already has login credentials, and if so, attempts to automatically log them in by firing a DoLogin.event; if not, it redirects processing to the rendering logic by firing a RenderLogin.event.

    Both of these events cause a response to be sent back to the client browser. In the case of RenderLogin, we load the necessary XMLC objects to generate the appropriate HTML view. Before rendering, however, we need to check for errors and repopulate the data elements if possible. This illustrates an interesting characteristic of web-apps -- the same form is often rendered differently depending on how it was accessed:

    1. If the user is coming to the form for the first time (ie. GetLogin.event), there are no errors or previously entered data. All we need to do is display a blank form, which the user can then proceed to fill out.

    2. When the user actually submits a form (ie. DoLogin.event), we typically process the form and either redirect based on whether or not the information is valid -- if they entered a correct user/pwd, we fire a GetCatalog.event; if not, we fire a RenderLogin.event to redraw the login screen.

    In this latter case, we need to show additional information: an error message indicating the problem, and repopulated form fields (if there are many fields, the user will not want to re-enter them all when only one field was at fault.) The way we handle both of htese needs is through form mapping & validation.

  5. Form Mapping & Validation - When we handle a DoLogin.event, we need to process the incoming HttpServletRequest and retrieve the necessary form parameters. Barracuda provides an easy way to do this through form mapping. First we define a "virtual" form that looks something like this:

    class LoginForm extends DefaultFormMap {
    
        //login form constants (these values correspond to the HTML param names)
        static final String USER = "login";                    
        static final String PASSWORD = "password";           
    
        public LoginForm(HttpSession session) {
    
            //define the elements
            if (LoginScreen.this.showDebug>0) Debug.println (this, 0, "Defining Login form elements");           
            this.defineElement(new FormElement(USER, FormType.STRING, null,
                    new NotNullValidator("You must enter a Login.")));
            this.defineElement(new FormElement(PASSWORD, FormType.STRING, null,
                    new NotNullValidator("You must enter a Password.")));
               
            //define a form validator
            if (LoginScreen.this.showDebug>0) Debug.println (this, 0, "Defining Registration form validator");           
            this.defineValidator(new LoginValidator(session));
        }
    }

    This defines a form with two elements: USER, PASSWORD which map to the HTML parameters "login", "password". Both are to be mapped to String objects, and take a basic NotNullValidator (which ensures they are not null or blanks).   Notice that we also add a "form validator" to the form called LoginValidator, which takes care of complex validation tasks, like making sure the user name is on file and that the password entered matches.

    To map an HttpServletRequest to the FormMap, we would do something like this:

    LoginForm lf = new LoginForm();
    lf.map(req);

    This causes the FormMap class to do it's thing and convert all incoming parameters into real objects that can be easily accessed through the FormMap convenience methods.

    Validating a form is just as simple:

    try {
        lf.validate();
    catch (ValidationException ve) {
        ...process accordingly...
    }

    To sum then, when we process the DoLogin.event, we

    1. map the incoming request to a virtual form map
    2. validate the form map and catch any corresponding validation exceptions
    3. save both the form and any validation exceptions in the event queue
    4. redirect flow by firing either a GetCatalog.event (if they succeeded in logging in) or a RenderLogin.event (if they failed)
    5. in this latter case, the event handling code for the RenderLogin.event can retrieve the errors and form from the event queue and use them to repopulate the page with appropriate information.

    The client screen is regenerated, and the process continues with the next user "action".

  6. Using Barracuda Components - The DiscRack examples have recently been upgraded to take advantage of the Barracuda Component model. The process is really quite straightforward. When we wish to render the HTML page back to the user, rather than manipulating the DOM directly we do the following instead.

    First, we get a reference which we'd like to bind a BTemplateComponent to it. Note that we need both a View and a Model when creating the component:

    Node node = page.getDocument().getElementById("LoginForm");
    BTemplate templateComp = new BTemplate(new LoginModel(context), new DefaultTemplateView(node));

    The template component will treat the node it is bound too as a template, parsing through it looking for commands or "directives" embedded within that reference specific data elements in the model. A template component may have multiple models.

    Next, we render the component. This will cause the components to update the DOM.

    try {templateComp.render(new DefaultViewContext(context));}
    catch (RenderException re) {Debug.println (this, 1, "Render err:"+re);}

    Finally, we use a DOM writer to actually render the page.

    new DefaultDOMWriter().write(page, context.getResponse());

    Hmm. That was pretty simple. Let's take a look at what the actual directives and model aspects look like. If we look in the Login.html template, we see lines that looks something like this:

    <tr>
      <td align="right">Login: </td>
      <td>
        <input class="Dir::Get_Data.Login.Username" name="login" ...>
      </td>
    </tr>
    <tr>
      <td align="right">Password: </td>
      <td>
        <input class="Dir::Get_Data.Login.Password" type="password" ...>
      </td>
    </tr>
  7. Here we can see simple directives embedded in the class attributes. Directives can also be stored in a completely separate file, but we've left them in the template for this example just to keep things simple.

    The model looks something like this:

    class LoginModel extends AbstractTemplateModel {
    
        FormMap fm = null;
        ValidationException ve = null;
    
        //constructor (extract form, exception from context)        
        public LoginModel(EventContext ec) {
            fm = (FormMap) ec.get(LOGIN_FORM);
            ve = (ValidationException) ec.get(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.getStringValDefault(LoginForm.USER, "") : "");
            } else if (key.equals("Password")) {
                return (fm!=null ? fm.getStringValDefault(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);
        }
    }

    What happens is when the template component encounters directives requesting data, it tries to match the directives with a known model (in this case the "Login" model), and then attempts to look up data in the model based on the specified key (ie. "Username", "Password", etc). In the example above, the model is returning simple String data. It can also return a DOM node or even another Barracuda component. Basically, what we are doing is returning data if we can find it in the form (ie. the user already entered it), and if not, we just return blanks.

    For more information about Barracuda components, try looking at the Barracuda Component Model Tutorial.

For all the latest information on Barracuda, please refer to http://barracuda.enhydra.org
Questions, comments, feedback? Let us know...