I've been trying to get the hang of struts now for the past few months, and am
wondering how people write "re-usable" base classes for Struts. If you have
the time, I'd really appreciate your thoughts on this - bear with me, as it's a
lot of reading:
My actions tend to be "similar" but making them "reusable" seems to be eluding
me.
What I would like to have is a set of reusable actions that handle the
following standard application features:
1. A wizard (multiple jsps, one form bean, data formatting, validation specific
per page, with persistence when any navigation occurs, not just at the end).
I've seen an example of an "at-the-end" wizard in the sample Chapter from
Struts Recipes by George Franciscus and Danilo Gurovich.
2. A multi-record screen ( one jsp, array of items, formatting of data,
validation w/ messages, and persistence)
Are these already written, so I can save some time?
So far, I've tried to write a re-usable piece of code for #2.
Based on ideas in the ObjectSource Struts Survival Guide, Chapter 10:
#1: I wrote my own mapping. Here are the properties:
public class CRUDActionMapping extends ActionMapping{
private String formType;
private String formMultiRecordProperty;
private String formDefConfig;
private String databaseBeanClassName;
private String saveServiceMethod;
private String retrieveServiceMethod;
private String serviceName;
#2: My struts-config.xml for the action for this class is defined as:
<action path="/cbdeqr_engcodehours"
className="dep.ccdreporting.view.mappings.CRUDActionMapping"
type="dep.ccdreporting.view.actions.cbp.CbdeqrEngCodeHoursAction"
name="CbdeqrEngCodeHoursForm"
input="/WEB-INF/jsp/cbp/cbdeqr_engcodehours.jsp"
scope="session"
validate="false"
parameter="event_save=save,event_codehours=initializePage,
event_reportInProgress=previous,event_countyHours=next,
default=unspecified">
<set-property property="cancellable" value="true"/>
<set-property property="formType" value="MultiRecord"/>
<set-property property="formMultiRecordProperty"
value="codehours"/>
<set-property property="formDefConfig"
value="CbdeqrEngCodeHoursForm"/>
<set-property property="databaseBeanClassName"
value="dep.ccdreporting.model.vo.cbp.CbdeqrCodeHours"/>
<set-property property="saveServiceMethod"
value="saveCbdeqrCodeHours"/>
<set-property property="retrieveServiceMethod"
value="getEngCodeHours"/>
<set-property property="serviceName"
value="dep.ccdreporting.model.service.cbp.CBPDaoService"/>
<forward name="hourspercounty"
path="/cbdeqr_engcountyhours.do"/>
<forward name="cbdeqr_main" path="/cbdeqr.do"/>
<forward name="success"
path="/WEB-INF/jsp/cbp/cbdeqr_engcodehours.jsp"/>
</action>
#3: The action class CbdeqrEngCodeHoursAction is reduced to two overridden
methods (save and initializePage, due to calling a calculation) of a base
class, and one method that gets the parameters to retrieve the data, which is:
public HashMap getRetrieveServiceMethodParams( HttpServletRequest request ) {
HttpSession session = request.getSession();
UserBean userbean = (UserBean) session.getAttribute("usrbean");
HashMap codeparams = new HashMap();
codeparams.put("report_id" , userbean.getReport_id());
return codeparams;
} // getRetrieveServiceMethodParams
#4: The base class handles the initialization of the page and the save. Here's
the save() call so this message doesn't get too wordy:
public ActionForward save(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception,
ServletException
{
ActionForward fwd = mapping.getInputForward();
if (isFormValid(mapping, form, request)) {
if ( mapping instanceof CRUDActionMapping ) {
CRUDActionMapping myMapping = (CRUDActionMapping) mapping;
if (myMapping.getFormType().equalsIgnoreCase("MULTIRECORD")) {
saveMultiRecordForm ( mapping, form, request );
...
and the save code -- this is where I'm confused - is this a reasonable
approach, or am I really missing the boat. Specifically: Should I be creating
the service like this? Also, I really want the ability to log who is
performing the service methods as well - i.e. request.getRemoteUser(), so do I
have to pass this to the service method, or can I get it another way??
public void saveMultiRecordForm ( ActionMapping mapping,
ActionForm form,
HttpServletRequest request )
throws Exception
{
// get all the action-mapping properties
CRUDActionMapping crudMapping = (CRUDActionMapping) mapping;
String formPropertyName = crudMapping.getFormMultiRecordProperty();
String formDefConfigName = crudMapping.getFormDefConfig();
String databaseBeanClass = crudMapping.getDatabaseBeanClassName();
String saveServiceMethodName = crudMapping.getSaveServiceMethod();
// form-def returns an array of dynaActionForms, which is stored in the:
formPropertyName
DynaActionForm dynaform = (DynaActionForm ) form;
ArrayList listOfFormDetails = (ArrayList) dynaform.get( formPropertyName );
// db bean array needed for all our service method calls
Class classOfDbBean = Class.forName( databaseBeanClass );
Object[] details = (Object[]) Array.newInstance( classOfDbBean,
listOfFormDetails.size() );
// Create an instance of the service class.
Class serviceClass = Class.forName( crudMapping.getServiceName() );
Object service = serviceClass.newInstance();
// find the method that takes an array of data, and invoke it.
Object arguments[] = new Object[] { details };
Method svcMethod = serviceClass.getMethod(saveServiceMethodName, new Class[]
{ details.getClass()} );
Object result = svcMethod.invoke( service, arguments );
} // saveMultiRecordForm
Thanks for anyone who has made it this far!