Virtual Library 2.0 Technical Overview

Basic Ingredients

  • The Virtual Library (VL) is a Apache Struts/Tiles 1.3.5 and Spring Framework 2.0.2, Java servlet-based application.
  • The VL is set up to use a single SQL 92 / JDBC compliant database for both the application and user store.
  • The application has been tested using Tomcat 5 (Java 5) through Tomcat 8 (Java 8).  
  • The VL uses Container-managed security, using a JDBC Realm.
  • The VL uses the mail session and JDBC connection pool resources provided by Spring.   The connection pool is Apache Commons DBCP.
  • The VL uses log4j, version 1.2.6 for logging and Apache Commons BeanUtils, which requires Commons Collections and Commons Logging. It also uses the Commons Lang package.
  • The VL 2.0 is built using Apache Maven. The project pom.xml is included with sources.
Runtime Environment

The application runs entirely within a serlvet 2.3 and newer container.   There are no EJBs, RMI servers or other components running outside of the servlet engine.   The application will work (and has been tested exclusively) running Tomcat as a standalone (i.e., no external HTTP server).  The database location is configured in the Spring configuration file (lightweightcontainer.xml) and it can be either local or remote.   An outbound SMTP host is required.   Like the database, this is configured in Spring config file, can be either local or remote and must accept SMTP connections from the host running VL.  

Application Architecture

The VL is a struts 1 MVC application.  Consult the following references for background on MVC and struts:

Model Components
The VL model is implemented in domain beans (Book, Reader, etc.), a manager class (LibraryManager) and DAO classes.  The domain beans are operated upon (updated, inserted, retrieved, etc.) by DAO related classes at the request of the LibraryManager.   Domain beans are instantiated by controller Actions, which use BeanUtils to synch their properties with Form beans in the view.   

The LibraryManager is a collection of methods supporting business functions such as processing book requests, checkin, checkout, reader/book exists, etc, accessing the database through the DAO layer. The LibraryManager is "injected" into the Action classes using Spring, with its member DAO instances injected with properties set in the Spring configuration file, lightweightcontainer.xml. All SQL used by the DAO classes is specified in this file.

View Components
The jsp's that form the view portion of the VL are limited to pure presentation.  There are no sriptlets in the VL jsp's!  . There is also no javascript. The VL jsp's make extensive use of struts tag libraries and all of the pages "inside" the library are generated from a struts template (template.jsp).   This template divides the page into five logical components:

  • The page title
  • The page header - the title that appears on the page as rendered by the browser
  • sidebar -- the left-hand nav bar
  • the page content
  • the page footer -- hard coded in the template
A typical VL page has two jsp's associated with it -- one being the "instantiated template" that references the components above -- and the other being the content for the content portion of the page.   The name of the second page ends with "Content".  For example, "editReader.jsp" is the "instantiated template" for the edit reader page and "editReaderContent.jsp" contains the content for the page.    See the struts docs for a full description of how templates work; but just looking at the VL jsp's the structure should be obvious.

Controller Components
The Struts ActionServlet is the core controller component of any Struts 1 application.  The VL does not subclass the struts ActionServlet or extend the core struts framework in any other way.  The VL does include, however, a base Action class, LibraryAction that extends org.apache.struts.action.Action.  The LibraryAction class overrides the execute() method of the struts Action class, including generic logging, action cancellation handling and dispatch to an abstract executeAction() method that all concrete LibraryActions implement.  This class also includes a standardForward() method that forwards to the "error" target if errors have been encountered in Action processing or to "success" if there are no errors.  All VL browser requests are for Actions configured in /conf/struts-config.xml.  Action URI's end in ".do" (this is configured in web.xml and is what links struts-controlled URI's to the struts ActionServlet).

Container Managed Security Implementation

The VL uses the servlet implementation of container managed security as defined in the Servlet 2.3 specification. Refer to the spec and/or the Tomcat Realm HOWTO for details on how this implementation works.

Roles
The VL uses a very simple set of role definitions.  There are just two roles: "reader" and "administrator".   Readers can add books, search for books, get status on books, request books and edit their own profile information.  Administrators can check books out and in and add/delete both books and readers.  They can also modify any reader's profile. Finally, administrators can perform the most important function of any administrator -- they can "promote" other readers to become administrators.  (They can also "demote" which can result in a "no administrator" state if they are not careful).

Authorization
All URI's starting with /a/ or /r/ are protected by the container.   The /a/* path is available to both readers and administrators. The /r/* URLs are available to administrators only.   This is specified specified in security constraints in web.xml:

                            <!-- Security Constraints -->
                            <security-constraint>
                            web-resource-collection>
                            <web-resource-name>Secure Area</web-resource-name>
                            <url-pattern>/a/*</url-pattern>
                            </web-resource-collection>
                            <auth-constraint>
                            <role-name>administrator</role-name>
                            <role-name>reader</role-name>
                            </auth-constraint>
                            </security-constraint>
                            <security-constraint>
                            web-resource-collection>
                            <web-resource-name>Administrative Functions</web-resource-name>
                            <url-pattern>/r/*</url-pattern>
                            </web-resource-collection>
                            <auth-constraint>
                            <role-name>administrator</role-name>
                            </auth-constraint>
                            </security-constraint>
                        
If a user requests a page such as "/library/a/listBook.do?selector=None", if s/he is not logged in, the container will present the login page.   On success, the user will be redirected to the desired destination page.   The only "non-secure" pages in the VL are the login page, the reader registration pages and their associated actions, and a "goodbye" page targeted on logoff. 

In some places, The VL uses selective rendering using the struts logic tags to limit what what readers can do.   For example, in editReaderContent.jsp, we have:

                            <logic:present role="administrator">
                            <tr><th align="right"><bean:message key="reader.prompt.administrator"/></th>
                            <td align="left"><html:checkbox property="administrator"/></td></tr>
                            </logic:present>
                        
If the user does not have the "administrator" role, the "administrator" checkbox on the reader edit form will not appear.   Administrators can use this checkbox to "promote/demote" readers.

It should be noted that limiting what users can do by selective rendering of html elements is a very weak form of security.   If the VL were processing financial transactions or managing sensitive data, an additional layer of authorization checking at the controller and/or model level would be required.

Error Management

Model components
The class LibraryException extends org.sourceforge.util.PortableException which is a generic Exception wrapper that enables stack traces to be fully serialized.  Model components should throw LibraryExceptions or instances of subclasses of this class. 

Actions
Actions catch Throwable in meaningful blocks within their executeAction() methods.  A list of LibraryExceptions wrapping the Throwables trapped during executeAction() processing is accumulated in an errors ArrayList.   If this ArrayList is not empty on completion of the executeAction() method, it is placed in a "processingErrors" request attribute and control is forwarded to the generic error page (content is in hosedContent.jsp).  See discussion above of standardForward().   A customized top-level message to the user may also be placed in a request attribute named "userMessage".  Both of these request keys -- along with all other parameter names, are defined in Constants.java and referred to in the code by their symbolic names (e.g. Constants.PROCESSINGERRORS = "processingErrors").

Generic Error Page
Here is the core content of HosedContent.jsp:

                            <logic:present name="userMessage">
                            <h3>  <bean:write name="userMessage"/> </h3>
                            </logic:present>
                            <br>
                            <logic:present name="processingErrors">
                            <hr>
                            <bean:message key="hosed.message"/>
                            ...
                            <logic:iterate 
                            id="er" 
                            name="processingErrors" 
                            type="org.sourceforge.vlibrary.exceptions.LibraryException">
                            <strong><bean:write name="er" property="message"/> </strong> <br>
                            </logic:iterate>
                        </logic:present>

If the "userMessage" request attribute has been set, the content of that message is displayed first.   Then, the generic message defined under the key "hosed.message" in ApplicationResources.properties is displayed as a header for the list of errors encountered during processing.   This list of messages is generated by iterating over the ArrayList stored in the "processingErrors" request attribute.   The elements of this list are expected to be LibraryExceptions.  

The current setup of hosedContent.jsp will display stack traces to end users -- generally not something that one should do in a production app.   This reflects the beta state of the application.   This page will be replaced when the vlibrary application reaches "stable" state.

Logging

All vlibrary-specific logging uses log4j. The configuration file is log4j.properties, located in the /conf directory of CVS and the source distribution and deployed to /webapps/library/WEB-INF/classes. In log4j.properties ("production" example on a Unix/Linux system), we have:

                            # Set root logging level to WARN to prevent Stuts/Tomcat from flooding
                            log4j.rootLogger=WARN, R
                            
                            # Set application log level to DEBUG
                            log4j.logger.org.sourceforge=DEBUG
                            
                            log4j.appender.R=org.apache.log4j.RollingFileAppender
                            log4j.appender.R.File=/var/logs/vlibrary2.0/libraryTran.log # example, admins should set to whatever correct location is needed
                            log4j.appender.R.MaxFileSize=100KB
                            
                            # Keep 5 backup files
                            log4j.appender.R.MaxBackupIndex=5
                            
                            log4j.appender.R.layout=org.apache.log4j.PatternLayout
                            log4j.appender.R.layout.ConversionPattern=%d %p %c - %m%n
                        
The root log level is set to WARN to prevent Struts and/or Tomcat from generating DEBUG error messages, which the org.sourceforge.* classes are configured to do. To get less verbose logging, set this to WARN or ERROR.

In this example, the application-specific log messages will go to /var/logs/vlibrary2.0/libraryTran.log, which will roll over when it reaches 100KB. See sources for a "development" example for this configuration file.

The standard logging pattern is to define a static logger for each class -- e.g.,

                            /** log4j Logger */
                            private static Logger logger =
                            Logger.getLogger(SaveReaderAction.class.getName());
                        
and then to follow the vlibrary logging standards:
  1. All log messages should be externalized -- i.e., use code like:
    if (logger.isDebugEnabled())
                          logger.debug(messages.getMessage("entering.perform"));
  2. Include if (logger.isDebugEnabled()), isInfoEnabled(), tests etc. as above to avoid unecessary stack operations (see the log4j pages for explanation of this best practice)
  3. log all exceptions (generally as ERROR)
  4. log all transactions as INFO, including ids for readers and books