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:
- All log messages should be externalized -- i.e., use code like:
if (logger.isDebugEnabled())
logger.debug(messages.getMessage("entering.perform"));
- Include if (logger.isDebugEnabled()), isInfoEnabled(), tests etc. as above
to avoid unecessary stack operations (see the log4j pages for explanation of
this best practice)
- log all exceptions (generally as ERROR)
- log all transactions as INFO, including ids for readers and books
|