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    }