Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
ActivityServlet |
|
| 3.9;3.9 |
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 | 0 | 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 | 0 | 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 | 0 | private String attribute = "org.apache.commons.workflow.CONTEXT"; |
86 | ||
87 | ||
88 | /** | |
89 | * The debugging detail level for this servlet. | |
90 | */ | |
91 | 0 | private int debug = 0; |
92 | ||
93 | ||
94 | /** | |
95 | * The debugging detail level for our Digester. | |
96 | */ | |
97 | 0 | 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 | 0 | } |
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 | 0 | doPost(request, response); |
127 | ||
128 | 0 | } |
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 | 0 | HttpSession session = request.getSession(true); |
146 | 0 | WebContext context = (WebContext) |
147 | session.getAttribute(attribute); | |
148 | 0 | if (context == null) { |
149 | 0 | if (debug >= 1) |
150 | 0 | log("{" + session.getId() + "} Creating new Context"); |
151 | 0 | context = new WebContext(); |
152 | 0 | context.setActivity(activity); |
153 | 0 | context.setHttpSession(session); |
154 | 0 | context.setServletContext(getServletContext()); |
155 | 0 | if (debug >= 3) |
156 | 0 | context.addContextListener(this); |
157 | 0 | session.setAttribute(attribute, context); |
158 | } | |
159 | ||
160 | ||
161 | // Execute the next stage of the current Activity | |
162 | 0 | synchronized (context) { |
163 | ||
164 | // If we are not already executing our associated Activity, call it | |
165 | 0 | if (!activity.equals(context.getActivity())) { |
166 | 0 | if (debug >= 2) |
167 | 0 | log("{" + session.getId() + "} calling Activity " + |
168 | activity.getId()); | |
169 | 0 | context.call(activity); |
170 | } | |
171 | ||
172 | // Associate our context with the current request and response | |
173 | 0 | context.setServletRequest(request); |
174 | 0 | context.setServletResponse(response); |
175 | ||
176 | // Execute our activity until suspended or ended | |
177 | try { | |
178 | 0 | if (debug >= 2) |
179 | 0 | log("{" + session.getId() + "} executing Activity " + |
180 | activity.getId()); | |
181 | 0 | context.execute(); |
182 | 0 | } catch (StepException e) { |
183 | 0 | if (e.getCause() == null) |
184 | 0 | throw new ServletException(e.getMessage(), e); |
185 | else | |
186 | 0 | throw new ServletException(e.getMessage(), e.getCause()); |
187 | 0 | } |
188 | ||
189 | 0 | } |
190 | ||
191 | 0 | } |
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 | 0 | String debug = getServletConfig().getInitParameter("debug"); |
204 | 0 | if (debug != null) { |
205 | try { | |
206 | 0 | this.debug = Integer.parseInt(debug); |
207 | 0 | } catch (NumberFormatException e) { |
208 | 0 | throw new UnavailableException |
209 | ("Debug initialization parameter must be an integer"); | |
210 | 0 | } |
211 | } | |
212 | 0 | String detail = getServletConfig().getInitParameter("detail"); |
213 | 0 | if (detail != null) { |
214 | try { | |
215 | 0 | this.detail = Integer.parseInt(detail); |
216 | 0 | } catch (NumberFormatException e) { |
217 | 0 | throw new UnavailableException |
218 | ("Detail initialization parameter must be an integer"); | |
219 | 0 | } |
220 | } | |
221 | ||
222 | // Record the attribute name for our current Context | |
223 | 0 | String attribute = getServletConfig().getInitParameter("attribute"); |
224 | 0 | if (attribute != null) |
225 | 0 | this.attribute = attribute; |
226 | ||
227 | // Parse the activity definition file for our Activity | |
228 | 0 | String path = getServletConfig().getInitParameter("activity"); |
229 | 0 | if (path == null) |
230 | 0 | throw new UnavailableException |
231 | ("Must specify an 'activity' attribute"); | |
232 | 0 | parse(path); |
233 | 0 | if (activity == null) |
234 | 0 | throw new UnavailableException("No activity defined in resource " |
235 | + path); | |
236 | ||
237 | 0 | } |
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 | 0 | this.activity = activity; |
248 | ||
249 | 0 | } |
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 | 0 | WebContext context = (WebContext) event.getContext(); |
266 | 0 | HttpSession session = context.getHttpSession(); |
267 | 0 | StringBuffer sb = new StringBuffer("{"); |
268 | 0 | sb.append(session.getId()); |
269 | 0 | sb.append("} afterActivity"); |
270 | 0 | log(sb.toString()); |
271 | ||
272 | 0 | } |
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 | 0 | WebContext context = (WebContext) event.getContext(); |
284 | 0 | HttpSession session = context.getHttpSession(); |
285 | 0 | StringBuffer sb = new StringBuffer("{"); |
286 | 0 | sb.append(session.getId()); |
287 | 0 | sb.append("} afterStep "); |
288 | 0 | sb.append(event.getStep()); |
289 | 0 | if (context.getSuspend()) |
290 | 0 | sb.append(" (Suspended)"); |
291 | 0 | if (context.getNextStep() == null) |
292 | 0 | sb.append(" (Finished)"); |
293 | 0 | log(sb.toString()); |
294 | 0 | if (event.getException() != null) |
295 | 0 | log("-->Step threw exception", event.getException()); |
296 | ||
297 | 0 | } |
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 | 0 | WebContext context = (WebContext) event.getContext(); |
310 | 0 | HttpSession session = context.getHttpSession(); |
311 | 0 | StringBuffer sb = new StringBuffer("{"); |
312 | 0 | sb.append(session.getId()); |
313 | 0 | sb.append("} beforeActivity"); |
314 | 0 | log(sb.toString()); |
315 | ||
316 | 0 | } |
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 | 0 | WebContext context = (WebContext) event.getContext(); |
327 | 0 | HttpSession session = context.getHttpSession(); |
328 | 0 | StringBuffer sb = new StringBuffer("{"); |
329 | 0 | sb.append(session.getId()); |
330 | 0 | sb.append("} beforeStep "); |
331 | 0 | sb.append(event.getStep()); |
332 | 0 | log(sb.toString()); |
333 | ||
334 | 0 | } |
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 | 0 | InputStream is = |
352 | getServletContext().getResourceAsStream(path); | |
353 | 0 | if (is == null) |
354 | 0 | throw new UnavailableException("Cannot access resource " + |
355 | path); | |
356 | ||
357 | // Configure a Digester instance to parse our definition file | |
358 | 0 | Digester digester = new Digester(); |
359 | 0 | digester.setNamespaceAware(true); |
360 | 0 | digester.setValidating(false); |
361 | 0 | digester.push(this); |
362 | ||
363 | // Add rules to recognize the built-in steps that we know about | |
364 | 0 | BaseRuleSet brs = new BaseRuleSet(); |
365 | 0 | digester.addRuleSet(brs); |
366 | 0 | digester.addRuleSet(new CoreRuleSet()); |
367 | 0 | digester.addRuleSet(new IoRuleSet()); |
368 | 0 | digester.addRuleSet(new WebRuleSet()); |
369 | ||
370 | // Add a rule to register the Activity being created | |
371 | 0 | digester.setRuleNamespaceURI(brs.getNamespaceURI()); |
372 | 0 | digester.addSetNext("activity", "setActivity", |
373 | "org.apache.commons.workflow.Activity"); | |
374 | ||
375 | // Parse the activity definition file | |
376 | try { | |
377 | 0 | digester.parse(is); |
378 | 0 | } catch (Throwable t) { |
379 | 0 | log("Cannot parse resource " + path, t); |
380 | 0 | throw new UnavailableException("Cannot parse resource " + path); |
381 | } finally { | |
382 | 0 | try { |
383 | 0 | is.close(); |
384 | 0 | } catch (Throwable u) { |
385 | ; | |
386 | 0 | } |
387 | 0 | } |
388 | ||
389 | 0 | } |
390 | ||
391 | ||
392 | } |