Hi everybody, there is a mail I already sent to this list with another subject, because somebody asked for it. There is some very simple extension to Struts which I implemented, because I needed to have at least a minimum workflow support. Maybe you want to check out this little extension and reuse at least some of the ideas. Here is the mail text I am talking about. I also attached the files again. --- Matthias ******************************************************************* Hi everybody. There were quite a few requests to share the code I talked about in an earlier mail with this subject. The code subclasses some struts classes and provides the following enhancements: 1. Control flow. For each action you can specify the action which must be executed directly before. Thus, you can easily prevent someone from hitting the reload button or using the browser's back button and submit a form for the second time. The mechanism I chose can replace the token mechanism provided by struts and brings some further enhancements. A small example: An action displayLogon displays the logon page of a web application. The next action logonAction defines the action displayLogon as the previous action. An exception is raised (and causes a forward to an appropriate error page) when the previous action is something else then displayLogon. 2. User authentication: For each action you can specify an object which does a check whether the user is authenticated to execute this action. Together with the paradigm (which I would warmly suggest to anybody) to only display jsp pages through actions like displayXXX you can easily do fine grained authorization checks for your whole webapp. 3. Maintenance mode: If your web-site is currently under maintenance set the debug level to -1 and all the actions automatically forward to a maintenance page, which should display an appropriate message the the user. Now, here is a brief code description. For details please have a look at the source code which (I hope) is richly commented. GenericAction ============= Extends Action and is the class from which all other Actions need to be derived. It does the follwing: 1. Check if we are in maintenance mode. If we are, then forward to the maintenance page. 2. Check for the authentication object (of type GenericAuthentication) which is specified by the action's parameter "authtype". If authentication fails, forward to an authentication exception action, which should display a page with a reasonable message. 3. Check whether the action attribute "prevpath" matches the current prevpath value which was stored by the (directly before executed) GenericAction in the user's session. If it does not match forward to a control flow exception action. 4. Now call the method "performAction" (which needs to be overridden by all actions that subclass GenericAction). If debugging is switched off, all exceptions thrown in this method are catched an a forward to an exception action is done. 5. Update the session variable which stores the prevpath value, so it holds the correct value when the next action checks it. AuthenticationException ======================= Exception thrown, when trying to access a resource that you are not allowed to access. GenericAuthentication ===================== Interface that should be implemented by classes which provide authentication checks, which are used by GenericAction. Please note that GenericAction uses only a single instance of each authentication object. So you have to be very careful when you use data members. Normally only the single method check is provided, which does not work on any object's variables. AnyUserAuthentication ===================== Example for an authentication class which implements the interface GenericAuthentication. ApplicationMapping ================== Implementation of enhanced ActionMapping. It defines the following custom properties: - prevpath - The context-relative URI of the previous action. This enables the Action-Classes to compare this value to the value they can easily track. This enables rudimentary flow control checks. - authtype - The name of the authentication class which checks if the user is allowed to execute the mapping's action. For the ApplicationMapping to become effective you have to change the parameter mapping in web.xml like this: <init-param> <param-name>mapping</param-name> <param-value>ApplicationMapping</param-value> </init-param> struts-config.xml ================= The struts configuration file can then look like this: =================================================================================== <!-- ========== Global Forward Definitions ============================== --> <global-forwards> <forward name="authenticationexception" path="/authenticationException.jsp" /> <forward name="maintenance" path="/maintenance.jsp" /> <forward name="controlflowexception" path="/controlFlowException.jsp"/> <forward name="exception" path="/exception.jsp" /> </global-forwards> <!-- ========== Action Mapping Definitions ============================== --> <action-mappings> <!-- Display login --> <action path="/displayLogin" type="SuccessAction"> <forward name="success" path="/index.jsp" /> </action> <!-- Execute the login --> <action path="/loginAction" type="LoginAction" name="loginForm" scope="request" input="/index.jsp"> <set-property property="prevpath" value="/displayLogin"/> <forward name="success" path="/displayPasswordChange.do" /> </action> <!-- Display change password page --> <action path="/displayPasswordChange" type="SuccessAction"> <set-property property="authtype" value="AnyUserAuthentication"/> <forward name="success" path="/passwordChange.jsp" /> </action> <!-- Change password action for an already registered user --> <action path="/passwordChangeAction" type="PasswordChangeAction" name="passwordForm" scope="request" input="/passwordChange.jsp"> <set-property property="authtype" value="AnyUserAuthentication"/> <set-property property="prevpath" value="/displayPasswordChange"/> <forward name="success" path="/passwordChangeSuccess.jsp" /> </action> </action-mappings> =================================================================================== See what happens? - You are not allowed to execute displayPasswordChange or passwordChangeAction, when you are not correctly logged in. The framework does the check for you. - passwordChangeAction and loginAction are only executed, if displayPasswordChange or displayLogin respectively, have been executed directly before. I found these enhancements very helpful for my web applications, because it deals with some basic issues you always have to solve in webapps. Therefore I would like to see these enhancements incorporated into the standard struts framework. I plan to propose this officially after Struts 1.0 has been released. I am sending it to the list now, because some of you asked for it. In the meantime I would appreciate any feedback (both positive or negative ones). Hope you have fun, --- Matthias ******************************************************************* Jonathan Asbell wrote: > Is there anyone on the list that actually HAS experience developing with > workflow engines? > > ----- Original Message ----- > From: "Rey Francois" <[EMAIL PROTECTED]> > To: <[EMAIL PROTECTED]> > Sent: Wednesday, June 06, 2001 4:40 AM > Subject: RE: Work flow RFC > > > >>Another possibility is to develop extensions for the TogetherJ CASE tool. >> > It > >>is entirely written in Java, therefore can run on most platform, and from >> > my > >>understanding it is possible to define new diagram types and patterns. >> >>This may not directly relate to workflow, but we have in our team created >>the concept of a request servicing diagram which is a class diagram >>representing the objects involved in servicing a request. Particularly on >>this diagram we display the request object and the action it is mapped to. >>Although we have not done it yet, it is quite possible to develop a >> > pattern > >>that generates the corresponding action mapping entry in the >>struts-config.xml, and vice-versa. >> >>François Rey >>Financial WebSuite >>Capco >>http://www.capco.com/ >> >> >>-----Original Message----- >>From: Craig Tataryn [mailto:[EMAIL PROTECTED]] >>Sent: 05 June 2001 20:06 >>To: Jonathan >>Cc: [EMAIL PROTECTED] >>Subject: Re: Work flow RFC >> >> >>Is this a workflow editor or just a configuration editor (which would be >>nice >>for struts)? >> >>craig. >> >>Jonathan wrote: >> >> >>>Again, Ive got to say look at the Barracuda project. They have one of >>> >>these >> >>>gui configurers. Check it out at >>>http://barracuda.enhydra.org/Barracuda/GetBConfig.event >>> >>>----- Original Message ----- >>>From: "Craig Tataryn" <[EMAIL PROTECTED]> >>>To: <[EMAIL PROTECTED]> >>>Sent: Tuesday, June 05, 2001 12:28 PM >>>Subject: Work flow RFC >>> >>> >>>>Hi, I would like your comments for the workflow item on our TODO list. >>>>Currently this is how I've envisioned the workflow project: >>>> >>>>1) A nice GUI type Applet or Application that has visual constructs >>>>which can be connected in a Visio type manner to create an Activity >>>>diagram or some other type of flow diagram. >>>> >>>>2) This diagram will be persisted in an XML file which holds meta data >>>>for the elements in diagram (position, type of construct (controller, >>>>flat html page, cgi script, flow arrow, etc..)). >>>> >>>>3) The diagram can be exported to a struts config file via XSLT (i.e. >>>>workflow.xml -> workflow2struts.xsl -> struts-config.xml) >>>> >>>>4) A diagram can also be imported from a struts-config.xml file via >>>> > XSLT > >>>>(i.e. struts-config.xml -> struts2workflow.xsl -> workflow.xml). Of >>>>course some sort of "pretty layout" code would have to be used to >>>>un-jumble the mess of constructs that are sucked out of the >>>>struts-config.xml file (i.e. take a guess at proper positioning >>>>information). >>>> >>>>The GUI should employ some sort of extensibility mechanism like BSF >>>>(http://oss.software.ibm.com/developerworks/projects/bsf) or Bean >>>> > Shell > >>>>(http://www.beanshell.org/) to allow users to plug-in their own >>>>functionality (i.e. validation code) without jeopardizing the core >>>> > code > >>>>(what I call the Emeril Lagasse technique -- BAM!). >>>> >>>>I realize this is a very high level look at the TODO but I think as we >>>>get more comments we will get more granular and can start dishing out >>>>segments. >>>> >>>>Let me know what you think. >>>> >>>><tataryn:craig/> >>>> >>************************************************************************ >>The information in this email is confidential and is intended solely >>for the addressee(s). >>Access to this email by anyone else is unauthorised. If you are not >>an intended recipient, you must not read, use or disseminate the >>information contained in the email. >>Any views expressed in this message are those of the individual >>sender, except where the sender specifically states them to be >>the views of Capco. >> >>http://www.capco.com >>*********************************************************************** >> >> y
package board; import javax.servlet.http.*; import javax.servlet.*; /** * Class that checks if any user (no matter if customer or admin) is correctly logged on * * @author M. Bauer * @version $Revision: 1.1 $ $Date: 2001/04/06 13:11:10 $ */ public class AnyUserAuthentication implements GenericAuthentication { /** * Checks whether a user is logged on * @param session the user's session object * @return true, if the user passed this check, false otherwise */ public boolean check(HttpSession session) { User user = User.get(session); return (user != null); } }
package board; import org.apache.struts.action.ActionMapping; /** * Implementation of enhanced <strong>ActionMapping</strong>. * It defines the following custom properties: * <ul> * <li><b>prevpath</b> - The context-relative URI of the previous * action. This enables the Action-Classes to compare this value to the * value they can easily track. This enables rudimentary flow control checks.</li> * <li><b>authtype</b> - The name of the authentication class which checks if the user * is allowed to execute the mapping's action.</li> * </ul> * * @author M. Bauer * @version $Revision: 1.1 $ $Date: 2001/04/06 13:11:10 $ */ public final class ApplicationMapping extends ActionMapping { /** * The only allowed previous path for this mapping */ private String prevpath = null; /** * Return prevpath for this mapping. */ public String getPrevpath() { return (this.prevpath); } /** * Set prevpath for this mapping. * @param prevpath the prevpath for this mapping */ public void setPrevpath(String prevpath) { this.prevpath = prevpath; } /** * The authentication class which checks the user's permission */ private String authtype = null; /** * Return authtype for this mapping. */ public String getAuthtype() { return (this.authtype); } /** * Set authtype for this mapping. * @param authtype the authtype for this mapping */ public void setAuthtype(String authtype) { this.authtype = authtype; } /** * Constructor */ public ApplicationMapping() { super(); setValidate(false); } }
package board; import javax.servlet.ServletException; /** * Exception thrown, when trying to access a resource that you are not allowed to access * * @author M. Bauer * @version $Revision: 1.1 $ $Date: 2001/04/06 13:11:10 $ */ public class AuthenticationException extends ServletException { /** * Constructs the Exception with no specified detail message. */ public AuthenticationException() { super(); } /** * Constructs the Exception with the specified detail message. * @param s the detail message. */ public AuthenticationException(String s) { super(s); } }
package board; import org.apache.struts.action.*; import javax.servlet.http.*; import javax.servlet.*; import java.io.*; /** * Action from which all the other action in this application are derived. * * @author M. Bauer * @version $Revision: 1.1 $ $Date: 2001/04/06 13:11:10 $ */ public abstract class GenericAction extends Action { /* session key for the previous state */ public static final String PREVPATH_KEY = "prevpath"; /* loglevels */ public static final int LOGLEVEL_MAINTENANCE = -1; public static final int LOGLEVEL_OFF = 0; public static final int LOGLEVEL_ERROR = 1; public static final int LOGLEVEL_NOTIFY = 2; public static final int LOGLEVEL_TRACE = 3; /* forward constants */ public static final String FORWARD_SUCCESS = "success"; public static final String FORWARD_FAILURE = "failure"; public static final String FORWARD_AUTHENTICATIONEXCEPTION = "authenticationexception"; public static final String FORWARD_CONTROLFLOWEXCEPTION = "controlflowexception"; public static final String FORWARD_MAINTENANCE = "maintenance"; public static final String FORWARD_EXCEPTION = "exception"; /** * Method called by perform which must be overridden by descendents of this class * @param mapping The ActionMapping used to select this instance * @param actionForm The ActionForm bean for this request (RegisterForm) * @param request The HTTP request we are processing * @param response The HTTP response we are creating * @return ActionForward instance that describes where and how control should * be forwarded to. * @exception IOException if an input/output error occurs * @exception ServletException if a servlet exception occurs */ public abstract ActionForward performAction (ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException; /** * Process the action request by calling performAction * Returns an <code>ActionForward</code> instance describing where and how * control should be forwarded, or <code>null</code> if the response has * already been completed. * * @param mapping The ActionMapping used to select this instance * @param actionForm The ActionForm bean for this request (RegisterForm) * @param request The HTTP request we are processing * @param response The HTTP response we are creating * @return ActionForward instance that describes where and how control should * be forwarded to. * @exception IOException if an input/output error occurs * @exception ServletException if a servlet exception occurs */ public final ActionForward perform (ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { ActionForward retVal; ApplicationMapping applicationMapping = (ApplicationMapping) mapping; int debugLevel = servlet.getDebug(); String definedPrevpath = applicationMapping.getPrevpath(); String authtype = applicationMapping.getAuthtype(); String currentPath = mapping.getPath(); HttpSession session = request.getSession(true); String currentPrevpath = (String)session.getAttribute(PREVPATH_KEY); ServletContext servletContext = servlet.getServletContext(); /* if we are in maintenance mode, then we always jump to the maintenance page */ if (LOGLEVEL_MAINTENANCE == debugLevel) { retVal = mapping.findForward(FORWARD_MAINTENANCE); } else { if (LOGLEVEL_TRACE <= debugLevel) { servlet.log("Current path: " + currentPath); servlet.log("Previous path: " + currentPrevpath); servlet.log("Defined prevpath: " + definedPrevpath); servlet.log("Authtype: " + authtype); } /* * check if authentication allows execution of this action: first create the authentication object if it * does not yet exist */ GenericAuthentication authentication = null; if (null != authtype) { authentication = (GenericAuthentication) servletContext.getAttribute(authtype); if (null == authentication) { if (LOGLEVEL_TRACE <= debugLevel) { servlet.log("Authentication object does not yet exist. I will try to create one."); } try { authentication = (GenericAuthentication) Class.forName(authtype).newInstance(); servletContext.setAttribute(authtype, authentication); } catch (ClassNotFoundException classNotFoundException) { throw new ServletException("ClassNotFoundException: " + classNotFoundException.getMessage()); } catch (InstantiationException instantiationException) { throw new ServletException("InstantiationException: " + instantiationException.getMessage()); } catch (IllegalAccessException illegalAccessException) { throw new ServletException("IllegalAccessException: " + illegalAccessException.getMessage()); } } } /* now do the authentication check */ if ( (null != authentication) && (!authentication.check(session)) ) { if (LOGLEVEL_TRACE <= debugLevel) { servlet.log("Authentication failed!"); } retVal = mapping.findForward(FORWARD_AUTHENTICATIONEXCEPTION); } /* check if flow control is ok */ else if ( (null != definedPrevpath) && (!definedPrevpath.equals(currentPrevpath)) ) { if (LOGLEVEL_TRACE <= debugLevel) { servlet.log("Previous path does not match prevpath!"); } retVal = mapping.findForward(FORWARD_CONTROLFLOWEXCEPTION); } /* if debugging is switched off, all exceptions are catched an we are forwarding to an appropriate page */ else if (LOGLEVEL_OFF == debugLevel) { try { retVal = performAction(mapping, form, request, response); } catch (AuthenticationException e) { retVal = mapping.findForward(FORWARD_AUTHENTICATIONEXCEPTION); } catch (Exception e) { retVal = mapping.findForward(FORWARD_EXCEPTION); } } /* debugging is switched on, so we let exceptions wander through */ else { try { retVal = performAction(mapping, form, request, response); } catch (AuthenticationException e) { retVal = mapping.findForward(FORWARD_AUTHENTICATIONEXCEPTION); } } /* * update prevpath for the next action: only update, if we are not forwarding back to the input page * (this usually happens, when input validation failed) */ if ( (null == retVal) || (!retVal.getPath().equals(mapping.getInput()) ) ) { if (LOGLEVEL_TRACE <= debugLevel) { servlet.log("Setting prevpath to " + currentPath); } request.getSession(true).setAttribute(PREVPATH_KEY, currentPath); } } if (LOGLEVEL_TRACE <= debugLevel) { if (null != retVal) { servlet.log("Forward to: " + retVal.getPath()); } } return retVal; } }
package board; import org.apache.struts.action.*; import javax.servlet.http.*; import javax.servlet.*; import java.io.*; /** * Interface that should be implemented by classes which provide authentication checks, * which are used by GenericAction. * Please note that GenericAction uses only a single instance of each authentication * object. So you have to be very careful when you use data members. Normally only the * single method <code>check</code> is provided, which does not work on any object's * variables. * * @author M. Bauer * @version $Revision: 1.1 $ $Date: 2001/04/06 13:11:10 $ */ public interface GenericAuthentication { /** * Check the user's authentication. * @param session the user's session object * @return true, if the user passed this check, false otherwise */ public boolean check(HttpSession session); }