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
006package fc.web.servlet;
007
008import java.util.*;
009import java.util.logging.*;
010import java.io.*;
011import javax.servlet.*;
012import javax.servlet.http.*;
013import java.net.*;
014
015import fc.io.*;
016
017/** 
018Misc web related utility functions 
019
020@author hursh jain
021**/
022public final class WebUtil 
023{
024final static Log log = Log.get("fc.web.servlet");
025
026//--------------------- getParameters ----------------------
027
028/** 
029Gets the specified required initialization parameter from
030the servlet context. If this param is not found, throws a
031permanent {@link javax.servlet.UnavailableException}.
032**/
033public static final String getRequiredParam
034  (ServletContext context, String name) 
035throws 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/** 
049Gets the specified initialization parameter from the servlet context. If 
050this param is not found, then the specified backup value is returned.
051**/
052public 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/** 
063Gets the specified initialization parameter for the specified servlet
064(via it's ServletConfig). Note, servlet init parameters are servlet
065specific whereas context parameters are shared by all servlets within
066that context. If this param is not found, throws a permanent {@link
067javax.servlet.UnavailableException}.
068**/
069public static final String getRequiredParam(Servlet servlet, String name) 
070throws 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/** 
085Gets the specified initialization parameter for the specified servlet
086(via it's ServletConfig). Note, servlet init parameters are servlet
087specific whereas context parameters are shared by all servlets within
088that context. If this param is not found, then the specified backup value
089is returned.
090**/
091public 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/** 
103Gets the specified required parameter from the request
104object. If this param is not found, throws a ServletException
105**/
106public static final String getRequiredParam(
107            HttpServletRequest request, String name) 
108throws 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/** 
122Gets the specified parameter from the request object. 
123If this param is not found, returns the backup value.
124**/
125public 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/** 
136The returned value will be <tt>true</tt> if the specified parameter
137is present in the request and is a non-null non-empty string (<b>of
138any value</b>). This is useful for radioboxes and checkboxes.
139**/
140public 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/** 
151Convenience method that returns the specified parameter as a boolean 
152value. The returned value will be converted via the
153{@link Boolean#parseBoolean(String)} (for <tt>true</tt>, the 
154value should be non-null and equal ignore case to "true").
155**/
156public 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/** 
167Gets the specified required attribute from the servlet context. If 
168this attribute is not found, throws a permanent 
169{@link javax.servlet.UnavailableException}.
170**/
171public static final Object getRequiredAttribute(
172  ServletContext context, String name) 
173throws 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/** 
187Gets the specified attribute from the servlet context. If this attribute
188is not found, then the specified backup value is returned.
189**/
190public static final Object getAttribute(
191  ServletContext context, String name, String backup) 
192throws ServletException
193  {
194  Object param = context.getAttribute(name);    
195  
196  if (param != null)    
197    return param; 
198  else
199    return backup;
200  }
201
202/** 
203Gets the specified required attribute from the request object. 
204If this param is not found, throws a ServletException.
205**/
206public static final Object getRequiredAttribute(
207            HttpServletRequest request, String name) 
208throws 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/** 
222Gets the specified attribute from the request object. 
223If this attribute is not found, returns the backup value.
224**/
225public static final Object getAttribute(
226  HttpServletRequest request, String name, String backup) 
227throws 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/** 
239Returns a HttpSession attribute as a String or null if the attribute
240was 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**/ 
247public 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/**
257Returns the cookie with the specified name or <tt>null</tt> if no cookie
258was found.
259*/
260public 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/** 
278Redirects the request (server side redirect) to the specified relative URL
279via the {@link javax.servlet.ServletRequest#getRequestDispatcher}. See
280{@link javax.servlet.RequestDispatcher#forward} for restrictions while
281forwarding to another page (in particular, the response must not have been
282committed before calling this method).
283<p>
284The calling code <b>must</b> have a <tt>return</tt> statement immediately
285after calling this method, otherwise RequestDispatching will probably not
286work 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**/
296public static final void forward ( HttpServletRequest req, 
297                   HttpServletResponse res,
298                   String URL)
299throws 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/** 
310Redirects the browser to the specified URL via a client side redirect URL.
311Automatically creates a full URL <u>(including the webapp context path)</u>
312suitable for this purpose. This method is a thin wrapper around {@link
313javax.servlet.http.HttpServletResponse#sendRedirect Response.sentRedirect} 
314<p>
315Session 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
319considered 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**/
329public static final void clientRedirect(
330  HttpServletRequest req, HttpServletResponse res, String location)
331throws 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/** 
343Creates an absolute path starting from the web document root directory. It
344does so by prepending the webapp context path (if any) of the specified
345request to the specified path. The path is <b>not</b> URL encoded with
346session informtion (see {@link #absoluteEncPath}).
347<p>
348This method should be used for all HTML links (in html pages and forms) 
349and also for client side redirects. This ensures the ability to move the calling code 
350to a different web app (other than the default root ("/") web app). If
351it's certain that there will always only be 1 web app (the default)
352then calling this method is not necessary. 
353<p>
354Note also that server-side redirects and includes (with RequestDispatcher)
355should *not* use this method since all such server side redirects etc
356automatically work as expected inside the web app context and are always
357relative 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**/
366public static final String absolutePath(HttpServletRequest req, String path) 
367  {
368  return req.getContextPath() + path;
369  }
370
371/** 
372URL encodes and returns the path obtained via {@link #absolutePath}. 
373**/
374public 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**/
386public 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
400private static final String confirm = "confirm";
401private static final String cancel = "cancel";
402
403/** 
404Returns <tt>true</tt> only if a request parameter with the
405name <tt>confirm</tt> is seen. Forms can set a submit button
406with this name to signal a confirm request.
407**/
408public static final boolean requestCancelled(HttpServletRequest req) {
409  String str = req.getParameter(cancel);
410  return (str != null); //testing for key not value
411  }
412
413/** 
414Returns <tt>true</tt> only if a request parameter with the
415name <tt>cancel</tt> is seen. Forms can set a submit button
416with this name to signal a cancel request.
417**/
418public static final boolean requestConfirmed(HttpServletRequest req) {
419  String str = req.getParameter(confirm);
420  return (str != null); //testing for key not value
421  }
422  
423/** 
424writes all request params received in the specied request to
425<tt>System.out</tt>. Useful for debugging.
426**/
427public static final void debugRequestParams(HttpServletRequest req) 
428  {
429  debugRequestParams(req, System.out);
430  }
431  
432
433/** 
434writes all request params received in the specied request to
435the specified output stream. Useful for debugging.
436**/
437public 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/** 
459set expire headers to cover all cases
460**/
461public 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/*
474Parse a query parameter string (useful for things like websocket and custom
475servers. The query param should be url form encoded and is decoded using
476UTF-8 (via {@link java.net.URLDecoder}). Returns a map containing each
477<tt>name:value</tt> pair
478*/
479public 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/**
494Returns true if the client is a mobile device 
495*/
496public 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/**
506Returns true if the client is an iOS mobile device.
507*/
508public 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