001    // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 
002    // The Molly framework is freely distributable under the terms of an
003    // MIT-style license. For details, see the molly pages web site at:
004    // http://www.mollypages.org/. Use, modify, have fun !
005    
006    package fc.web.servlet;
007    
008    import java.util.*;
009    import java.util.logging.*;
010    import java.io.*;
011    import javax.servlet.*;
012    import javax.servlet.http.*;
013    import java.net.*;
014    
015    import fc.io.*;
016    
017    /** 
018    Misc web related utility functions 
019    
020    @author hursh jain
021    **/
022    public final class WebUtil 
023    {
024    final static Log log = Log.get("fc.web.servlet");
025    
026    //--------------------- getParameters ----------------------
027    
028    /** 
029    Gets the specified required initialization parameter from
030    the servlet context. If this param is not found, throws a
031    permanent {@link javax.servlet.UnavailableException}.
032    **/
033    public static final String getRequiredParam
034      (ServletContext context, String name) 
035    throws ServletException
036      {
037      String param = context.getInitParameter(name);    
038    
039      if (param == null) {
040        String error = "Required Servlet Init Parameter: '" + name + "' was not found";
041        log.error(error);
042        throw new UnavailableException(error);
043        }
044    
045      return param; 
046      }
047    
048    /** 
049    Gets the specified initialization parameter from the servlet context. If 
050    this param is not found, then the specified backup value is returned.
051    **/
052    public static final String getParam(
053      ServletContext context, String name, String backup) 
054      {
055      String param = context.getInitParameter(name);    
056      if (param != null)
057        return param; 
058      else
059        return backup;
060      }
061    
062    /** 
063    Gets the specified initialization parameter for the specified servlet
064    (via it's ServletConfig). Note, servlet init parameters are servlet
065    specific whereas context parameters are shared by all servlets within
066    that context. If this param is not found, throws a permanent {@link
067    javax.servlet.UnavailableException}.
068    **/
069    public static final String getRequiredParam(Servlet servlet, String name) 
070    throws ServletException
071      {
072      ServletConfig config = servlet.getServletConfig();
073      String param = config.getInitParameter(name);   
074    
075      if (param == null) {
076        String error = "Required Servlet Init Parameter: '" + name + "' for servlet '" + config.getServletName() + "' was not found";
077        log.error(error);
078        throw new UnavailableException(error);
079        }
080    
081      return param; 
082      }
083    
084    /** 
085    Gets the specified initialization parameter for the specified servlet
086    (via it's ServletConfig). Note, servlet init parameters are servlet
087    specific whereas context parameters are shared by all servlets within
088    that context. If this param is not found, then the specified backup value
089    is returned.
090    **/
091    public static final String getParam(
092      Servlet servlet, String name, String backup) 
093      {
094      ServletConfig config = servlet.getServletConfig();
095      String param = config.getInitParameter(name);   
096      if (param != null)
097        return param; 
098      else
099        return backup;
100      }
101    
102    /** 
103    Gets the specified required parameter from the request
104    object. If this param is not found, throws a ServletException
105    **/
106    public static final String getRequiredParam(
107                HttpServletRequest request, String name) 
108    throws ServletException
109      {
110      String param = request.getParameter(name);    
111    
112      if (param == null) {
113        String error = "Required Parameter: '" + name + "' was not found";
114        log.error(error);
115        throw new ServletException(error);
116        }
117    
118      return param; 
119      }
120    
121    /** 
122    Gets the specified parameter from the request object. 
123    If this param is not found, returns the backup value.
124    **/
125    public static final String getParam(
126      HttpServletRequest request, String name, String backup) 
127      {
128      String param = request.getParameter(name);    
129      if (param != null)
130        return param; 
131      else
132        return backup;
133      }
134    
135    /** 
136    The returned value will be <tt>true</tt> if the specified parameter
137    is present in the request and is a non-null non-empty string (<b>of
138    any value</b>). This is useful for radioboxes and checkboxes.
139    **/
140    public static final boolean isSelected(
141      HttpServletRequest request, String name) 
142      {
143      String param = request.getParameter(name);    
144      if (param == null || param.equals(""))
145        return false;       
146        
147      return true;
148      }
149    
150    /** 
151    Convenience method that returns the specified parameter as a boolean 
152    value. The returned value will be converted via the
153    {@link Boolean#parseBoolean(String)} (for <tt>true</tt>, the 
154    value should be non-null and equal ignore case to "true").
155    **/
156    public static final boolean getBooleanParameter(
157      HttpServletRequest request, String name)
158      {
159      String param = request.getParameter(name);    
160      return Boolean.parseBoolean(param);
161      }
162    
163    
164    //--------------------- getAttributes ----------------------
165      
166    /** 
167    Gets the specified required attribute from the servlet context. If 
168    this attribute is not found, throws a permanent 
169    {@link javax.servlet.UnavailableException}.
170    **/
171    public static final Object getRequiredAttribute(
172      ServletContext context, String name) 
173    throws ServletException
174      {
175      Object param = context.getAttribute(name);    
176    
177      if (param == null) {
178        String error = "Required context attribute: '" + name + "' was not found";
179        log.error(error);
180        throw new UnavailableException(error);  
181        }
182    
183      return param; 
184      }
185    
186    /** 
187    Gets the specified attribute from the servlet context. If this attribute
188    is not found, then the specified backup value is returned.
189    **/
190    public static final Object getAttribute(
191      ServletContext context, String name, String backup) 
192    throws ServletException
193      {
194      Object param = context.getAttribute(name);    
195      
196      if (param != null)    
197        return param; 
198      else
199        return backup;
200      }
201    
202    /** 
203    Gets the specified required attribute from the request object. 
204    If this param is not found, throws a ServletException.
205    **/
206    public static final Object getRequiredAttribute(
207                HttpServletRequest request, String name) 
208    throws ServletException
209      {
210      Object param = request.getAttribute(name);    
211      
212      if (param == null) {
213        String error = "Required Parameter: '" + name + "' was not found";
214        log.error(error);
215        throw new ServletException(error);  
216        }
217    
218      return param; 
219      }
220    
221    /** 
222    Gets the specified attribute from the request object. 
223    If this attribute is not found, returns the backup value.
224    **/
225    public static final Object getAttribute(
226      HttpServletRequest request, String name, String backup) 
227    throws ServletException
228      {
229      Object param = request.getAttribute(name);    
230      if (param != null)
231        return param; 
232      else
233        return backup;
234      }
235    
236    //----------------------------------------------------------
237    
238    /** 
239    Returns a HttpSession attribute as a String or null if the attribute
240    was absent. 
241    
242    @param  name  the name of the session attribute
243    @throws ClassCastException    if the attribute was not a String
244    @throws NullPointerException  if the specified session or name parameters
245                    were null
246    **/ 
247    public static final String getSessionString(HttpSession session, String name) 
248      {
249      Object obj = session.getAttribute(name);
250      if (obj != null)
251        return (String) obj;
252      else
253        return null;
254      }
255    
256    /**
257    Returns the cookie with the specified name or <tt>null</tt> if no cookie
258    was found.
259    */
260    public static Cookie getCookie(HttpServletRequest req, String cookieName)
261      {
262      Cookie[] cookies = req.getCookies();
263      
264      if (cookies == null)
265        return null;
266        
267      for (int n = 0; n < cookies.length; n++) 
268        {
269        if (cookies[n].getName().equals(cookieName))
270          {
271          return cookies[n];
272          }
273        }
274      return null;
275      }
276      
277    /** 
278    Redirects the request (server side redirect) to the specified relative URL
279    via the {@link javax.servlet.ServletRequest#getRequestDispatcher}. See
280    {@link javax.servlet.RequestDispatcher#forward} for restrictions while
281    forwarding to another page (in particular, the response must not have been
282    committed before calling this method).
283    <p>
284    The calling code <b>must</b> have a <tt>return</tt> statement immediately
285    after calling this method, otherwise RequestDispatching will probably not
286    work as intended.
287    
288    @param  req   the current request
289    @param  res   the current response
290    @param  URL   The pathname specified may be relative, although it 
291            cannot extend outside the current servlet context. If the
292            path begins with a "/" it is interpreted as starting from
293            the root of the current context. (i.e., the webapp context,
294            if present, does not need to be specified).
295    **/
296    public static final void forward ( HttpServletRequest req, 
297                       HttpServletResponse res,
298                       String URL)
299    throws ServletException, IOException
300      {
301      RequestDispatcher rd = req.getRequestDispatcher(URL);
302      if (log.canLog(Log.DEBUG)) {
303        StringBuffer buf = getRequestURL(req);
304        log.bug(buf, "forwarding to page: ", URL);
305        }
306      rd.forward(req, res); 
307      }
308    
309    /** 
310    Redirects the browser to the specified URL via a client side redirect URL.
311    Automatically creates a full URL <u>(including the webapp context path)</u>
312    suitable for this purpose. This method is a thin wrapper around {@link
313    javax.servlet.http.HttpServletResponse#sendRedirect Response.sentRedirect} 
314    <p>
315    Session information is preserved if using container provided URL-based sessions. 
316    <p>
317    <u>If the response has already been committed, this method throws an
318    <tt>IllegalStateException</tt></u>. After using this method, the response should be
319    considered to be committed and should not be written to.
320    
321    @param  req     the current request
322    @param  res     the current response
323    @param  location  See 
324              {@link javax.servlet.http.HttpResponse#sendRedirect}.
325              Essentially, the location can be relative to the
326              specified request's URI or relative to the 
327              context root if it contains a leading '/'
328    **/
329    public static final void clientRedirect(
330      HttpServletRequest req, HttpServletResponse res, String location)
331    throws ServletException, IOException
332      {
333      String redirectURL = res.encodeRedirectURL(
334                  absolutePath(req, location));
335      if (log.canLog(Log.DEBUG)) {
336        StringBuffer buf = getRequestURL(req);
337        log.bug(buf, "redirecting to page: ", redirectURL);
338        }   
339      res.sendRedirect(redirectURL);
340      }
341    
342    /** 
343    Creates an absolute path starting from the web document root directory. It
344    does so by prepending the webapp context path (if any) of the specified
345    request to the specified path. The path is <b>not</b> URL encoded with
346    session informtion (see {@link #absoluteEncPath}).
347    <p>
348    This method should be used for all HTML links (in html pages and forms) 
349    and also for client side redirects. This ensures the ability to move the calling code 
350    to a different web app (other than the default root ("/") web app). If
351    it's certain that there will always only be 1 web app (the default)
352    then calling this method is not necessary. 
353    <p>
354    Note also that server-side redirects and includes (with RequestDispatcher)
355    should *not* use this method since all such server side redirects etc
356    automatically work as expected inside the web app context and are always
357    relative to the web app context itself.
358    
359    @param  req   the current HttpServletRequest
360    @param  path  an absolute path starting from the root of
361            the context associated with the request. 
362            If the current webapp (context) is <tt>/foo</tt> and
363            the specified path is <tt>/bar</tt>, then this
364            method will return <tt>/foo/bar</tt>
365    **/
366    public static final String absolutePath(HttpServletRequest req, String path) 
367      {
368      return req.getContextPath() + path;
369      }
370    
371    /** 
372    URL encodes and returns the path obtained via {@link #absolutePath}. 
373    **/
374    public static final String absoluteEncPath(
375      HttpServletRequest req, HttpServletResponse res, String path) 
376      {
377      return res.encodeURL(absolutePath(req, path));
378      }
379      
380    /** 
381    @return the full requested URL including any query parameters.  
382        (the {@link 
383        javax.servlet.Http.HttpServletRequest.getRequestURL() RequestURL}
384        method in HttpServletRequest does not return the query part of the URL)
385    **/
386    public static final StringBuffer getRequestURL(HttpServletRequest req) 
387      {
388      StringBuffer buf =  req.getRequestURL();
389      String qs = req.getQueryString();
390      if (qs !=null) {
391        buf.append('?');
392        buf.append(qs);
393        }
394      return buf;
395      }
396      
397      
398    //== HTML Form related ==========================
399    
400    private static final String confirm = "confirm";
401    private static final String cancel = "cancel";
402    
403    /** 
404    Returns <tt>true</tt> only if a request parameter with the
405    name <tt>confirm</tt> is seen. Forms can set a submit button
406    with this name to signal a confirm request.
407    **/
408    public static final boolean requestCancelled(HttpServletRequest req) {
409      String str = req.getParameter(cancel);
410      return (str != null); //testing for key not value
411      }
412    
413    /** 
414    Returns <tt>true</tt> only if a request parameter with the
415    name <tt>cancel</tt> is seen. Forms can set a submit button
416    with this name to signal a cancel request.
417    **/
418    public static final boolean requestConfirmed(HttpServletRequest req) {
419      String str = req.getParameter(confirm);
420      return (str != null); //testing for key not value
421      }
422      
423    /** 
424    writes all request params received in the specied request to
425    <tt>System.out</tt>. Useful for debugging.
426    **/
427    public static final void debugRequestParams(HttpServletRequest req) 
428      {
429      debugRequestParams(req, System.out);
430      }
431      
432    
433    /** 
434    writes all request params received in the specied request to
435    the specified output stream. Useful for debugging.
436    **/
437    public static final void debugRequestParams(HttpServletRequest req, OutputStream out) 
438      {
439      System.out.print("Request params/");
440      System.out.print(Thread.currentThread().getName());
441      System.out.print(" ");
442      
443      Iterator i = req.getParameterMap().entrySet().iterator();
444      while (i.hasNext()) {
445        Map.Entry me = (Map.Entry) i.next();
446        System.out.print(me.getKey());
447        System.out.print(":");
448        System.out.print(Arrays.toString((String[])me.getValue()));
449        
450        if (i.hasNext()) 
451          System.out.print(", ");
452        else 
453          System.out.println();
454        }
455      }
456    
457    
458    /** 
459    set expire headers to cover all cases
460    **/
461    public static final void setExpireHeaders(HttpServletResponse res) 
462      {
463      // Set standard HTTP/1.1 no-cache headers.
464      res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
465      // Set IE extended HTTP/1.1 no-cache headers (use addHeader).
466      res.addHeader("Cache-Control", "post-check=0, pre-check=0");
467      // Set standard HTTP/1.0 no-cache header.
468      res.setHeader("Pragma", "no-cache");
469      //expire in past
470      res.setHeader("Expires", "-1");
471      }
472    
473    /*
474    Parse a query parameter string (useful for things like websocket and custom
475    servers. The query param should be url form encoded and is decoded using
476    UTF-8 (via {@link java.net.URLDecoder}). Returns a map containing each
477    <tt>name:value</tt> pair
478    */
479    public static Map parseQuery(String query) throws UnsupportedEncodingException 
480      {
481        Map m = new HashMap();
482        String[] params = query.split("&");
483        for (int n = 0; n < params.length; n++) {
484          String param = params[n]; 
485          int i = param.indexOf("=");
486            m.put(
487              URLDecoder.decode(param.substring(0, i), "UTF-8"),    
488              URLDecoder.decode(param.substring(i + 1), "UTF-8"));
489          }
490        return m;
491      }
492    
493    /**
494    Returns true if the client is a mobile device 
495    */
496    public static boolean isMobileClient(HttpServletRequest req)
497      {
498      //https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
499    
500      //name is case insensitve (in servlet and http spec), different browsers send different cases so that's good
501      String user_agent = req.getHeader("user-agent"); 
502      return user_agent != null && user_agent.indexOf("obile") > 0; //Mobile or mobile 
503      }
504    
505    /**
506    Returns true if the client is an iOS mobile device.
507    */
508    public static boolean isIOSClient(HttpServletRequest req)
509      {
510      //https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
511    
512      //name is case insensitve (in servlet and http spec), different browsers send different cases so that's good
513      String user_agent = req.getHeader("user-agent"); 
514      return user_agent != null && user_agent.indexOf("obile") > 0 && user_agent.indexOf("afari") > 0; 
515      }
516      
517    }          //~class WebUtil
518