Transactional Document Actions
Action classes represent the "controller" part of Struts, and the "controller" part of the MVC pattern controls things. It does things. Yes, that's right, the Action class is where the money's at, yo, because once a user presses a button on a page, it's up to an Action class to respond to it and make something happen.
Extending org.kuali.kfs.kns.web.struts.action.KualiTransactionalDocumentActionBase
For our own document's actions, we need to extend org.kuali.rice.kns.web.struts.action.KualiTransactionalDocumentActionBase or a descendant class. Again, the rules about parallel hierarchies are also in effect here. The class should be named {transactional document name}Action and should live in the org.kuali.kfs.module.{the module we're working in}.web.struts package.
For the most part, an Action class is made up of two sorts of methods:
- the execute() method. execute() is defined by Struts, and it is called on every single action. That, in turn defers to a given "reaction" method, depending on the "methodToCall" parameter in the URL.
- "reaction" methods - methods that react to certain actions within a page, or, more technically, to an HTTP request to a page.
Pre-defined action methods
Most documents have a lot of actions already: submit for routing, saving, canceling, approving, even creating a new document. Do we have to define actions for all of those? Absolutely not; that is why we're extending KualiTransactionalDocumentActionBase in the first place. We can override these pre-defined action methods if we need to, however, so let's take a look at some of the major actions that are already defined for us. And again, if we are overriding the method, we typically want to call the super version of the method at some point within the method execution.
These are the action methods defined in org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase:
- loadDocument() - called when a pre-existing document is loaded
- createDocument() - called when a new document is created
- reload() - called when the page is reloaded
- save() - called when a document is saved
- route() - called when a document is first submitted for routing
- blanketApprove() - called when a document is blanket approved
- approve() - called when a document is approved
- disapprove() - we're starting a sense a pattern...this method is called when a document gets disapproved
- cancel() - called when a document is canceled by its initiator
- close() - called to close the document and return to the portal
- fyi() and acknowledge() - called when a document is either acknowledged or given "fyi" viewing in workfow
KualiDocumentActionBase also has methods to handle adding and deleting ad hoc routing and the adding or deleting of notes and attachments. There are only two action methods defined in org.kuali.kfs.kns.web.struts.action.KualiTransactionalDocumentActionBase:
- copy() - called when a transactional document is copied
- errorCorrect() - called when a transactional document is error corrected
Also remember that the execute() method can be overridden, which is useful if some controller logic needs to be called every single request/response cycle.
Action methods
An action method is simply a method that is called when a certain action takes place on a page. It can have any name, though it has a set signature: it must return an object of org.apache.struts.action.ActionForward, and it must take in parameters of org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, and javax.servlet.http.HttpServletResponse in that order. Most declarations of action methods also throw java.lang.Exception...just in case.
Other than that, an action method can perform any logic that it needs to. Here's an example from AdvanceDepositAction:
public ActionForward addAdvanceDeposit(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { AdvanceDepositForm adForm = (AdvanceDepositForm) form; AdvanceDepositDocument adDoc = adForm.getAdvanceDepositDocument(); AdvanceDepositDetail newAdvanceDeposit = adForm.getNewAdvanceDeposit(); adDoc.prepareNewAdvanceDeposit(newAdvanceDeposit); // advanceDeposit business rules boolean rulePassed = validateNewAdvanceDeposit(newAdvanceDeposit); if (rulePassed) { // add advanceDeposit adDoc.addAdvanceDeposit(newAdvanceDeposit); // clear the used advanceDeposit AdvanceDepositDetail advanceDepositDetail = new AdvanceDepositDetail(); advanceDepositDetail.setDefautBankCode(); adForm.setNewAdvanceDeposit(advanceDepositDetail); } return mapping.findForward(KFSConstants.MAPPING_BASIC); }
This is a fairly basic method that validates an advance deposit, and if the deposit is good, adds it to the collection of advance deposits and resets the Form's newAdvanceDeposit property. Note that the passed in ActionForm must be cast to the Form class that the method expects, and the last line of the method that returns the forward named "basic": that is, the default JSP page for the document.
Best Practice - Keeping Your Controllers Thin
Best practice generally dictates that controllers are "thin". This is to say, if the business logic in a controller has something to do with validation, it should likely exist in a rule. Otherwise it should likely be in a Spring service. Rules and services are both much easier to customize and test than actual controllers, and therefore, it's typically a good idea to have the least amount of logic in the controller as possible.
Generating new rule events
As promised, we needed to take a look at how rule events are generated by action methods. As you'll remember, we saw that events forced the rules validation framework to validate a certain set of rules on a document, but we said that we'd hold the last piece - the lynch-pin, if you will - until now, when we could see what action methods look like. Here is the addCheck method of the org.kuali.module.financial.web.struts.action.CashReceiptAction Action class:
public ActionForward addCheck(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { CashReceiptForm crForm = (CashReceiptForm) form; CashReceiptDocument crDoc = crForm.getCashReceiptDocument(); Check newCheck = crForm.getNewCheck(); newCheck.setDocumentNumber(crDoc.getDocumentNumber()); // check business rules boolean rulePassed = SpringContext.getBean(KualiRuleService.class).applyRules(new AddCheckEvent(KFSConstants.NEW_CHECK_PROPERTY_NAME, crDoc, newCheck)); if (rulePassed) { // add check crDoc.addCheck(newCheck); // clear the used newCheck crForm.setNewCheck(crDoc.createNewCheck()); } return mapping.findForward(KFSConstants.MAPPING_BASIC); }
This gets an implementation of the KualiRulesService. It then applies the correct rules, by creating a new org.kuali.kfs.fp.document.validation.event.AddCheckEvent, and handing that event the name of the property where the error would have occurred for the error path, the CashReceiptDocument, and the specific newCheck being added. The proper rules are then called, and a boolean variable is passed back to let the action know if the rule passed or not. And there we go, we now know completely how to add new rule events to documents.
Setting up the Struts mapping
For a page to be accessed by Struts, Struts needs to have a mapping, which connects a URL to an Action class. Mappings also define "forwards," JSP pages that should be shown by a controller. Let's take a look at a very basic mapping, the mapping to the KFS portal:
<form-bean name="KualiForm" type="org.kuali.kfs.kns.web.struts.form.KualiForm" /> <action path="/portal" name="KualiForm" type="org.kuali.kfs.kns.web.struts.action.KualiSimpleAction"> <forward name="basic" path="/portal.jsp" /> </action>
Here we see the basic pieces of an action mapping. The form-bean tag defines a name and a Form class. The action tag takes in a path, the unique URL this action should map to, the name of the Form to use, and a type, which is the name of the Action class to use. Within the action tag, at least one (and maybe more) mappings get defined; each mapping has a name unique to the action and a path - the path of the JSP page to render. KFS uses the name "basic" for the default JSP page for most actions.
Of course, we rarely need to set up such mappings, because most modules with transactional documents have a Struts configuration like this:
<action path="/financial*" name="{1}Form" input="/jsp/fp/{1}.jsp" type="org.kuali.kfs.fp.document.web.struts.{1}Action" scope="request" parameter="methodToCall" validate="true" attribute="KualiForm"> <set-property property="cancellable" value="true" /> <forward name="basic" path="/jsp/fp/{1}.jsp" /> </action>
Here, we can see that any URL with the path that starts with the word "financial" will have the portion of the path after "financial" held as a pattern. That pattern is then used to set the right form class, the correct view page for input, the Action class, and the "basic" forward, which simply goes to the JSP page for the document. Here, we see that if we've properly named our Form, Action, and page, everything goes together. For instance, for the URL "/financialAdvanceDeposit.do", this mapping will kick in, tying together the AdvanceDepositForm, the input page AdvanceDeposit.jsp, and the Action class org.kuali.kfs.fp.document.web.struts.AdvanceDepositAction and it will create a forward named "basic" which will point to AdvanceDeposit.jsp.
Naturally, for some pages, we won't want this basic a mapping scheme. In that case, we can set up a "basic" mapping, just like the example above. However, for the most part, it's typically a lot easier to let KFS take care of most of the mapping stuff for us by using the naming defaults.
Kuali documentation is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License.
Kuali software is licensed for use pursuant to the Affero General Public License, version 3.
Copyright © 2014 Kuali, Inc. All rights reserved.
Portions of Kuali are copyrighted by other parties as described in the Acknowledgments screen.
Kuali ® is a registered trademark of the Trustees of Indiana University.