DispatchingServlet.java :  » Web-Framework » argun » biz » hammurapi » web » Java Open Source

Java Open Source » Web Framework » argun 
argun » biz » hammurapi » web » DispatchingServlet.java
/*
  * argun 1.0
 * Web 2.0 delivery framework 
 * Copyright (C) 2007  Hammurapi Group
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * URL: http://www.hammurapi.biz
 * e-Mail: support@hammurapi.biz 
 */
package biz.hammurapi.web;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import biz.hammurapi.authorization.AuthorizationProvider;
import biz.hammurapi.config.Context;
import biz.hammurapi.metrics.MeasurementCategoryFactory;
import biz.hammurapi.metrics.TimeIntervalCategory;
import biz.hammurapi.web.menu.MenuFilter;
import biz.hammurapi.xml.dom.AbstractDomObject;
import biz.hammurapi.xml.dom.CompositeDomSerializer;

/**
 * Dispatching servlet dispatches requests to action methods. Action method is any method which takes 3 parameters:
 * HttpServletRequest, HttpServletResponse, DispatchingServlet or two parameters HttpServletRequest and HttpServletResponse.
 * Return value of action method is processed in the following way:
 * a) If it is instance of Forward then forward is performed
 * b) If it is instance of String then it is gets written to response output stream.
 * c) Otherwise it is XML-ized and then styled. 
 */
public abstract class DispatchingServlet extends StylingServlet {
  private static final String AUTH_NONE = "none";
  private static final String AUTH_TRUST_MENU = "trust-menu";
  private static final String AUTH_STRICT = "strict";
  private static final String AUTHORIZATION = "authorization";
  private static final String EXCEPTION_HANDLING = "exception-handling";
  private static final String PERMISSIONS_ELEMENT = "permissions-element";
  private String permissionsElement;
  private String exceptionHandling;
  private String authorization;
  
  private static final Logger logger=Logger.getLogger(DispatchingServlet.class);
  private static final TimeIntervalCategory tic=MeasurementCategoryFactory.getTimeIntervalCategory(DispatchingServlet.class);
  private static final TimeIntervalCategory atic=MeasurementCategoryFactory.getTimeIntervalCategory(DispatchingServlet.class.getName()+".action");
    private static final String AUTHORIZATION_PROVIDER_ATTRIBUTE = AuthorizationProvider.class.getName();
  
  /**
   * Override this method to return custom serializer if needed.
   * @return DomSerializer
   */
  protected CompositeDomSerializer getDomSerializer() {
      return CompositeDomSerializer.getThreadInstance();
  }
    
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        config.getServletContext().log("Initialization: "+config.getServletName());
        
    authorization = config.getInitParameter(AUTHORIZATION);
    if (authorization == null) {
      authorization = AUTH_STRICT;
    } 
    
    if (!(AUTH_STRICT.equals(authorization) || AUTH_NONE.equals(authorization) || AUTH_TRUST_MENU.equals(authorization))) {
      throw new ServletException("Invalid authorization mode: "+authorization);
    }
    
    String initParameter = config.getInitParameter(PERMISSIONS_ELEMENT);
    if (initParameter!=null) {
      permissionsElement=initParameter;
      config.getServletContext().log("permissions-element="+permissionsElement);
    }
    
    initParameter = config.getInitParameter(EXCEPTION_HANDLING);
    if (initParameter!=null) {
      exceptionHandling=initParameter;
      config.getServletContext().log("exception-handling="+exceptionHandling);
    }      
    
    config.getServletContext().setAttribute("servlet/"+config.getServletName(), this);
    }   
    
    /**
     * Extracts instance name from the path and returns instance for the path.
     * @param actionPath
     * @return Object to invoke action methods
     * @throws HammurapiWebException
     */
    protected abstract Object getActionInstance(String path) throws HammurapiWebException;
    
    /**
     * @param path
     * @return Action method name
     * @throws HammurapiWebException
     */
    protected abstract String getActionName(String path) throws HammurapiWebException;
    
    /**
     * @return Position of "/" in the servlet path info which separates action name from style info.
     */
    protected abstract int styleSeparatorPosition();
        
    private Map actions=new HashMap();
    
    private class Action  {
    private Method method;
        private Object instance;
        private int argCount;
        private boolean isContextMethod; // true if the first parameter is context
        
        public Action(Method method, Object instance) {
            super();
            this.method = method;
            this.instance = instance;
            argCount = method.getParameterTypes().length;
            isContextMethod = Context.class.equals(method.getParameterTypes()[0]);
        }
        
        public Object execute(HttpServletRequest request, HttpServletResponse response, String path) throws HammurapiWebException {
          // Do authorization
          if (AUTH_STRICT.equals(authorization) || (AUTH_TRUST_MENU.equals(authorization) && request.getAttribute(MenuFilter.MENU_MATCHED_ATTRIBUTE)==null)) {
          Object ap = request.getAttribute(AUTHORIZATION_PROVIDER_ATTRIBUTE);
          if (ap == null) {
            HttpSession session = request.getSession(false);
            ap = session == null ? null : session.getAttribute(AUTHORIZATION_PROVIDER_ATTRIBUTE);
          }
                    
        if (ap instanceof AuthorizationProvider) {
          if (!((AuthorizationProvider) ap).hasInstancePermission(instance, method.getName())) {
            return "You are not authorized to access this page"; // Replace with proper Http error if needed.
          }
        } else if (ap!=null) {
          logger.warn("Configuration problem with authorization provider: "+ ap.getClass().getName()+" does not implement "+AuthorizationProvider.class.getName());            
        }
          }
          
          long start=atic.getTime();
            try {
              Object[] args;
              if (isContextMethod) {
                RequestContext rc = new RequestContext(request);
                switch (argCount) {
                case 1:
                  args=new Object[] {rc};
                  break;
                case 2:
                  args=new Object[] {rc, DispatchingServlet.this};
                  break;
                case 3:
                  args=new Object[] {rc, DispatchingServlet.this, path};
                  break;
                default:
                  throw new HammurapiWebException("Invalid number of arguments in method "+method);              
                }                
              } else {
                switch (argCount) {
                case 2:
                  args=new Object[] {request, response};
                  break;
                case 3:
                  args=new Object[] {request, response, DispatchingServlet.this};
                  break;
                case 4:
                  args=new Object[] {request, response, DispatchingServlet.this, path};
                  break;
                default:
                  throw new HammurapiWebException("Invalid number of arguments in method "+method);              
                }
              }
                return method.invoke(instance, args);
            } catch (IllegalAccessException e) {
              logger.error("Exception is action "+method.toString(), e);
                throw new HammurapiWebException(e);
            } catch (InvocationTargetException e) {
              logger.error("Exception is action "+method.toString(), e);
              if (e.getCause()!=null) {
                  if ("message".equals(exceptionHandling)) {
                    return e.getCause().getMessage();
                  } else if ("to-string".equals(exceptionHandling)) {
                    return e.getCause().toString();
                  } else if ("stack-trace".equals(exceptionHandling)) {
                    StringWriter sw = new StringWriter();
                    PrintWriter pw = new PrintWriter(sw);
                    e.getCause().printStackTrace(pw);
                    pw.close();
                    try {
              sw.close();
            } catch (IOException e1) {
              throw new HammurapiWebException("Should never ever happen", e1);
            }
                    return "<pre>"+sw.toString()+"</pre>";                    
                  } else if ("cause-message".equals(exceptionHandling)) {
                    return getToTheCause(e).getMessage();
                  } else if ("cause-to-string".equals(exceptionHandling)) {
                    return getToTheCause(e).toString();
                  } else if ("cause-stack-trace".equals(exceptionHandling)) {
                    StringWriter sw = new StringWriter();
                    PrintWriter pw = new PrintWriter(sw);
                    getToTheCause(e).printStackTrace(pw);
                    pw.close();
                    try {
              sw.close();
            } catch (IOException e1) {
              throw new HammurapiWebException("Should never ever happen", e1);
            }
                    return "<pre>"+sw.toString()+"</pre>";                    
                  }
              }
                throw new HammurapiWebException(e);
            } finally {
              atic.addInterval(method.toString(), start);
            }
        }

        /**
         * Executes action without authorization checks
         * @param ctx - Context, typically request context
         * @param path - action path tail
         * @return
         * @throws HammurapiWebException
         */
        public Object execute(Context ctx, String path) throws HammurapiWebException {
          
          long start=atic.getTime();
            try {
              Object[] args;
              if (isContextMethod) {
                switch (argCount) {
                case 1:
                  args=new Object[] {ctx};
                  break;
                case 2:
                  args=new Object[] {ctx, DispatchingServlet.this};
                  break;
                case 3:
                  args=new Object[] {ctx, DispatchingServlet.this, path};
                  break;
                default:
                  throw new HammurapiWebException("Invalid number of arguments in method "+method);              
                }                
              } else {
                throw new HammurapiWebException("Cannot execute non-context method: "+method);              
              }
                return method.invoke(instance, args);
            } catch (IllegalAccessException e) {
              logger.error("Exception is action "+method.toString(), e);
                throw new HammurapiWebException(e);
            } catch (InvocationTargetException e) {
              logger.error("Exception is action "+method.toString(), e);
              if (e.getCause()!=null) {
                  if ("message".equals(exceptionHandling)) {
                    return e.getCause().getMessage();
                  } else if ("to-string".equals(exceptionHandling)) {
                    return e.getCause().toString();
                  } else if ("stack-trace".equals(exceptionHandling)) {
                    StringWriter sw = new StringWriter();
                    PrintWriter pw = new PrintWriter(sw);
                    e.getCause().printStackTrace(pw);
                    pw.close();
                    try {
              sw.close();
            } catch (IOException e1) {
              throw new HammurapiWebException("Should never ever happen", e1);
            }
                    return "<pre>"+sw.toString()+"</pre>";                    
                  } else if ("cause-message".equals(exceptionHandling)) {
                    return getToTheCause(e).getMessage();
                  } else if ("cause-to-string".equals(exceptionHandling)) {
                    return getToTheCause(e).toString();
                  } else if ("cause-stack-trace".equals(exceptionHandling)) {
                    StringWriter sw = new StringWriter();
                    PrintWriter pw = new PrintWriter(sw);
                    getToTheCause(e).printStackTrace(pw);
                    pw.close();
                    try {
              sw.close();
            } catch (IOException e1) {
              throw new HammurapiWebException("Should never ever happen", e1);
            }
                    return "<pre>"+sw.toString()+"</pre>";                    
                  }
              }
                throw new HammurapiWebException(e);
            } finally {
              atic.addInterval(method.toString(), start);
            }
        }        
    }
    
    private Throwable getToTheCause(Throwable th) {
      while (th.getCause()!=null && th.getCause()!=th) {
        th = th.getCause();
      }
      
      return th;
    }
    
    /**
     * Subclasses can override this method and throw an exception of methods
     * which are not supposed to be dispatched to.
     * @param method
     * @throws HammurapiWebException 
     */
    protected void verifyMethod(Method method) throws HammurapiWebException {
      
    }
    
    private synchronized Action getAction(String path) throws HammurapiWebException {
        Action ret=(Action) actions.get(path);
        if (ret==null) {
            Object instance = getActionInstance(path);
            if (instance == null) {
              throw new HammurapiWebException("Action instance not found for path "+path); 
            }
            String actionName = getActionName(path);
            
            Method candidate = null;
            
            Method[] methods = instance.getClass().getMethods();
            for (int i=0; i<methods.length; ++i) {
              Method method = methods[i];
        Class[] parameterTypes = method.getParameterTypes();
        if (parameterTypes.length==4 
            && method.getName().equals(actionName)
            && HttpServletRequest.class.equals(parameterTypes[0]) 
            && HttpServletResponse.class.equals(parameterTypes[1]) 
            && parameterTypes[2].isInstance(this)
            && String.class.equals(parameterTypes[3])) {
                candidate = method;
                break;
              }
            }
            
            if (candidate == null) {
                for (int i=0; i<methods.length; ++i) {
                  Method method = methods[i];
            Class[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length==3 
                && method.getName().equals(actionName)
                && HttpServletRequest.class.equals(parameterTypes[0]) 
                && HttpServletResponse.class.equals(parameterTypes[1]) 
                && parameterTypes[2].isInstance(this)) {
                    candidate = method;
                    break;
                  }
                }                              
            }
            
            if (candidate == null) {
                for (int i=0; i<methods.length; ++i) {
                  Method method = methods[i];
            Class[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length==2 
                && method.getName().equals(actionName)
                && HttpServletRequest.class.equals(parameterTypes[0]) 
                && HttpServletResponse.class.equals(parameterTypes[1])) {
                    candidate = method;
                    break;
                  }
                }                              
            }
            
            // Methods which take context instead or request and response
            for (int i=0; i<methods.length; ++i) {
              Method method = methods[i];
        Class[] parameterTypes = method.getParameterTypes();
        if (parameterTypes.length==3 
            && method.getName().equals(actionName)
            && Context.class.equals(parameterTypes[0]) 
            && parameterTypes[2].isInstance(this)
            && String.class.equals(parameterTypes[3])) {
                candidate = method;
                break;
              }
            }
            
            if (candidate == null) {
                for (int i=0; i<methods.length; ++i) {
                  Method method = methods[i];
            Class[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length==2 
                && method.getName().equals(actionName)
                && Context.class.equals(parameterTypes[0]) 
                && parameterTypes[2].isInstance(this)) {
                    candidate = method;
                    break;
                  }
                }                              
            }
            
            if (candidate == null) {
                for (int i=0; i<methods.length; ++i) {
                  Method method = methods[i];
            Class[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length==1 
                && method.getName().equals(actionName)
                && Context.class.equals(parameterTypes[0])) {
                    candidate = method;
                    break;
                  }
                }                              
            }                       
            
            if (candidate == null) {
              throw new HammurapiWebException("Action method "+actionName+" not found for path "+path+" in class "+instance.getClass().getName());
            }
            
            ret=new Action(candidate, instance);
            actions.put(path, ret);
        }
        
        return ret;
    }
    
    /** Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response)  throws ServletException, java.io.IOException {
        processRequest(request, response);
    }
    
    /** Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, java.io.IOException {
        processRequest(request, response);
    }
    
    protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      long start=tic.getTime();
    String pathInfo=request.getPathInfo();
    logger.debug("Serving "+pathInfo);
      try {
      if (pathInfo==null || "/".equals(pathInfo)) {
        response.sendError(404, "Invalid action path");
        return;
      } 
  
        int styleIdx=pathInfo.indexOf("/", styleSeparatorPosition());
        if (styleIdx==-1) {
        response.sendError(404, "Invalid action path");
        return;
        }
        
        styleIdx=pathInfo.indexOf("/", styleIdx+1);
      
        Action action=getAction(styleIdx==-1 ? pathInfo.substring(1) : pathInfo.substring(1, styleIdx));
        String styleName=styleIdx==-1 ? null : pathInfo.substring(styleIdx+1);
        
        Object ret=action.execute(request, response, styleName);      
        if (ret==null) {
            return;
        }
        
        if (ret instanceof Forward) {
            getServletContext().getRequestDispatcher(((Forward) ret).getUri()).forward(request, response);
            return;
        }
        
        if (ret instanceof Include) {
            getServletContext().getRequestDispatcher(((Include) ret).getUri()).include(request, response);
            return;
        }
        
        if (ret instanceof Redirect) {
            Redirect redirect = (Redirect) ret;
        response.sendRedirect(redirect.getLocation());
            PrintWriter out = response.getWriter();
            out.write("<HTML><HEAD></HEAD><BODY>");
            out.write(redirect.getMessage()==null ? "Redirecting" : redirect.getMessage());
            out.write("</BODY></HTML>");
            return;
        }        
        
        if (ret instanceof HttpError) {
            HttpError error = (HttpError) ret;
            if (error.getMessage()==null) {
              response.sendError(error.getErrorCode());
            } else {
              response.sendError(error.getErrorCode(), error.getMessage());
            }              
            return;
        }        
        
        if (ret instanceof String) {
            Writer w=new OutputStreamWriter(response.getOutputStream());
            w.write((String) ret);
            w.close();
            return;
        }
        
        style(ret, styleName, request, response);
      } catch (HammurapiWebException e) {
        logger.error("Exception serving '"+pathInfo+"': "+e, e);
        throw new ServletException(e);
      } catch (ParserConfigurationException e) {
        throw new ServletException(e);
    } finally {
      tic.addInterval(pathInfo, start);
    }
    }

    public String executeAction(String pathInfo, Context ctx) throws ServletException, IOException {
      long start=tic.getTime();
    logger.debug("Serving "+pathInfo);
      try {
      if (pathInfo==null || "/".equals(pathInfo)) {
        return "Invalid action path";
      } 
  
        int styleIdx=pathInfo.indexOf("/", styleSeparatorPosition());
        if (styleIdx==-1) {
        return "Invalid action path";
        }
        
        styleIdx=pathInfo.indexOf("/", styleIdx+1);
      
        Action action=getAction(styleIdx==-1 ? pathInfo : pathInfo.substring(0, styleIdx));
        String styleName=styleIdx==-1 ? null : pathInfo.substring(styleIdx+1);
        
        Object ret=action.execute(ctx, styleName);      
        if (ret==null) {
            return null;
        }
        
        if (ret instanceof Forward) {            
            return "Forward: "+((Forward) ret).getUri();
        }
        
        if (ret instanceof Include) {
            return "Include: "+((Include) ret).getUri();
        }
        
        if (ret instanceof Redirect) {
            Redirect redirect = (Redirect) ret;
          return "Redirect to "+redirect.getLocation()+" with message "+redirect.getMessage();
        }        
        
        if (ret instanceof HttpError) {
            HttpError error = (HttpError) ret;            
            if (error.getMessage()==null) {
              return "Error "+error.getErrorCode();
            }
            
        return "Error "+error.getErrorCode()+": "+ error.getMessage();              
        }        
        
        if (ret instanceof String) {
            return (String) ret;
        }
        
        StringWriter sw = new StringWriter();
        style(ret, styleName, ctx, new StreamResult(sw));
        sw.close();
        return sw.toString();
      } catch (HammurapiWebException e) {
        logger.error("Exception serving '"+pathInfo+"': "+e, e);
        throw new ServletException(e);
      } catch (ParserConfigurationException e) {
        throw new ServletException(e);
    } finally {
      tic.addInterval(pathInfo, start);
    }
    }
    
    /**
     * Converts object returned from action to XML and applies style
     * @param ret Object to be styled
     * @param styleName Style name
     * @param request request
     * @param response response
     * @throws ParserConfigurationException
     * @throws FactoryConfigurationError
     * @throws ServletException
     */
  public void style(Object ret, String styleName, HttpServletRequest request, HttpServletResponse response) throws ParserConfigurationException, FactoryConfigurationError, ServletException {
    if (ret instanceof Document) {
      getTransformer(styleName).transform((Node) ret, getSetParametersCallback(request, styleName), response);
    } else {
      Document doc=newDocumentBuilder().newDocument();          
        Element re = doc.createElement("response");
        re.setAttribute("context-path", request.getContextPath());
        doc.appendChild(re);
        getDomSerializer().toDomSerializable(ret).toDom(re);
        if (permissionsElement!=null) {
        Object ap = request.getAttribute(AUTHORIZATION_PROVIDER_ATTRIBUTE);
        if (ap == null) {
          HttpSession session = request.getSession(false);
          ap = session == null ? null : session.getAttribute(AUTHORIZATION_PROVIDER_ATTRIBUTE);
        }
                  
        if (ap instanceof AuthorizationProvider) {
          getDomSerializer()
            .toDomSerializable(((AuthorizationProvider) ap).getPermissions())
            .toDom(AbstractDomObject.addElement(re, permissionsElement));
        } else if (ap!=null) {
          logger.warn("Configuration problem with authorization provider: "+ ap.getClass().getName()+" does not implement "+AuthorizationProvider.class.getName());            
        } 
        }
      getTransformer(styleName).transform(doc, getSetParametersCallback(request, styleName), response);
    }    
  }
    
    /**
     * Converts object returned from action to XML and applies style
     * @param ret Object to be styled
     * @param styleName Style name
     * @param request request
     * @param response response
     * @throws ParserConfigurationException
     * @throws FactoryConfigurationError
     * @throws ServletException
     */
  public void style(Object ret, String styleName, Context context, Result result) throws ParserConfigurationException, HammurapiWebException {
    Document doc;
    if (ret instanceof Document) {
      doc=(Document) ret;
    } else {
      doc=newDocumentBuilder().newDocument();          
        Element re = doc.createElement("response");
        String contextPath = (String) context.get("context-path");
        if (contextPath!=null) {
          re.setAttribute("context-path", contextPath);
        }
        doc.appendChild(re);
        getDomSerializer().toDomSerializable(ret).toDom(re);
    }
    
    getTransformer(styleName).transform(doc, result);
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.