OpenSubsystems

Business Components for Java Applications

Open Core

Tutorial

Documentation
Last modified

$Author: bastafidli $
$Date: 2007/03/11 06:30:45 $
$Revision: 1.23 $
$RCSfile: tutorial_webapps.html,v $

Constructing the web application

Web applications have become increasingly popular in recent years mainly due to familiarity of users with web browsers and Internet web sites. More and more business applications are being delivered as web applications since once deployed on the Internet or intranet they become instantly available to large audience of users without forcing them to download or install any software.

Open Core makes developing web applications easier by providing set of classes that allow to easily make an application available on the Internet, handle the initialization, configuration, security, and tie together all the code developed so far. In addition, Open Core integrates Tiles template engine and provides several ready to use templates to speed up development of the web user interface.

Our tasks

We will decide what screens and web pages our user interface consists of. We can implement these easily by deriving them from one of the available templates without having to know much about HTML. We can customize the default look and feel by providing custom stylesheets that modify the behavior of the default stylesheets provided by Open Core. Once the decision about the screens and navigation is made, we will connect the user interface with the business logic by implementing the web tier. This can be easily done by deriving our own servlets from the base classes provided by Open Core. These will handle for us security related tasks, session management, user interface configuration and initialization, etc.

DatabaseContextListener - class responsible for initialization of database layer for web applications. Since web application doesn't have any main method, there is a need for an entry point where all database components the application consists of are initialized in a dynamic manner. DatabaseContextListener allows you to define in your standard web.xml file what subsystems the application consists of without hardcoding the dependencies in the code or in proprietary configuration files.

WebSessionServlet - base class for all servlets that might be concerned with controlling access to the resources provided by the web application. It intercepts requests and if the application requires authentication it suspends the unauthenticated requests and allows them to resume once the user was authenticated by the system. If authentication is not required, this class provides additional help with session handling, coding of common logic, etc.

WebUIServlet - base class for all servlets that display results of a business logic processing using some web user interface. It allows to configure the pages responsible for rendering of the business logic results using standard web.xml or if desired using application specific configuration file.

WebUIDispatchServlet - servlet derived from WebUIServlet that simply redirects requests to a configured resource while still enforcing authentication established by WebSessionServlet.

WebModule, WebModuleDefinitionManager, WebModuleListener and WebModuleTag - classes allowing to divide the application user interface into modules that can be constructed and operate independently. WebModuleListener allows to define in the standard web.xml file what modules the user interface consists of without hardcoding the dependencies in the code or in a proprietary configuration files. WebModuleDefintionManager and WebModuleTab make easy to generate visual representation of the available modules in the application user interface.

OpenChronicle user interface

The user interface of OpenChronicle consists of 10 pages, but it takes only three pages to create the entire skeleton of the application. These three pages will be used to browse all the chronicles and entries:

  • blogindex.jsp View source - page displaying list of available chronicles. It also allows user to submit request to create new chronicle if he or she is logged in.
  • blogviewer.jsp View source - page displaying selected chronicle together with all its entries. If user is logged in, the chronicle can be also modified or user can submit request to add new entry or delete the chronicle.
  • blogentryviewer.jsp View source - page displaying selected entry. If user is logged in, the entry can also be modified or user can submit request to delete the entry.

As you may have noticed, these three pages work in two modes. For users that are logged in, the pages alllow to modify the data they display. For users that are not logged in, these pages just display the data for their reading and watching pleasure. Next four pages allow simple maintenance of chronicles and entries and are available only to logged in users:

  • newblog.jsp View source - page allowing logged in user to create new chronicle.
  • newblogentry.jsp View source - page allowing logged in user to create new entry.
  • confirmdeleteblog.jsp View source - page allowing logged in user to confirm and therefore delete chronicle.
  • confirmdeleteblogentry.jsp View source - page allowing logged in user to confirm and therefore delete entry.

Next two pages are related to authentication. Open Core provides all the necessary plumbing to ensure that user is authenticated when necessary and that the user interface knows about it. We just need to provide a "face" for this process.

  • login.jsp View source - page allowing user to login to the application in order to modify the data in it.
  • logout.jsp View source - page allowing user to confirm and therefore logout from the system.

At last comes the page that handles errors. User can enter URL that may not correspond to any existing chronicle or entry. User can also click on a link to chronicle or entry, which was meanwhile deleted. This page improves the user experience in case of an error:

  • missingpageerror.jsp View source - error handling page providing nice looking error message and always working link to get the user back on track.

So far this looks like a regular web application with couple of JSPs. What sets it apart is that when you look at the page source code you will notice that they are not concerned with the look and feel of the application or the fact that HTML pages require particular structure, blocks and elements that have only little to do with our application. This all thanks to the fact that the pages are using single common template implemented as another JSP page:

  • blog.jsp View source - template for the entire application ensuring common look and feel and simplifying the coding of what really matters.

You can read more about how the template engine works, how easy it is to create templates and pages using these templates and about the templates Open Core provides in separate document. Let's discuss some Open Core specifics to demonstrate the benefits it brings when developing web applications.

Coding the web pages

We have decided to provide user friendly layout for our application with always visible header, footer and content that fills out the space in between and can scroll if necessary. To achieve this without Open Core we would have to use some tricky CSS or JavaScript. In our case it was easy. The layout blog.jsp just specifies that it wants to reuse Open Core provided template called basic website that offers these capabilities.

   <%-- Here we specify what main layout we use. This acts as inheritance mechanism. --%>
   <tiles:insert page="/core/jsp/layout/basicwebsite.jsp" flush="true">
               

Now instead of coding the entire page from scrach, it is easy to just specify that we want to reuse the application layout:

   <%-- This page is reusing global layout --%>
   <tiles:insert page="/blog/jsp/layout/blog.jsp" flush="true">
              

and then enter the values for the title of the page and what should be its content, for example:

   <tiles:put name="blogtitle" value="Delete chronicle"/>

   <%-- Main content area --%>
   <tiles:put name="blogcontent" direct="true">

      <%-- Form to delete existing blogs --%>
      <logic:equal name="loggedin" value="true">
         <form method="post" action="">
            <input type="hidden" 
                      name="<%=WebUIServlet.FORM_NAME_REQUEST_PARAM%>"
                      value="FORM_DELETE_BLOG">
            ...
         </form>
      </logic:equal>

   </tiles:put>
               

Many pages and the layout provide slightly different content based on the fact if user is logged in or not. It is easy to detect this scenario, since Open Core automatically sets flag loggedin to true, when user is logged in. You just need to declare it and use it in your page, for example:

   <bean:define id="loggedin" name="loggedin" scope="request"  type="java.lang.Boolean"/>
   ...               
   <logic:equal name="loggedin" value="false">
      <a href="<%=contextpath%>/<%=BlogNavigator.LOGIN_WEB_PAGE%>">Login</a>
   </logic:equal>
   <%-- If user is logged in, he can modify the blog data --%>
   <logic:equal name="loggedin" value="true">
      <a href="<%=contextpath%>/<%=BlogNavigator.LOGOUT_WEB_PAGE%>">Logout</a>
   </logic:equal>
               

You can find more details by examining the source code of the pages but with such time savers as these creating the entire user interface for our application is just a matter or few hours.

Connecting user interface with business logic

The web pages we have just designed and coded will display our application in the browser that can run on any computer connected to the server running the application over the Internet or an intranet. The server has to contain code, that can communicate with the browser and translate it's requests to format required by the business logic APIs we have implemented earlier. This code is part of the application web tier and is implemented using simple Java servlets.

The skeleton of the application enables user to browse the chronicles and entries. All this functionality is concentrated in a single class, BlogBrowserServlet View source. This servlet is not concerned with how the data are created or modified or how and if at all user logs in to the application. This servlet is responsible for displaying the web pages to user and therefore it is derived from the WebUIServlet servlet. The first thing the blog browser servlet needs to do is to identify the JSP pages that will be used to display the data since these are fully configurable. In our application it is done in the init method using functionality provided by the parent class.

   public static final String BLOGBROWSER_BLOG_INDEX_PAGE = "blogbrowser.blog.index.page";

   public void init(
      ServletConfig scConfig
   ) throws ServletException
   {
      super.init(scConfig);

      // Load UI pages for this blog
      // The main entry point to the blog functionality
      cacheUIPath(scConfig, BLOGBROWSER_BLOG_INDEX_PAGE, 
                  "Path to main index page is not set in property " 
                  + BLOGBROWSER_BLOG_INDEX_PAGE);
      ...
   }               
               

Once the pages are ready to be used, the next step is to identify when to display which page. Each request from the browser to display read only data is received in method doGet. The implementation examines the URL for the requested static HTML page and maps it to either list of chronicles, single chronicle or a single entry from the chronicle. If the URL doesn't make sense an error message page we have created earlier will be displayed to the user.

   protected void doGet(
      HttpServletRequest  hsrqRequest,
      HttpServletResponse hsrpResponse
   ) throws ServletException, 
            IOException
   {
      if (WebUtils.isIndexPage(hsrqRequest))
      {
         if (WebUtils.isMainIndexPage(hsrqRequest))
         {
            // Create page displaying all existing blogs
            createMainIndexPage(hsrqRequest, hsrpResponse);
         }
         else
         {
            // Create page displaying one blog
            createIndexPage(hsrqRequest, hsrpResponse);
         }
      }
      else
      {
         if (WebUtils.isStaticWebPage(hsrqRequest))
         {
            // Create page displaying one entry of a blogs
            createBlogEntryPage(hsrqRequest, hsrpResponse);
         }
         else
         {
            hsrpResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
         }
      }
   }
               

The last step is to display the requested page:

   protected void createIndexPage(
      HttpServletRequest  hsrqRequest,
      HttpServletResponse hsrpResponse
   ) throws IOException,
            ServletException
   {
      s_logger.entering(this.getClass().getName(), "createIndexPage");

      try
      {
         Object   objBlogIdentification;
         Object[] arObjects;
         BlogNavigator navigator;

         navigator = getNavigator(hsrqRequest);
         objBlogIdentification = navigator.getBlogIdentification(hsrqRequest);
         arObjects = getController().getWithEntries((String)objBlogIdentification);
                  
         if ((arObjects != null) && (arObjects[0] != null)) 
         {
            hsrqRequest.setAttribute("blog", arObjects[0]);
            // It is ok to do not have any entries
            if (arObjects[1] != null)
            {
               hsrqRequest.setAttribute("blogentries", arObjects[1]);
            }
            hsrqRequest.setAttribute("blognavigator", navigator);
            displayUI(BLOGBROWSER_BLOG_VIEWER_PAGE, hsrqRequest, hsrpResponse);
         }
         else
         {
            // We have found either the blog or the entry so tell the user
            // about it 
            hsrpResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
         }
      }
      catch (Exception eExc)
      {
         s_logger.log(Level.WARNING, "An error has occured while retrieving blog.", 
                      eExc);
         messageBoxPage(hsrqRequest, hsrpResponse, "Error", eExc.getMessage(),
                        getNavigator(hsrqRequest).getRootURL(),
                        eExc.getCause());
      }
      finally
      {
         s_logger.exiting(this.getClass().getName(), "createIndexPage");
      }
   }
               

Notice how the controller is used to invoke the business logic and retrieve all the data web page needs at once. The displayUI method is used to display the page (cached earlier in the init method) that will render the retrieved data.

Processing data modifications

When logged in user browses through the chronicles and entries, the generated web pages provide him an opportunity to modify data on the server using forms, edit fields and other controls. Once user submits the data modifications to the server, the server needs to accept such requests and send them to the business logic for processing. All this functionality is provided by a single class, BlogEditServlet View source, derived from BlogBrowserServlet View source. Thanks to the inheritance this servlet has now the opportunity to do both, browse the data and modify them if the user allowed. The developer has now option to create either a read only version of the application using the BlogBrowserServlet or application that allows modifying the data using BlogEditServlet.

The edit servlet caches pages that allow the data modification (e.g. creation or deletion) in the init method the same way as described earlier. Each request to modify the data is received and processed by the method doPost. Implementation of this method recognizes what action has the user requested and forwards it for processing.

   protected void doPost(
      HttpServletRequest  hsrqRequest,
      HttpServletResponse hsrpResponse
   ) throws ServletException, IOException
   {
      switch(getFormToProcess(hsrqRequest))
      {
         ...
         case FORM_NEW_BLOG_ID :
         {
            processNewBlogForm(hsrqRequest, hsrpResponse);
            break;
         }

         case FORM_CREATE_BLOG_ID :
         {
            processCreateBlogForm(hsrqRequest, hsrpResponse);
            break;
         }
         ...
         default :
         {
            super.doPost(hsrqRequest, hsrpResponse);
            break;
         }
      }
   }
               

Each request for data modification is accompanied by a form name defined in the web page we have previously constructed. The web page can for example contain form

   <%-- Form with navigation to create new blogs --%>
   <form method="post" action="<bean:write name="blognavigator" 
                                           property="postURL"/>">
      <input type="hidden" 
             name="<%=WebUIServlet.FORM_NAME_REQUEST_PARAM%>" 
             value="FORM_EDIT_BLOG">
      <input type="submit" name="create" value="Start new chronicle...">
   </form>
               

and the servlet uses the FORM_NAME variable value to detect the user's action. The detection is performed in getFormToProcess method overridden from the Open Core provided servlet base class.

   protected int getFormToProcess(
      HttpServletRequest hsrqRequest
   )
   {
      String strFormName;
      int    iReturn = FORM_UNKNOWN_ID;

      strFormName = hsrqRequest.getParameter(FORM_NAME_REQUEST_PARAM);

      if (strFormName == null)
      {
         iReturn = super.getFormToProcess(hsrqRequest);
      }      
      ...
      else if (strFormName.equals(FORM_CREATE_BLOG_NAME))
      {
         // Create new blog from already supplied details
         iReturn = FORM_CREATE_BLOG_ID;
      }
      ...
      else if (strFormName.equals(FORM_DELETE_BLOGENTRY_NAME))
      {
         // Delete current blog
         iReturn = FORM_DELETE_BLOGENTRY_ID;
      }
      else
      {
         iReturn = super.getFormToProcess(hsrqRequest);
      }

      return iReturn;
   }
               

There might be two kinds of requests for data modifications. The first kind is requests to display page that enables some data modification but doesn't actually perform one itself. These pages may not normally accessible by browsing the existing data. These kinds of requests are handled the same way as the other read only requests by retrieving the data and rendering it on the page.

   protected void processNewBlogForm(
      HttpServletRequest  hsrqRequest,
      HttpServletResponse hsrpResponse
   ) throws IOException,
            ServletException
   {
      s_logger.entering(this.getClass().getName(), "processNewBlogForm");

      try
      {
         Blog  blog;

         // Create template object which will be used to will the form with 
         // default values
         blog = (Blog)getController().get(DataObject.NEW_ID);

         hsrqRequest.setAttribute("blog", blog);
         hsrqRequest.setAttribute("blognavigator", getNavigator(hsrqRequest));
         displayUI(BLOGEDIT_NEW_BLOG_PAGE, hsrqRequest, hsrpResponse);
      }
      catch (Exception eExc)
      {
         s_logger.log(Level.WARNING, 
                      "An error has occured while retrieving information about" +
                      " new blog.", eExc);
         messageBoxPage(hsrqRequest, hsrpResponse, "Error", eExc.getMessage(),
                        getNavigator(hsrqRequest).getRootURL(),
                        eExc.getCause());
      }
      finally
      {
         s_logger.exiting(this.getClass().getName(), "processNewBlogForm");
      }
   }
               

Notice above that even pages allowing user to create the data use the controller to retrieve empty template object, which is then used to populate the default values to the form when the page is rendered.

The second kind of requests is the one actually asking the server to modify some data and is usually accompanied by the changed data. Processing of these requests consists of three steps. First step retrieves the changed data from the request.

   protected Blog parseBlogFromRequest(
      HttpServletRequest hsrqRequest
   )
   {
      String    strTemp;
      int       iBlogId;
      int       iDomainId = DataObject.NEW_ID;
      String    strFolder;
      String    strCaption;
      String    strComments;
      Timestamp creationDate;
      Timestamp modificationDate;
      
      strTemp = hsrqRequest.getParameter("BLOG_ID");
      if (strTemp == null)
      {
         throw new IllegalArgumentException("Blog id is not specified.");
      }      
      iBlogId = Integer.parseInt(strTemp);
      
      iDomainId = CallContext.getInstance().getCurrentDomainId();
      
      strFolder = hsrqRequest.getParameter("BLOG_FOLDER");
      if (strFolder == null)
      {
         strFolder = "";
      }
      ...
      // User can never change modification date, therefore we require to store
      // it as the millisecond value since that is independent from the display format       
      strTemp = hsrqRequest.getParameter("BLOG_MODIFICATION_DATE");
      if (strTemp == null)
      {
         modificationDate = null;
      }
      else
      {
         modificationDate = DateUtils.parseTimestamp(strTemp);
      }

      return new Blog(iBlogId, iDomainId, strFolder, strCaption, strComments, 
                      creationDate, modificationDate);
   }
               

In the second step the servlet uses the controller to send the retrieve data to the business logic for processing. The last step involves handling of any errors that may have occurred.

   protected void processCreateBlogForm(
      HttpServletRequest  hsrqRequest,
      HttpServletResponse hsrpResponse
   ) throws IOException,
            ServletException
   {
      s_logger.entering(this.getClass().getName(), "processCreateBlogForm");

      UserTransaction transaction =  null;
      Blog            blog = null;
      try
      {
         transaction = DatabaseTransactionFactoryImpl.getInstance().requestTransaction();

         blog = parseBlogFromRequest(hsrqRequest);
         Blog blogUpdated = null;
      
         // Since one request corresponds to user transaction and we know we are
         // going to modify data, start database transaction to group any 
         // databast modification and queries we may need to execute to satisfy
         // this action.
         transaction.begin();         
         blogUpdated = (Blog)getController().create(blog);         
         transaction.commit();
                  
         // Redirect user to the page displaying newly created blog
         hsrpResponse.sendRedirect(getNavigator(hsrqRequest).getURL(blogUpdated));
      }
      catch (OSSInvalidDataException ideExc)
      {
         TransactionUtils.rollback(transaction);
         CallContext.getInstance().getMessages().addMessages(
                                                    ideExc.getErrorMessages());
         // Return the submitted value with the modified entries
         hsrqRequest.setAttribute("blog", blog);
         hsrqRequest.setAttribute("blognavigator", getNavigator(hsrqRequest));
         displayUI(BLOGEDIT_NEW_BLOG_PAGE, hsrqRequest, hsrpResponse);
      }
      catch (Throwable thr)
      {
         TransactionUtils.rollback(transaction);
         s_logger.log(Level.WARNING, "An error has occured while creating blog.", thr);
         messageBoxPage(hsrqRequest, hsrpResponse, "Error", thr.getMessage(),
                        getNavigator(hsrqRequest).getRootURL(),
                        thr.getCause());
      }
      finally
      {
         s_logger.exiting(this.getClass().getName(), "processCreateBlogForm");
      }
   }
               

Notice how errors are handled in two different ways. Errors involving invalid data are displayed back to the user using the same data and on the same page from which the request was sent. This allows the user to correct the data and resubmit the request. CallContext object allows convenient way how to send such error messages to the user interface.

Fatal errors, which are for example caused by loss of database connectivity are handled using generic message box page provided by Open Core.

Each method that modifies data should also handle transactions to ensure consistency of the data. Either all data modifications will be performed at the moment when the transaction is committed or all will be cancelled when the transaction is rolled back. This step is necessary since if the application is deployed in non J2EE environment (e.g. as POJO) there is no container to handle the transactions for us. The nice thing is that Open Core provides the UserTransaction object, which will work in all environments and therefore the above code will work for both, POJO or J2EE.

Application is now almost complete. It allows creating, modifying or deleting data and browsing the existing data. What is still missing is to ensure that only the authorized users can create or change the data.

Next: Securing the web application
Previous: Developing the business logic