JPA MailReader | |
------------------------------------------------------------------------ | |
Quality Grade: test-build only. | |
---- | |
The JPA MailReader is a best-practices example of using Struts 2 with | |
the Java Persistence API with a standard SQL database. | |
The example descends from the original MailReader Demonstration | |
Application and uses a modified version of the MailReader JPA package | |
developed for the Apache Shale Example. | |
The application uses the Struts 2 CodeBehind plugin that eliminates | |
documenting Actions in XML. The Action classes and pages are linked | |
together using convention over configuration. In some cases, | |
Result annotations are used to move between workflows. | |
This implementation expresses the business classes in an "entity" | |
package. (Business classes are also referred to as "domain" classes | |
or "model" classes.) | |
The business classes are designed from the ground-up to be used with | |
the JPA. Sufficient annotation is provided with the entity | |
implementations so that a SQL database schema can be generated from | |
the entity classes, just by running the application, or a unit test. | |
(See the entity.BootStrapDataTest for an example.) | |
The persistence logic is contained in a "Service" class that is | |
associated with each entity class. The service classes are backed by | |
a static, singleton EntityManagerHelper class that provides the | |
service implementations with "data access object" functionality. | |
The inter-object and inter-layer logic is also contained in the | |
Service classes. Any code that is not dependant on a Struts Action is | |
pushed to the service layer, where it is easier to test and reuse. | |
Each business class also has an XWork type converter. Most often, | |
the converters use a substitute key for the conversion ID, rather than | |
the primary key. (Exposing the primary key to the user interface layer | |
is considered a bad practice in DBA circles.) | |
The type converters do make use of the services to perform database | |
lookups, as needed. An EntityInterceptor opens and closes a transaction | |
for each request (OpenSessionInView), and the type converters share | |
that transaction and a persistence context with the Actions. (The | |
retrieved object is not detached.) | |
To provide maximum portability, the internal primary keys are UUIDs. | |
The entities are based on a mapper superclass which provides shared | |
code for creating and managing the primary keys. | |
The business class, manager, and type converter are all stored in | |
a sub-package named for each business class. Essentially, the | |
sub-package is a resource container that encapsulates all of the | |
entity's data and behavior. | |
An interface and default implementation is provided for each | |
entity and service. Since all of these are one-off implementations, | |
the interface is not strictly needed, but interfaces are | |
still useful containers for JavaDocs, and encourage correct thinking. | |
The Action packaging follows a similar strategy, but also creates | |
a resource heirarchy from the entities. The "root" action package | |
contains the "welcome" resources. The "action.user" package encapsulates | |
the "user" resources, and the "action.user.subscription" package | |
encapsulates the "subscription" resources. The nesting of subscription | |
within user reflects the one-to-many relationship between a user | |
resource and and its subscriptions. | |
Essentially, each package/namespace represents a distinct "resource" | |
from the REST perspective. | |
Each package/namespace has an Index Action, which is usually the | |
superclass for other Actions in the same package. The Index Actions | |
utilize the services to access business logic and persistent data. | |
The default constructor for an Action instantiates the default | |
service, but an alternate constructor is provided that could be | |
used to pass in a mock or alternate service. | |
Action aliases are not used. Each action has it's own Action | |
class, which subclass an Index Action. The execute methods are | |
intentionally anemic, and implementation details are encapsulated | |
on helper methods in the Index Action. | |
The Actions provide request-scope User and Subscription objects | |
that represent the resources being edited in the current request. | |
The pages record the User and/or Subscription resource as a hidden | |
field. The type converters fetch the appropriate entity from the | |
persistence unit, so that it can be set as the current User or | |
Subscription object. | |
Of course, the hidden field can also be expressed as a GET | |
attribute, and this technique is used to restore the current User | |
after a redirect. Overall, the design is intended to be "RESTful" | |
or at least REST-like. | |
The logged-in user is maintained in a session-scope "Profile". | |
Since the CodeBehind package is being utilized, the folder structure | |
for the JSP templates follows the package structure. | |
KNOWN ISSUES | |
* No POM is provided. Dependencies include | |
** commons-logging-1.0.4 | |
** derbyclient | |
** freemarker-2.3.8 | |
** ognl-2.6.11 | |
** struts-codebehind-plugin-2.0.10 | |
** struts-core-2.0.10 | |
** toplink-essentials | |
** toplink-agent-essentials | |
** xwork-2.0.4 | |
* Toplink could be replaced with OpenJPA as a default provider. | |
* Other providers, like Hibernate, could also be swapped in. | |
* Need a routine to autocreate the Derby database at installation, | |
perhaps by running a test suite. | |
* The index.html is not redirecting to the index action. | |
* Global error handling and logging is either not working or not | |
handling 500 errors. | |
* There is no access security. Anyone can access any user resource. | |
* Despite the scope of the application, the example could include | |
a demonstration of handling optimistic locking | |
* The services could be exposed as Web Services (XFire/CFX) using | |
an EntityFilter, once the A&A is sorted out. | |
CURRENT FOCUS | |
* With the application structure in place, work is now focussed on | |
creating a unit test suit against the data access logic. | |
======================================================================== | |