Wanted to submit the following code for comment from the Struts dev
group.  Being faced with a pretty large development project and a
development environment that oftentimes had one developer working on the
Web tier code (Struts) and another developer working at the middle tier
level (EJB's),  an attempt was made to alleviate the need to update
action code and workflow to accommodate changes in the web tier/middle
tier contract.  

Specifically this related to how business logic exceptions were defined
and handled.  We have found that within our Struts actions we often see
large amounts of business logic exception handling which usually does
nothing more then configure a message and forward the user to some kind
or error url or back to the previous page with a validation type error
message.  Not only is this verbose and repetitious, but it is fragile to
changes in the middle tier since every time a new exception is added, a
struts-knowledgeable person needs go into all the struts code and add
exception handling for it.  As with other repitious and tedious code,
our feeling was that the best way to handle this was to allow for
exception handling to be declarative and configurable through the
struts-config.xml.  We have therefore created a scheme where exceptions
are treated much the same as forwards in the current Struts code-line.
Each action mapping can define any number of exceptions that may occur
and how they should be handled.  The handling of the exceptions consists
of the definition of a key value (error message, etc) , and a path
(optional - input of the action would be the default).  When an
exception occurs that can be handled the ActionException (consisting of
key, and path) is placed into the user's session under a key constant -
org.apache.struts.action.ACTION_EXCEPTION.  It can then be handled
however is necessary by an app.

Exception hierarchies are supported just as they would be in a typically
try{}catch code block through an optional mapping parameter to determine
whether or not to support the hierarchy.  When attempting to match and
handle an exception first a specific match would be sought, if one could
not be found, then a search for any assignable exception would be made.
The first match would end the search.

The following would be an example of an action mapping:

<action path="/handleSomething" 
        name="someForm"
        validate="false"
        input="/someUri.....">

        <forward ...just like always/>
        <exception key="some.key" 
                     type="some.package.business.logic.Exception"/>
        <exception key="some.other.key"
                         type="some.package.business.logic.Exception2"
                     path="/someotherUri..."/>
        <exception key="general"
                     type="java.lang.Exception"/>
        <exception key="ejb"
                     type="javax.ejb.EjbException"
                     hierarchachal="false"/>
</action>

The following outlines how each of the exceptions would be handled:

1) If an some.package.business.logic.Exception is throw from the Action
perform method, the client will be dispatched to the input of the form
with an ActionException placed into the session under a specific key.

2) If an some.package.business.logic.Exception2 is thrown, the client
will be dispatched to the path specified in that mapping with the
ActionException placed in the session.

3) If anything other than the previous 2 exception or an EjbException is
thrown, the client would be re-directed to the input of the form, again
with the ActionException placed into the session.

4) If an EjbException is thrown (it's children would not be handled by
this) then the client would be dispatched with that ActionException in
the session under a defined constant value.

The changes to the code base consisted of:

1) Addition of ActionException and ActionExceptions classes.  Very much
like the existing mapping classes.
2) Change of the perform() method signature to throw Exception rather
than IOException and ServletException
3) Update the processActionPerform() method of the ActionServlet perform
the try{}cactch{} and to map resultant exceptions.
4) Add the digesting into the ActionServlet init()
5) Add the ActionExceptions reference into the ActionMapping.

I have attached the code:
 <<changes.zip>> 

As well as diffs of the changes to the ActionServlet and Action classes
 <<Action-diff.txt>>  <<ActionServlet-diff.txt>> 

A couple of major @todo's in the code would be the implementation of
global-exceptions, and a currently hard-coded exception message in any
unhandled exception that is wrapped into a ServletException.  I wanted
to see if there was interest for this type of feature, if so I can
easily put the rest of it together and submit it.

Thanks, any comments or feedback would be appreciated.

changes.zip

2,4c2,4
<  * $Header: //dev/components/struts/1.0/org/apache/struts/action/Action.java#2 $
<  * $Revision: #2 $
<  * $Date: 2001/10/29 $
---
>  * $Header: 
>/home/cvspublic/jakarta-struts/src/share/org/apache/struts/action/Action.java,v 1.25 
>2001/08/16 03:52:09 craigmcc Exp $
>  * $Revision: 1.25 $
>  * $Date: 2001/08/16 03:52:09 $
111c111
<  * @version $Revision: #2 $ $Date: 2001/10/29 $
---
>  * @version $Revision: 1.25 $ $Date: 2001/08/16 03:52:09 $
137a138,144
>     /**
>      * The request attributes key under which your action should store an
>      * <code>org.apache.struts.action.ActionMessages</code> object, if you
>      * are using the corresponding custom tag library elements.
>      */
>     public static final String MESSAGE_KEY =
>       "org.apache.struts.action.ACTION_MESSAGE";
232,237d238
<     /**
<      * The session attribute key under which to store an exception that
<      * occured during action dispatching that could be handled.
<      */
<     public static final String ACTION_EXCEPTION_KEY =
<         "org.apache.struts.action.ACTION_EXCEPTION";
290,319d290
<      *
<      * @deprecated Use the new perform() method without a servlet argument
<      *
<      * @param servlet The ActionServlet instance owning this Action
<      * @param mapping The ActionMapping used to select this instance
<      * @param actionForm The optional ActionForm bean for this request (if any)
<      * @param request The servlet request we are processing
<      * @param response The servlet response we are processing
<      *
<      * @exception IOException if an input/output error occurs
<      * @exception ServletException if a servlet exception occurs
<      */
<     public ActionForward perform(ActionServlet servlet,
<                                  ActionMapping mapping,
<                                  ActionForm form,
<                                  ServletRequest request,
<                                  ServletResponse response)
<         throws Exception {
< 
<         return (perform(mapping, form, request, response));
< 
<     }
< 
< 
<     /**
<      * Process the specified non-HTTP request, and create the corresponding
<      * non-HTTP response (or forward to another web component that will create
<      * it).  Return an <code>ActionForward</code> instance describing where
<      * and how control should be forwarded, or <code>null</code> if the
<      * response has already been completed.
336c307
<         throws Exception {
---
>         throws IOException, ServletException {
350,379d320
<      * Process the specified HTTP request, and create the corresponding
<      * HTTP response (or forward to another web component that will create
<      * it).  Return an <code>ActionForward</code> instance describing where
<      * and how control should be forwarded, or <code>null</code> if the
<      * response has already been completed.
<      *
<      * @deprecated Use the new perform() method without a servlet argument
<      *
<      * @param servlet The ActionServlet instance owning this Action
<      * @param mapping The ActionMapping used to select this instance
<      * @param actionForm The optional ActionForm bean for this request (if any)
<      * @param request The servlet request we are processing
<      * @param response The servlet response we are processing
<      *
<      * @exception IOException if an input/output error occurs
<      * @exception ServletException if a servlet exception occurs
<      */
<     public ActionForward perform(ActionServlet servlet,
<                                  ActionMapping mapping,
<                                  ActionForm form,
<                                  HttpServletRequest request,
<                                  HttpServletResponse response)
<         throws Exception {
< 
<         return (perform(mapping, form, request, response));
< 
<     }
< 
< 
<     /**
398c339
<       throws Exception {
---
>       throws IOException, ServletException {
496c437,459
<         // Retrieve the saved transaction token from our session
---
>         return (isTokenValid(request, false));
> 
>     }
> 
> 
>     /**
>      * Return <code>true</code> if there is a transaction token stored in
>      * the user's current session, and the value submitted as a request
>      * parameter with this action matches it.  Returns <code>false</code>
>      * <ul>
>      * <li>No session associated with this request</li>
>      * <li>No transaction token saved in the session</li>
>      * <li>No transaction token included as a request parameter</li>
>      * <li>The included transaction token value does not match the
>      *     transaction token in the user's session</li>
>      * </ul>
>      *
>      * @param request The servlet request we are processing
>      * @param reset Should we reset the token after checking it?
>      */
>     protected boolean isTokenValid(HttpServletRequest request, boolean reset) {
> 
>         // Retrieve the current session for this request
500c463,469
<         String saved = (String) session.getAttribute(TRANSACTION_TOKEN_KEY);
---
> 
>         synchronized (session) {
> 
>             // Retrieve the transaction token from this session, and
>             // reset it if requested
>             String saved = (String)
>                 session.getAttribute(TRANSACTION_TOKEN_KEY);
502a472,473
>             if (reset)
>                 session.removeAttribute(TRANSACTION_TOKEN_KEY);
513a485,486
>     }
> 
554a528,549
>     /**
>      * Save the specified messages keys into the appropriate request
>      * attribute for use by the &lt;struts:messages&gt; tag (if messages="true" is 
>set),
>      * if any messages are required.  Otherwise, ensure that the request 
>      * attribute is not created.
>      *
>      * @param request         The servlet request we are processing
>      * @param messages        Messages object
>      */
>     protected void saveMessages(HttpServletRequest request,
>                               ActionMessages messages) {
> 
>       // Remove any messages attribute if none are required
>       if ((messages == null) || messages.empty()) {
>           request.removeAttribute(MESSAGE_KEY);
>           return;
>       }
> 
>       // Save the messages we need
>       request.setAttribute(MESSAGE_KEY, messages);
> 
>     }
2,4c2,4
<  * $Header: 
//dev/components/struts/1.0/org/apache/struts/action/ActionServlet.java#3 $
<  * $Revision: #3 $
<  * $Date: 2001/10/30 $
---
>  * $Header: 
>/home/cvspublic/jakarta-struts/src/share/org/apache/struts/action/ActionServlet.java,v
> 1.76 2001/10/07 04:48:08 martinc Exp $
>  * $Revision: 1.76 $
>  * $Date: 2001/10/07 04:48:08 $
83,84c83,85
< import org.apache.struts.digester.Digester;
< import org.apache.struts.digester.Rule;
---
> import org.apache.commons.collections.FastHashMap;
> import org.apache.commons.digester.Digester;
> import org.apache.commons.digester.Rule;
86c87
< import org.apache.struts.util.FastHashMap;
---
> import org.apache.struts.upload.MultipartRequestWrapper;
92,93c93
< import org.apache.struts.upload.MultipartRequestWrapper;
< import org.xml.sax.AttributeList;
---
> import org.xml.sax.Attributes;
175c175,176
<  *     <code>MessageResources</code> object.</li>
---
>  *     <code>MessageResources</code> object.
>  *     [org.apache.struts.util.PropertyMessageResourcesFactory]</li>
195c196
<  *     if there is not a Locale object there already.</li>
---
>  *     if there is not a Locale object there already. [true]</li>
214c215,216
<  *     file uploads.  [org.apache.struts.upload.DiskMultipartRequestHandler]
---
>  *     file uploads. If set to <code>none</code>, disables Struts multipart
>  *     request handling.  [org.apache.struts.upload.DiskMultipartRequestHandler]
226,227d227
<  * <li><strong>validate</strong> - Are we using the new configuration file
<  *     format?  [true]</li>
233c233
<  * @version $Revision: #3 $ $Date: 2001/10/30 $
---
>  * @version $Revision: 1.76 $ $Date: 2001/10/07 04:48:08 $
317,322d316
<      * The Java class name of the ActionException implementation class to use.
<      */
<     protected String exceptionClass =
<       "org.apache.struts.action.ActionException";
< 
<     /**
373a368,369
>         "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN",
>         "/org/apache/struts/resources/struts-config_1_1.dtd",
396,401d391
<      * Are we using the new configuration file format?
<      */
<     protected boolean validate = true;
<     
< 
<     /**
1115c1105
<      * Construct and return a digester that uses the new configuration
---
>      * Construct and return a digester that uses the standard configuration
1123a1114
>         digester.setNamespaceAware(true);
1165,1175d1155
<         digester.addObjectCreate
<             ("struts-config/action-mappings/action/exception",
<              exceptionClass, "className");
<         digester.addSetProperties
<             ("struts-config/action-mappings/action/exception");
<         digester.addSetNext("struts-config/action-mappings/action/exception",
<                             "addException",
<                             "org.apache.struts.action.ActionException");
< 
<         
<         
1208,1247d1187
<      * Construct and return a digester that uses the old configuration
<      * file format.
<      */
<     protected Digester initDigesterOld(int detail) {
< 
<       // Initialize a new Digester instance
<       Digester digester = new Digester();
<       digester.push(this);
<       digester.setDebug(detail);
<       digester.setValidating(false);
< 
<       // Configure the processing rules
<       digester.addObjectCreate("action-mappings/action", mappingClass,
<                                "className");
<       digester.addSetProperties("action-mappings/action");
<       digester.addSetNext("action-mappings/action", "addMapping",
<                           "org.apache.struts.action.ActionMapping");
<       digester.addObjectCreate("action-mappings/action/forward",
<                                forwardClass, "className");
<       digester.addSetProperties("action-mappings/action/forward");
<       digester.addSetNext("action-mappings/action/forward", "addForward",
<                           "org.apache.struts.action.ActionForward");
<       digester.addSetProperty("action-mappings/action/forward/property",
<                               "name", "value");
<       digester.addSetProperty("action-mappings/action/property",
<                               "name", "value");
<       digester.addObjectCreate("action-mappings/forward",
<                                forwardClass, "className");
<       digester.addSetProperties("action-mappings/forward");
<       digester.addSetNext("action-mappings/forward", "addForward",
<                           "org.apache.struts.action.ActionForward");
<       digester.addSetProperty("action-mappings/forward/property",
<                               "name", "value");
< 
<       return (digester);
< 
<     }
< 
< 
<     /**
1288,1297d1227
<       // Initialize the format selector flag
<       value = getServletConfig().getInitParameter("validate");
<       if (value != null) {
<           if (value.equalsIgnoreCase("true") ||
<               value.equalsIgnoreCase("yes"))
<               validate = true;
<           else
<               validate = false;
<       }
< 
1337,1341c1267
<       Digester digester = null;
<       if (validate)
<           digester = initDigester(detail);
<       else
<           digester = initDigesterOld(detail);
---
>       Digester digester = initDigester(detail);
1359,1373d1284
<         // Transitional support for old format
<         if (!validate) {
<             String paths[] = mappings.findMappings();
<             for (int i = 0; i < paths.length; i++) {
<                 String name =
<                     mappings.findMapping(paths[i]).getName();
<                 if (name == null)
<                     continue;
<                 ActionFormBean formBean = new ActionFormBean();
<                 formBean.setName(name);
<                 formBean.setType(name);
<                 formBeans.addFormBean(formBean);
<             }
<         }
< 
1429a1341
>         digester.setNamespaceAware(true);
1445a1358,1359
>         if (debug >= 1)
>             log("Scanning web.xml for controller servlet mapping");
1498a1413,1415
>             if (classValue.equals("none"))
>                 multipartClass = null;
>             else
1803,1806c1720
<       ActionForward forward = null;
<         
<         try{
<             //Try and perform the action
---
>       ActionForward forward =
1808,1835d1721
<         }catch(Exception ex){
<             //If an exception occurs, try and handle it gracefully
<             
<             //Look for an exception mapping
<             ActionException ae = mapping.findException(ex.getClass());
<             
<             //If one is found, place it in the session
<             if (ae !=null){
<                 request.getSession().setAttribute(Action.ACTION_EXCEPTION_KEY, ae);
<                 String path;
<                 
<                 //build the forward from the exception mapping if it exists
<                 //or from the form 
<                 if (ae.getPath() !=null){
<                     path = ae.getPath();
<                 }else{
<                     path = mapping.getInput();
<                 }
<                 
<                 forward = new ActionForward(path);
<             }else{
<                 throw new ServletException("[ActionServlet] - Unhandled Exception",
<                 ex);
<             }
<             
<         }
<         
<         //return the forward
1989a1876,1877
>         if (mapping != null)
>             request.setAttribute(Action.MAPPING_KEY, mapping);
2106,2108d1993
<         //also pass the mapping through the request
<         request.setAttribute(Action.MAPPING_KEY,
<                              mapping);
2226c2111
<     public void begin(AttributeList attributes) throws Exception {
---
>     public void begin(Attributes attributes) throws Exception {
2231c2116
<             if ("key".equals(attributes.getName(i))) {
---
>             if ("key".equals(attributes.getQName(i))) {
2,4c2,4
<  * $Header: 
//dev/components/struts/1.0/org/apache/struts/action/ActionServlet.java#3 $
<  * $Revision: #3 $
<  * $Date: 2001/10/30 $
---
>  * $Header: 
>/home/cvspublic/jakarta-struts/src/share/org/apache/struts/action/ActionServlet.java,v
> 1.76 2001/10/07 04:48:08 martinc Exp $
>  * $Revision: 1.76 $
>  * $Date: 2001/10/07 04:48:08 $
83,84c83,85
< import org.apache.struts.digester.Digester;
< import org.apache.struts.digester.Rule;
---
> import org.apache.commons.collections.FastHashMap;
> import org.apache.commons.digester.Digester;
> import org.apache.commons.digester.Rule;
86c87
< import org.apache.struts.util.FastHashMap;
---
> import org.apache.struts.upload.MultipartRequestWrapper;
92,93c93
< import org.apache.struts.upload.MultipartRequestWrapper;
< import org.xml.sax.AttributeList;
---
> import org.xml.sax.Attributes;
175c175,176
<  *     <code>MessageResources</code> object.</li>
---
>  *     <code>MessageResources</code> object.
>  *     [org.apache.struts.util.PropertyMessageResourcesFactory]</li>
195c196
<  *     if there is not a Locale object there already.</li>
---
>  *     if there is not a Locale object there already. [true]</li>
214c215,216
<  *     file uploads.  [org.apache.struts.upload.DiskMultipartRequestHandler]
---
>  *     file uploads. If set to <code>none</code>, disables Struts multipart
>  *     request handling.  [org.apache.struts.upload.DiskMultipartRequestHandler]
226,227d227
<  * <li><strong>validate</strong> - Are we using the new configuration file
<  *     format?  [true]</li>
233c233
<  * @version $Revision: #3 $ $Date: 2001/10/30 $
---
>  * @version $Revision: 1.76 $ $Date: 2001/10/07 04:48:08 $
317,322d316
<      * The Java class name of the ActionException implementation class to use.
<      */
<     protected String exceptionClass =
<       "org.apache.struts.action.ActionException";
< 
<     /**
373a368,369
>         "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN",
>         "/org/apache/struts/resources/struts-config_1_1.dtd",
396,401d391
<      * Are we using the new configuration file format?
<      */
<     protected boolean validate = true;
<     
< 
<     /**
1115c1105
<      * Construct and return a digester that uses the new configuration
---
>      * Construct and return a digester that uses the standard configuration
1123a1114
>         digester.setNamespaceAware(true);
1165,1175d1155
<         digester.addObjectCreate
<             ("struts-config/action-mappings/action/exception",
<              exceptionClass, "className");
<         digester.addSetProperties
<             ("struts-config/action-mappings/action/exception");
<         digester.addSetNext("struts-config/action-mappings/action/exception",
<                             "addException",
<                             "org.apache.struts.action.ActionException");
< 
<         
<         
1208,1247d1187
<      * Construct and return a digester that uses the old configuration
<      * file format.
<      */
<     protected Digester initDigesterOld(int detail) {
< 
<       // Initialize a new Digester instance
<       Digester digester = new Digester();
<       digester.push(this);
<       digester.setDebug(detail);
<       digester.setValidating(false);
< 
<       // Configure the processing rules
<       digester.addObjectCreate("action-mappings/action", mappingClass,
<                                "className");
<       digester.addSetProperties("action-mappings/action");
<       digester.addSetNext("action-mappings/action", "addMapping",
<                           "org.apache.struts.action.ActionMapping");
<       digester.addObjectCreate("action-mappings/action/forward",
<                                forwardClass, "className");
<       digester.addSetProperties("action-mappings/action/forward");
<       digester.addSetNext("action-mappings/action/forward", "addForward",
<                           "org.apache.struts.action.ActionForward");
<       digester.addSetProperty("action-mappings/action/forward/property",
<                               "name", "value");
<       digester.addSetProperty("action-mappings/action/property",
<                               "name", "value");
<       digester.addObjectCreate("action-mappings/forward",
<                                forwardClass, "className");
<       digester.addSetProperties("action-mappings/forward");
<       digester.addSetNext("action-mappings/forward", "addForward",
<                           "org.apache.struts.action.ActionForward");
<       digester.addSetProperty("action-mappings/forward/property",
<                               "name", "value");
< 
<       return (digester);
< 
<     }
< 
< 
<     /**
1288,1297d1227
<       // Initialize the format selector flag
<       value = getServletConfig().getInitParameter("validate");
<       if (value != null) {
<           if (value.equalsIgnoreCase("true") ||
<               value.equalsIgnoreCase("yes"))
<               validate = true;
<           else
<               validate = false;
<       }
< 
1337,1341c1267
<       Digester digester = null;
<       if (validate)
<           digester = initDigester(detail);
<       else
<           digester = initDigesterOld(detail);
---
>       Digester digester = initDigester(detail);
1359,1373d1284
<         // Transitional support for old format
<         if (!validate) {
<             String paths[] = mappings.findMappings();
<             for (int i = 0; i < paths.length; i++) {
<                 String name =
<                     mappings.findMapping(paths[i]).getName();
<                 if (name == null)
<                     continue;
<                 ActionFormBean formBean = new ActionFormBean();
<                 formBean.setName(name);
<                 formBean.setType(name);
<                 formBeans.addFormBean(formBean);
<             }
<         }
< 
1429a1341
>         digester.setNamespaceAware(true);
1445a1358,1359
>         if (debug >= 1)
>             log("Scanning web.xml for controller servlet mapping");
1498a1413,1415
>             if (classValue.equals("none"))
>                 multipartClass = null;
>             else
1803,1806c1720
<       ActionForward forward = null;
<         
<         try{
<             //Try and perform the action
---
>       ActionForward forward =
1808,1835d1721
<         }catch(Exception ex){
<             //If an exception occurs, try and handle it gracefully
<             
<             //Look for an exception mapping
<             ActionException ae = mapping.findException(ex.getClass());
<             
<             //If one is found, place it in the session
<             if (ae !=null){
<                 request.getSession().setAttribute(Action.ACTION_EXCEPTION_KEY, ae);
<                 String path;
<                 
<                 //build the forward from the exception mapping if it exists
<                 //or from the form 
<                 if (ae.getPath() !=null){
<                     path = ae.getPath();
<                 }else{
<                     path = mapping.getInput();
<                 }
<                 
<                 forward = new ActionForward(path);
<             }else{
<                 throw new ServletException("[ActionServlet] - Unhandled Exception",
<                 ex);
<             }
<             
<         }
<         
<         //return the forward
1989a1876,1877
>         if (mapping != null)
>             request.setAttribute(Action.MAPPING_KEY, mapping);
2106,2108d1993
<         //also pass the mapping through the request
<         request.setAttribute(Action.MAPPING_KEY,
<                              mapping);
2226c2111
<     public void begin(AttributeList attributes) throws Exception {
---
>     public void begin(Attributes attributes) throws Exception {
2231c2116
<             if ("key".equals(attributes.getName(i))) {
---
>             if ("key".equals(attributes.getQName(i))) {
--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to