001 /* 002 * Copyright 1999-2001,2004 The Apache Software Foundation. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package org.apache.commons.workflow.web; 018 019 020 import java.io.InputStream; 021 import java.io.IOException; 022 import javax.servlet.ServletException; 023 import javax.servlet.UnavailableException; 024 import javax.servlet.http.HttpServlet; 025 import javax.servlet.http.HttpServletRequest; 026 import javax.servlet.http.HttpServletResponse; 027 import javax.servlet.http.HttpSession; 028 import org.apache.commons.digester.Digester; 029 import org.apache.commons.workflow.Activity; 030 import org.apache.commons.workflow.Context; 031 import org.apache.commons.workflow.ContextEvent; 032 import org.apache.commons.workflow.ContextListener; 033 import org.apache.commons.workflow.Step; 034 import org.apache.commons.workflow.StepException; 035 import org.apache.commons.workflow.base.BaseRuleSet; 036 import org.apache.commons.workflow.core.CoreRuleSet; 037 import org.apache.commons.workflow.io.IoRuleSet; 038 import org.apache.commons.workflow.web.WebContext; 039 import org.apache.commons.workflow.web.WebRuleSet; 040 041 042 /** 043 * <p>Demonstration servlet that illustrates one way that workflow support can 044 * be integrated into web applications (or web services) without any 045 * dependency on application frameworks. For this implementation, a servlet 046 * <em>definition</em> (plus one or more servlet <em>mappings</em>) will be 047 * associated with each <code>Activity</code> supported by this web 048 * application.</p> 049 * 050 * <p>Initialization parameters (defaults in square brackets):</p> 051 * <ul> 052 * <li><strong>activity</strong> - Context-relative resource path to the 053 * definition file for the Activity that is supported by this servlet.</li> 054 * <li><strong>attribute</strong> - Name of the session attribute under 055 * which our current <code>Context</code> implementation is stored. 056 * [org.apache.commons.workflow.web.CONTEXT]</li> 057 * <li><strong>debug</strong> - The debugging detail level for this 058 * servlet, which controls how much information is logged. [0]</li> 059 * <li><strong>detail</strong> - The debugging detail level for the Digester 060 * we utilize in <code>initMapping()</code>, which logs to System.out 061 * instead of the servlet log. [0]</li> 062 * </ul> 063 * 064 * @author Craig R. McClanahan 065 * @version $Revision: 155475 $ $Date: 2005-02-26 13:31:11 +0000 (Sat, 26 Feb 2005) $ 066 */ 067 068 069 public class ActivityServlet extends HttpServlet implements ContextListener { 070 071 072 // ----------------------------------------------------- Instance Variables 073 074 075 /** 076 * The <code>Activity</code> that is supported by this servlet instance. 077 */ 078 private Activity activity = null; 079 080 081 /** 082 * Name of the session attribute under which our current 083 * <code>Context</code> is stored. 084 */ 085 private String attribute = "org.apache.commons.workflow.CONTEXT"; 086 087 088 /** 089 * The debugging detail level for this servlet. 090 */ 091 private int debug = 0; 092 093 094 /** 095 * The debugging detail level for our Digester. 096 */ 097 private int detail = 0; 098 099 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 }