Using Barracuda in an Enhydra super-servlet application
barracuda.gif (11357
            bytes)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.)
  1. Using a Separate Servlet
  2. Using Components and Forms Only
  3. Using full Barracuda without shared Enhydra session data
  4. Using full Barracuda with shared session data

  1. 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

  2. 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);
        }
      }

  3. 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

  4. 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 ) );
        }
      }

  5. 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...