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 }