View Javadoc

1   /*
2    * Copyright 1999-2001,2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */ 
16  
17  package org.apache.commons.workflow.web;
18  
19  
20  import java.io.InputStream;
21  import java.io.IOException;
22  import javax.servlet.ServletException;
23  import javax.servlet.UnavailableException;
24  import javax.servlet.http.HttpServlet;
25  import javax.servlet.http.HttpServletRequest;
26  import javax.servlet.http.HttpServletResponse;
27  import javax.servlet.http.HttpSession;
28  import org.apache.commons.digester.Digester;
29  import org.apache.commons.workflow.Activity;
30  import org.apache.commons.workflow.Context;
31  import org.apache.commons.workflow.ContextEvent;
32  import org.apache.commons.workflow.ContextListener;
33  import org.apache.commons.workflow.Step;
34  import org.apache.commons.workflow.StepException;
35  import org.apache.commons.workflow.base.BaseRuleSet;
36  import org.apache.commons.workflow.core.CoreRuleSet;
37  import org.apache.commons.workflow.io.IoRuleSet;
38  import org.apache.commons.workflow.web.WebContext;
39  import org.apache.commons.workflow.web.WebRuleSet;
40  
41  
42  /**
43   * <p>Demonstration servlet that illustrates one way that workflow support can
44   * be integrated into web applications (or web services) without any
45   * dependency on application frameworks.  For this implementation, a servlet
46   * <em>definition</em> (plus one or more servlet <em>mappings</em>) will be
47   * associated with each <code>Activity</code> supported by this web
48   * application.</p>
49   *
50   * <p>Initialization parameters (defaults in square brackets):</p>
51   * <ul>
52   * <li><strong>activity</strong> - Context-relative resource path to the
53   *     definition file for the Activity that is supported by this servlet.</li>
54   * <li><strong>attribute</strong> - Name of the session attribute under
55   *     which our current <code>Context</code> implementation is stored.
56   *     [org.apache.commons.workflow.web.CONTEXT]</li>
57   * <li><strong>debug</strong> - The debugging detail level for this
58   *     servlet, which controls how much information is logged.  [0]</li>
59   * <li><strong>detail</strong> - The debugging detail level for the Digester
60   *     we utilize in <code>initMapping()</code>, which logs to System.out
61   *     instead of the servlet log.  [0]</li>
62   * </ul>
63   *
64   * @author Craig R. McClanahan
65   * @version $Revision: 155475 $ $Date: 2005-02-26 13:31:11 +0000 (Sat, 26 Feb 2005) $
66   */
67  
68  
69  public class ActivityServlet extends HttpServlet implements ContextListener {
70  
71  
72      // ----------------------------------------------------- Instance Variables
73  
74  
75      /**
76       * The <code>Activity</code> that is supported by this servlet instance.
77       */
78      private Activity activity = null;
79  
80  
81      /**
82       * Name of the session attribute under which our current
83       * <code>Context</code> is stored.
84       */
85      private String attribute = "org.apache.commons.workflow.CONTEXT";
86  
87  
88      /**
89       * The debugging detail level for this servlet.
90       */
91      private int debug = 0;
92  
93  
94      /**
95       * The debugging detail level for our Digester.
96       */
97      private int detail = 0;
98  
99  
100     // --------------------------------------------------------- Public Methods
101 
102 
103     /**
104      * Perform a graceful shutdown of this servlet instance.
105      */
106     public void destroy() {
107 
108         ; // No processing required
109 
110     }
111 
112 
113     /**
114      * Process a GET transaction.
115      *
116      * @param request The servlet request we are processing
117      * @param response The servlet response we are processing
118      *
119      * @exception IOException if an input/output exception occurs
120      * @exception ServletException if a servlet exception occurs
121      */
122     public void doGet(HttpServletRequest request,
123                       HttpServletResponse response)
124         throws IOException, ServletException {
125 
126         doPost(request, response);
127 
128     }
129 
130 
131     /**
132      * Process a POST transaction.
133      *
134      * @param request The servlet request we are processing
135      * @param response The servlet response we are processing
136      *
137      * @exception IOException if an input/output exception occurs
138      * @exception ServletException if a servlet exception occurs
139      */
140     public void doPost(HttpServletRequest request,
141                        HttpServletResponse response)
142         throws IOException, ServletException {
143 
144         // Acquire or create the current Context for this user
145         HttpSession session = request.getSession(true);
146         WebContext context = (WebContext)
147             session.getAttribute(attribute);
148         if (context == null) {
149             if (debug >= 1)
150                 log("{" + session.getId() + "} Creating new Context");
151             context = new WebContext();
152             context.setActivity(activity);
153             context.setHttpSession(session);
154             context.setServletContext(getServletContext());
155             if (debug >= 3)
156                 context.addContextListener(this);
157             session.setAttribute(attribute, context);
158         }                
159 
160 
161         // Execute the next stage of the current Activity
162         synchronized (context) {
163 
164             // If we are not already executing our associated Activity, call it
165             if (!activity.equals(context.getActivity())) {
166                 if (debug >= 2)
167                     log("{" + session.getId() + "} calling Activity " +
168                         activity.getId());
169                 context.call(activity);
170             }
171 
172             // Associate our context with the current request and response
173             context.setServletRequest(request);
174             context.setServletResponse(response);
175 
176             // Execute our activity until suspended or ended
177             try {
178                 if (debug >= 2)
179                     log("{" + session.getId() + "} executing Activity " +
180                         activity.getId());
181                 context.execute();
182             } catch (StepException e) {
183                 if (e.getCause() == null)
184                     throw new ServletException(e.getMessage(), e);
185                 else
186                     throw new ServletException(e.getMessage(), e.getCause());
187             }
188 
189         }
190 
191     }
192 
193 
194     /**
195      * Perform a graceful startup of this servlet instance.
196      *
197      * @exception ServletException if we cannot process the activity
198      *  definition file for this activity
199      */
200     public void init() throws ServletException {
201 
202         // Record the debugging detail level settings
203         String debug = getServletConfig().getInitParameter("debug");
204         if (debug != null) {
205             try {
206                 this.debug = Integer.parseInt(debug);
207             } catch (NumberFormatException e) {
208                 throw new UnavailableException
209                     ("Debug initialization parameter must be an integer");
210             }
211         }
212         String detail = getServletConfig().getInitParameter("detail");
213         if (detail != null) {
214             try {
215                 this.detail = Integer.parseInt(detail);
216             } catch (NumberFormatException e) {
217                 throw new UnavailableException
218                     ("Detail initialization parameter must be an integer");
219             }
220         }
221 
222         // Record the attribute name for our current Context
223         String attribute = getServletConfig().getInitParameter("attribute");
224         if (attribute != null)
225             this.attribute = attribute;
226 
227         // Parse the activity definition file for our Activity
228         String path = getServletConfig().getInitParameter("activity");
229         if (path == null)
230             throw new UnavailableException
231                 ("Must specify an 'activity' attribute");
232         parse(path);
233         if (activity == null)
234             throw new UnavailableException("No activity defined in resource "
235                                            + path);
236 
237     }
238 
239 
240     /**
241      * Set the <code>Activity</code> associated with this instance.
242      *
243      * @param activity The new associated Activity
244      */
245     public void setActivity(Activity activity) {
246 
247         this.activity = activity;
248 
249     }
250 
251 
252     // ------------------------------------------------ ContextListener Methods
253 
254 
255     /**
256      * Invoked immediately after execution of the related Activity has
257      * been completed normally, been suspended, or been aborted by
258      * the throwing of a StepException.  The Step included in this event
259      * will be the last one to be executed.
260      *
261      * @param event The <code>ContextEvent</code> that has occurred
262      */
263     public void afterActivity(ContextEvent event) {
264 
265         WebContext context = (WebContext) event.getContext();
266         HttpSession session = context.getHttpSession();
267         StringBuffer sb = new StringBuffer("{");
268         sb.append(session.getId());
269         sb.append("} afterActivity");
270         log(sb.toString());
271 
272     }
273 
274 
275 
276     /**
277      * Invoked immediately after the specified Step was executed.
278      *
279      * @param event The <code>ContextEvent</code> that has occurred
280      */
281     public void afterStep(ContextEvent event) {
282 
283         WebContext context = (WebContext) event.getContext();
284         HttpSession session = context.getHttpSession();
285         StringBuffer sb = new StringBuffer("{");
286         sb.append(session.getId());
287         sb.append("} afterStep ");
288         sb.append(event.getStep());
289         if (context.getSuspend())
290             sb.append(" (Suspended)");
291         if (context.getNextStep() == null)
292             sb.append(" (Finished)");
293         log(sb.toString());
294         if (event.getException() != null)
295             log("-->Step threw exception", event.getException());
296 
297     }
298 
299 
300     /**
301      * Invoked immediately before execution of the related Activity has
302      * started.  The Step included in this event will be the first one
303      * to be executed.
304      *
305      * @param event The <code>ContextEvent</code> that has occurred
306      */
307     public void beforeActivity(ContextEvent event) {
308 
309         WebContext context = (WebContext) event.getContext();
310         HttpSession session = context.getHttpSession();
311         StringBuffer sb = new StringBuffer("{");
312         sb.append(session.getId());
313         sb.append("} beforeActivity");
314         log(sb.toString());
315 
316     }
317 
318 
319     /**
320      * Invoked immediately before the specified Step is executed.
321      *
322      * @param event The <code>ContextEvent</code> that has occurred
323      */
324     public void beforeStep(ContextEvent event) {
325 
326         WebContext context = (WebContext) event.getContext();
327         HttpSession session = context.getHttpSession();
328         StringBuffer sb = new StringBuffer("{");
329         sb.append(session.getId());
330         sb.append("} beforeStep ");
331         sb.append(event.getStep());
332         log(sb.toString());
333 
334     }
335 
336 
337     // -------------------------------------------------------- Private Methods
338 
339 
340     /**
341      * Parse the specified activity definition file for this instance.
342      *
343      * @param path Context-relative resource path of the activity
344      *  definition file
345      *
346      * @exception ServletException on any processing error in parsing
347      */
348     private void parse(String path) throws ServletException {
349 
350         // Get an input source for the specified path
351         InputStream is =
352             getServletContext().getResourceAsStream(path);
353         if (is == null)
354             throw new UnavailableException("Cannot access resource " +
355                                            path);
356 
357         // Configure a Digester instance to parse our definition file
358         Digester digester = new Digester();
359         digester.setNamespaceAware(true);
360         digester.setValidating(false);
361         digester.push(this);
362 
363         // Add rules to recognize the built-in steps that we know about
364         BaseRuleSet brs = new BaseRuleSet();
365         digester.addRuleSet(brs);
366         digester.addRuleSet(new CoreRuleSet());
367         digester.addRuleSet(new IoRuleSet());
368         digester.addRuleSet(new WebRuleSet());
369 
370         // Add a rule to register the Activity being created
371         digester.setRuleNamespaceURI(brs.getNamespaceURI());
372         digester.addSetNext("activity", "setActivity",
373                             "org.apache.commons.workflow.Activity");
374 
375         // Parse the activity definition file
376         try {
377             digester.parse(is);
378         } catch (Throwable t) {
379             log("Cannot parse resource " + path, t);
380             throw new UnavailableException("Cannot parse resource " + path);
381         } finally {
382             try {
383                 is.close();
384             } catch (Throwable u) {
385                 ;
386             }
387         }
388 
389     }
390 
391 
392 }