Struts is a component framework developed as an open source project (under the auspices of the Jakarta Apache project) to ease development of scalable web-tier applications. Struts defines an updated Model-View-Controller pattern (called MVC2) for implementing web-based applications. It also defines servlet and JSP components as either views or controllers, with the model implemented as business objects accessible to both view and controller components.
Views are either servlets or JSP pages that provide the HTML-generation end of the process. Controllers are usually servlets and provide the flow control and delegation to the business objects. Many common patterns for generating web-based forms are implemented in Struts as base classes, making construction of complex forms-based applications easy.
When using JDO with Struts, the issues are the same as with generic servlet and JSP pages. The PersistenceManagerFactory (or multiple instances of PersistenceManagerFactory) used with the application is constructed at server or application startup, and each component that needs JDO services needs to access the PersistenceManagerFactory in order to get the PersistenceManager used in the business logic.
Struts 1.1 does not include direct support for JDO, but it provides a flexible way to configure the controller servlet: by defining PlugIn classes that are initialized when the web container loads the Struts servlet. You can exploit this Struts feature by writing a JDOPlugIn class for JDO that manages the PersistenceManagerFactory. A Struts PlugIn class has an init( ) method invoked at servlet initialization, a destroy( ) method invoked at server shutdown, and an arbitrary number of configuration methods.
At servlet initialization, the Struts framework creates an instance of PlugIn for each plug-in element found in the struts-config.xml file in the application's war file. For each set-property element found in the plug-in element, the framework configures the PlugIn by calling the corresponding PlugIn method, following the JavaBeans get/set pattern. After configuring the PlugIn, the framework calls init( ) to have the PlugIn perform the initialization.
The following sample implementation of JDOPlugIn uses three properties: name, path, and jndiName, corresponding to the methods setName(String), setPath(String), and setJndiName(String), respectively. name is the name under which the PlugIn registers the PersistenceManagerFactory; it is required. path is the pathname where the properties file is located in the war file. jndiName is the JNDI name under which the PersistenceManagerFactory was registered by a server-specific process at server startup. One of path and jndiName is required. The following code shows the field declarations and the set( ) methods:
public class JDOPlugIn implements PlugIn { private ServletContext ctx; private String name; private String path; private String jndiName; public JDOPlugIn( ) { } public void setName(String name) { this.name = name; } public void setPath(String path) { this.path = path; } public void setJndiName(String jndiName) { this.jndiName = jndiName; }
The init( ) method uses these helper methods to locate or construct the PersistenceManagerFactory:
private PersistenceManagerFactory getPersistenceManagerFactoryFromPath(String path) throws IOException { Properties props = new Properties( ); InputStream in = ctx.getResourceAsStream(path); props.load(in); return JDOHelper.getPersistenceManagerFactory(props); } private PersistenceManagerFactory getPersistenceManagerFactoryFromJndi(String jndiName) throws NamingException { Context ic = new InitialContext( ); return (PersistenceManagerFactory) ic.lookup(jndiName); }
The init( ) method determines whether to load the PersistenceManagerFactory from a properties file using the path property or to look up the PersistenceManagerFactory from JNDI. It then puts the PersistenceManagerFactory into the servlet context using the given name:
public void init(ActionServlet servlet, ModuleConfig config) throws ServletException { ctx = servlet.getServletContext( ); if (name == null || name.length( ) == 0) { throw new ServletException ("You must specify name."); } try { PersistenceManagerFactory pmf; if (path != null) { pmf = getPersistenceManagerFactoryFromPath(path); } else if (jndiName != null) { pmf = getPersistenceManagerFactoryFromJndi(jndiName); } else { throw new ServletException ("You must specify either path or jndiName."); } ctx.setAttribute(name, pmf); } catch (Exception ex) { throw new ServletException( "Unable to load PMF: name:" + name + ", path: " + path + ", jndiName: " + jndiName, ex); } }
To use the JDOPlugIn, add elements to the struts-config.xml file. For each PersistenceManagerFactory you want to use in your Struts application, add a new plug-in element to the file, with set-property elements:
<plug-in className="com.mediamania.appserver.JDOPlugIn"> <set-property property="name" value="jdo.Movies"/> <set-property property="path" value="WEB-INF/jdoMovies.properties"/> </plug-in> <plug-in className="com.mediamania.appserver.JDOPlugIn"> <set-property property="name" value="jdo.Accounting"/> <set-property property="path" value="WEB-INF/jdoAccounting.properties"/> </plug-in>
Once the PlugIn has initialized one or more PersistenceManagerFactory instances, any Struts Action component associated with the ActionServlet can access them by name. Typically, these will be classes acting as controllers executing business logic. The execute( ) method in these classes gets the PersistenceManagerFactory by name from the servlet context, gets the PersistenceManager, performs whatever business logic is required, commits or rolls back the transaction, closes the PersistenceManager, and returns control to the Struts framework. For example, the execute( ) method might take a Movie name from the context as a movieName attribute, look up its description, and put the description into the context as a movieDescription attribute:
public class LookupMovieAction extends Action { PersistenceManagerFactory pmf = null; PersistenceManager pm = null; public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { try { ServletContext ctx = getServlet().getServletContext( ); pmf = (PersistenceManagerFactory)ctx.getAttribute("jdo.Movies"); pm = pmf.getPersistenceManager( ); Query q = pm.newQuery(Movie.class, "title == param1"); q.declareParameters ("String param1"); String movieName = request.getParameter("movieName"); Collection movies = (Collection)q.execute(movieName); Movie movie = (Movie)movies.iterator().next( ); String description = movie.getDescription( ); ctx.setAttribute("movieDescription", description); } catch (JDOException e) { } finally { if (pm != null) { pm.close( ); } pm = null; } return (mapping.findForward("success")); } }
A typical cycle of Struts processing in the web server involves several interactions between the browser and the web server. In the following sequence, "ACTION" represents a Struts Action component and "JSP" represents a JSP page:
HTTP request arrives at server.
ACTION initialize session (no JDO access).
JSP display page (includes an input form).
HTTP response sent back to user.
User fills in form.
HTTP request arrives at server.
ACTION update datastore based on the submitted form (transactional update).
ACTION read datastore and set up for next page (possibly nontransactional access).
JSP display page (includes another input form).
HTTP response sent back to user.
Repeat steps 5 through 10 until the logical conclusion of the interaction ("Thank you for your order") or the user goes away and the session expires.
User fills in form.
HTTP request arrives at server.
ACTION update datastore based on the submitted form (transactional update).
JSP display page (no input form).
HTTP response sent back to user.
With this pattern, each ACTION gets the configured PersistenceManagerFactory appropriate for the usage (transactional or nontransactional) and executes the business logic appropriate for that action.