EMMA Coverage Report (generated Mon Mar 20 21:27:43 EST 2006)
[all classes][com.webhydra.slug]

COVERAGE SUMMARY FOR SOURCE FILE [Slug.java]

nameclass, %method, %block, %line, %
Slug.java100% (1/1)100% (10/10)93%  (401/431)97%  (109.8/113)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class Slug100% (1/1)100% (10/10)93%  (401/431)97%  (109.8/113)
processRequest (HttpServletRequest, HttpServletResponse): void 100% (1/1)85%  (175/205)94%  (48.8/52)
Slug (): void 100% (1/1)100% (3/3)100% (1/1)
destroy (): void 100% (1/1)100% (63/63)100% (15/15)
doGet (HttpServletRequest, HttpServletResponse): void 100% (1/1)100% (5/5)100% (2/2)
doPost (HttpServletRequest, HttpServletResponse): void 100% (1/1)100% (5/5)100% (2/2)
getServletInfo (): String 100% (1/1)100% (2/2)100% (1/1)
getSlugConfig (): SlugConfig 100% (1/1)100% (3/3)100% (1/1)
init (): void 100% (1/1)100% (95/95)100% (23/23)
reportStatus (HttpServletRequest, HttpServletResponse, ProcessStatus, IProces... 100% (1/1)100% (20/20)100% (7/7)
setBody (HttpServletResponse, String, String): void 100% (1/1)100% (30/30)100% (9/9)

1/*
2 * @(#) $Id: Slug.java,v 1.2 2006/03/21 02:23:38 rossen Exp $
3 *
4 * Copyright (c) 2006, WebHydra.com
5 * All rights reserved.
6 * 
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 * 
10 *     * Redistributions of source code must retain the above copyright notice,
11 *       this list of conditions and the following disclaimer.
12 *     * Redistributions in binary form must reproduce the above copyright
13 *       notice, this list of conditions and the following disclaimer in the
14 *       documentation and/or other materials provided with the distribution.
15 *     * Neither the name of the WebHydra.com nor the names of its contributors
16 *       may be used to endorse or promote products derived from this software
17 *       without specific prior written permission.
18 * 
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31 
32package com.webhydra.slug;
33 
34import com.webhydra.slug.process.IProcessStatusTranslator;
35import com.webhydra.slug.process.ProcessStatus;
36import com.webhydra.slug.process.ISlugRunner;
37import com.webhydra.slug.process.SluggishProcess;
38import com.webhydra.slug.process.Status;
39import java.io.*;
40import java.util.StringTokenizer;
41 
42import javax.servlet.*;
43import javax.servlet.http.*;
44 
45/**
46 * Sluggish processes interface servlet.
47 * Process requests for <code>SuggishProcess</code>(es)' <code>ProcessStatus</code>
48 *
49 * The following calling convention utilizes Serlet's Path Info and Request's parameters:<br>
50 * /&lt;ACTION&gt;[/&lt;TRANSLATOR&gt]/?[rn=&lt;RUNNER&gt;&amp;]pid=&lt;PID&gt[;&amp;url=&lt;URL&gt;]
51 * <ul>
52 * <li>Where mandatory ACTION is one of the following:
53 * <dl>
54 *  <dt>status</dt> <dd>Reports <code>ProcessStatus</code> of spcified
55 * <code>SuggishProcess</code>;</dd>
56 *  <dt>consume</dt> <dd>Removes spcified <code>SuggishProcess</code> from
57 * it from its <code>ISlugRunner</code> Active Proces Llist Table and reports
58 * its <code>ProcessStatus</code>;</dd>
59 *  <dt>cancel</dt> <dd>Cancels spcified <code>SuggishProcess</code> and removes
60 * it from its <code>ISlugRunner</code> Active Proces Llist Table and reports
61 * its <code>ProcessStatus</code>;</dd>
62 * </dl>
63 * <li>TRANSLATOR is an optional name of <code>IProcessStatusTranslator</code> as
64 * listed in the translator conficuration section of Slug's config. If it is
65 * not provided the the default translator (the first in provided configuration)
66 * will be used.
67 * <li>RUNNER is an optional name of <code>ISlugRunner</code> as
68 * listed in the runners conficuration section of Slug's config. If not provided
69 * the default one (the first in provided configuration) will be used.
70 * <li>PID is mandatory Process ID of a <code>SluggishProcess</code> instance as
71 * is returned from the ISlugRunner.add(SluggishProcess,String) call.
72 * <li>URL is an optional redirection URL that will be used upon:
73 *  <ol>
74 *  <li>successfull process completion when <code>status</code> action is requested;
75 *  <li><code>cancel</code> or <code>consume</code> actions are called;
76 *  </ol>
77 * </ul>
78 * Example URL:<br>
79 * http://my.server.com/myapp/slug/<code>status/xsd/?rn=unlimited&amp;pid=1140468189:1&amp;url=/myapp/done?pid=1140468189:1</code>
80 *
81 * @author rossen
82 * @version 1.0
83 * @see com.webhydra.slug.process.ISlugRunner#add(SluggishProcess,String)
84 * @see com.webhydra.slug.process.SluggishProcess
85 * @see ISlugConfigParser
86 * @see PropertiesConfigParser
87 */
88public class Slug extends HttpServlet {
89 
90    private static final long serialVersionUID = 1L;
91 
92    /**
93     * CVS version of the distribution.
94     */
95    public static final String DIST_VERSION ="$Id: Slug.java,v 1.2 2006/03/21 02:23:38 rossen Exp $";
96 
97    /** Name of the servlet */
98    public static final String NAME = "Slug Servlet";
99 
100    /**
101     * Name of the configuration file name parameter.
102     * This paramater points to a file that will be used as input for the
103     * <code>ISlugConfigParser</code> implementation.
104     */
105    public static final String PARAM_NAME_CONFIG = "config";
106 
107    /**
108     * Name of the configuration parser class name parameter.
109     * This parameter defines the class name of a <code>ISlugConfigParser</code>
110     * implementation.
111     */
112    public static final String PARAM_NAME_PARSER = "parser";
113 
114    /**
115     * Runtime variables:
116     */
117 
118    /** Path Info constant for the status processor */
119    public static final String PATH_STATUS = "status";
120 
121    /** Path Info constant for the process consompsion */
122    public static final String PATH_CONSUME = "consume";
123 
124    /** Path Info constant for the process cancelation */
125    public static final String PATH_CANCEL = "cancel";
126 
127    /** Mandatory request parameter with <code>PID</code> value */
128    public static final String PARAM_PID = "pid";
129 
130    /** Optinal runner name parameter.
131     * If missed default runner will be used.
132     */
133    public static final String PARAM_RUNNER_NAME = "rn";
134 
135    /** Parameter for redirection */
136    public static final String PARAM_REFIRECT_URL = "url";
137 
138    /** <code>SlugConfig</code> instance */
139    private SlugConfig cfg;
140 
141    /** Returns a short description of the servlet.
142     * @return version info about the serlvet.
143     */
144    public String getServletInfo()
145    {
146        return NAME + ", ver. " + DIST_VERSION;
147    }
148 
149    /** Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
150     * <dl>Resulting Status codes:
151     *  <dt>501</dt> <dd>Provided <code>runner</code> or <code>translator</code>
152     * cannot be found or requested <code>action</code> is not supported</dd>
153     *  <dt>404</dt> <dd>When <code>PID</code> is missing or is empty.</dd>
154     *  <dt>410</dt> <dd>When <code>PID</code> can not be found and no redirect
155     * URL is provided</dd>
156     *  <dt>302</dt> <dd>When requested process is completed or canceled and
157     * redirection URL is provided</dd>
158     * </dl>
159     * @param request servlet request
160     * @param response servlet response
161     */
162    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
163    throws ServletException, IOException {
164 
165        // Get the PID first
166        String pid = request.getParameter(PARAM_PID);
167        if ((pid == null) || (pid.trim().length() == 0))
168        {
169            // PID is not provided.
170            // Pespond with 404 indicating that the requested resource is not available.
171            String msg = "Process ID parameter \"" + PARAM_PID + "\" is required!";
172            response.sendError(HttpServletResponse.SC_NOT_FOUND, msg);
173            return;
174        }
175 
176        try
177        {
178            // Obtain requested runner instance or fall back to the default one if none is provided
179            String runnerName = (request.getParameter(PARAM_RUNNER_NAME) != null) ?
180                request.getParameter(PARAM_RUNNER_NAME) : cfg.getDefaultRunnerName();
181 
182            // get the runner
183            ISlugRunner runner = cfg.getRunner(runnerName);
184            if (runner == null)
185            {
186                // Valid runner can not be obtained.
187                // Respond with 501 error indicating the HTTP server does not
188                // support the functionality needed to fulfill the request.
189                response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED,
190                                   "Provided runner is not known to the system!");
191                return;
192            }
193 
194            String sessionId = request.getSession(true).getId();
195            String path = request.getPathInfo();
196 
197            // obtain requested action and translator name
198            String action = "";
199            String format = "";
200            if ((path != null) && (path.length() > 0))
201            {
202                StringTokenizer pt = new StringTokenizer(path, "/", false);
203                action = pt.nextToken();
204                if (pt.hasMoreTokens())
205                {
206                    format = pt.nextToken();
207                }
208                else
209                {
210                    format = cfg.getDefaultTranslatorName();
211                }
212            }
213 
214            // obtain a translator for this request
215            IProcessStatusTranslator translator = cfg.getTranslator(format);
216            if (translator == null)
217            {
218                // Valid translator can not be obtained.
219                // Respond with 501 error indicating the HTTP server does not
220                // support the functionality needed to fulfill the request.
221                response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED,
222                                   "Provided translator is not known to the system!");
223                return;
224            }
225 
226            // process requested action
227            String redirectUrl = request.getParameter(PARAM_REFIRECT_URL);
228            if (PATH_STATUS.equals(action))
229            {
230                ProcessStatus ps = runner.checkProcess(sessionId, pid);
231                if ((redirectUrl != null) && ((ps == null) || Status.isCompleted(ps.getStatus())))
232                {
233                    // redirect to requested url if porcess is completed or not found (null).
234                    response.sendRedirect(redirectUrl);
235                }
236                else if (ps == null)
237                {
238                    // PID is not found.
239                    // Pespond with 410 indicating that the requested resource is not available.
240                    response.sendError(HttpServletResponse.SC_GONE,
241                            "Provided PID can not be found!");
242                }
243                else
244                {
245                    reportStatus(request, response, ps, translator);
246                }
247                return;
248            }
249            if (PATH_CANCEL.equals(action) || PATH_CONSUME.equals(action))
250            {
251                // cancel or cousnime the provided process
252                SluggishProcess sp = (PATH_CANCEL.equals(action)) ?
253                    runner.cancelProcess(sessionId, pid) :
254                    runner.consumeProcess(sessionId, pid);
255 
256                // redirect to requested URL if present
257                if (redirectUrl != null)
258                {
259                    response.sendRedirect(redirectUrl);
260                }
261                else
262                {
263                    // return current ptocess status
264                    if (sp == null)
265                    {
266                    // PID is not found.
267                    // Pespond with 410 indicating that the requested resource is not available.
268                        response.sendError(HttpServletResponse.SC_GONE,
269                                "Specified process does not exist on this server!");
270                    }
271                    else
272                    {
273                        reportStatus(request, response, sp.getProcessStatus(), translator);
274                    }
275                }
276                return;
277            }
278 
279            // Unsupported path action requested
280            // Respond with 501 error indicating the HTTP server does not
281            // support the functionality needed to fulfill the request.
282            response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED,
283                                   "Requested action is not supported!");
284        } catch (SlugInitializationException ex) {
285            // Slug is missconfigured.
286            // log the error
287            getServletContext().log(getServletName() + " is in service but its config is empty!", ex);
288            // return 500 error
289            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
290                               getServletName() + " is not properly configured!");
291        }
292    }
293 
294    /** Prints provided body to the browser.
295     * @param response current HTTP response.
296     * @param body HTML page to send to the browser.
297     * @param contentType response content type.
298     */
299    protected void setBody(HttpServletResponse response, String body, String contentType) throws IOException
300    {
301        String ct = (contentType == null) ? "text/html;charset=UTF-8" : contentType;
302        response.setContentType(ct);
303        
304        // prevent from caching
305        response.setHeader("Cache-Control","no-cache"); //HTTP 1.1
306        response.setHeader("Pragma","no-cache"); //HTTP 1.0
307        response.setDateHeader ("Expires", 0); //prevents caching at the proxy server
308        
309        PrintWriter out = response.getWriter();
310        out.print(body);
311        out.close();
312    }
313 
314    /** Report process' status
315     * @param request servlet request
316     * @param response servlet response
317     * @param ps status of current process
318     * @param translator <code>ProcessStarusTranslator</code> instance.
319     */
320    protected void reportStatus(HttpServletRequest request, HttpServletResponse response,
321            ProcessStatus ps, IProcessStatusTranslator translator) throws ServletException,
322            IOException
323    {
324        if (ps == null)
325        {
326            response.sendError(HttpServletResponse.SC_GONE,
327                    "Specified process is not available on the server!");
328            return;
329        }
330        String respBody = translator.translate(ps);
331        String contentType = translator.getContentType();
332        setBody(response, respBody, contentType);
333    }
334 
335    /** Handles the HTTP <code>GET</code> method.
336     * @param request servlet request
337     * @param response servlet response
338     */
339    protected void doGet(HttpServletRequest request, HttpServletResponse response)
340    throws ServletException, IOException {
341        processRequest(request, response);
342    }
343 
344    /** Handles the HTTP <code>POST</code> method.
345     * @param request servlet request
346     * @param response servlet response
347     */
348    protected void doPost(HttpServletRequest request, HttpServletResponse response)
349    throws ServletException, IOException {
350        processRequest(request, response);
351    }
352 
353    /** Overrides the default implementation from <code>javax.servlet.GenericServlet</code>
354     * @exception ServletException when important parameters are missing.
355     */
356    public void init() throws ServletException
357    {
358        getServletContext().log("Initializing " + getServletName() + "...");
359        // check the parameters presence
360        String clsImpl = getServletConfig().getInitParameter(PARAM_NAME_PARSER);
361        if (clsImpl == null)
362        {
363            String msg = "Missing " + PARAM_NAME_PARSER + " parameter!";
364            log(msg);
365            throw new ServletException(msg);
366        }
367        String cfgFile = getServletConfig().getInitParameter(PARAM_NAME_CONFIG);
368        if (cfgFile == null)
369        {
370            String msg = "Missing " + PARAM_NAME_CONFIG + " parameter!";
371            throw new ServletException(msg);
372        }
373        // instantiate the config instance
374        try
375        {
376            InputStream is = getServletContext().getResourceAsStream(cfgFile);
377            if (is == null)
378            {
379                log("Configuration file is not accessibe!");
380                throw new ServletException("Can not load " + cfgFile + " file!");
381            }
382            SlugConfig newCfg = SlugConfig.getInstance();
383            newCfg.init(clsImpl, is);
384            // assign only initialized instance!
385            this.cfg = newCfg;
386        }
387        catch (SlugInitializationException sie)
388        {
389            log("Initialization failed!");
390            throw new ServletException("Can not initialize the Slug config: ", sie);
391        }
392        log("Initialized successfully.");
393    }
394 
395    /**
396     * Provides convinient acess to the cfg property.
397     * @return current SlugConfig in use.
398     */
399    protected SlugConfig getSlugConfig()
400    {
401        return cfg;
402    }
403 
404    /**
405     * Overrides the default implelentation.
406     * Clean and releases all <code>ISlugRunner</code>s and releases all
407     * <code>IProcessStatusTranslator</code>s.
408     * @see com.webhydra.slug.process.ISlugService#release()
409     * @see com.webhydra.slug.process.ISlugRunner#release()
410     * @see com.webhydra.slug.process.IProcessStatusTranslator#release()
411     */
412    public void destroy() {
413        if (cfg != null)
414        {
415            // clean all runners
416            String [] runners = cfg.getRunnerNames();
417            if ((runners != null) && (runners.length > 0))
418            {
419                for (int i = 0; i < runners.length; i++)
420                {
421                    ISlugRunner runner = cfg.getRunner(runners[i]);
422                    runner.clear();
423                    runner.release();
424                }
425            }
426            // clen translators
427            String [] translators = cfg.getTranslatorNames();
428            if ((translators != null) && (translators.length > 0))
429            {
430                for (int i = 0; i < translators.length; i++)
431                {
432                    IProcessStatusTranslator t = cfg.getTranslator(translators[i]);
433                    t.release();
434                }
435            }
436            cfg = null;
437        }
438        super.destroy();
439    }
440}

[all classes][com.webhydra.slug]
EMMA 2.0.5312 (C) Vladimir Roubtsov