/*
 * RedirectionFilter.java,v 1.1 21/09/2004
 * Copyright (c) 2004 Esup Portail (www.esup-portail.org)
 * Classes: RedirectionFilter
 * Original Author: Raymond Bourges
*/

package org.esupportail.filter.redirectionFilter;

import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLDecoder;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import org.apache.log4j.Logger;


/**
 * Redirects an URL like /~user to the user homedir.<br/>
 * Two redirections are possible : direct and http<br/>
 * direct : the URL in the client navigator does not change - the redirection is invisible<br/>
 * http : the URL in the client navigator changes<br/>
 * 
 * Homedirs are built with a hash method (to improve access). The hash method can be :<br/>
 * with_hash : /u/us/user<br/>
 * with_hash_reverse : /r/re/user<br/>
 * 
 * The configuration of the redirection type and hash method is specified in the web.xml file of Slide<br/>
 * 
 * @author Raymond Bourges
 * date : 28/06/2004 
 * 
 * update by Julien Marchal
 * date : 21/09/2004
 */
public class RedirectionFilter implements Filter {
	private javax.servlet.FilterConfig filterConfig;
	private boolean debug = false;
	
	public Logger logger;
	/* (non-Javadoc)
	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
	 */
	public void init(FilterConfig arg0) throws ServletException {
		filterConfig = arg0;
		logger = Logger.getLogger("org.esupportail.filter.redirectionFilter.RedirectionFilter");
	}

	/* (non-Javadoc)
	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
	 */
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		// Getting the current URI and the contextPath in the request
        HttpServletRequest hreq = (HttpServletRequest)request;
        HttpServletResponse hres = (HttpServletResponse)response;
        // Must decode url ~ = %7E in a few client
        String requestUri = URLDecoder.decode(hreq.getRequestURI(), "UTF-8"); // ex: /context/~user
        String contextPath = hreq.getContextPath(); // ex: /context
        
        String newPath = null;
        String userPath = null;
        String username = null;
        boolean applyFilterDirect = false;
        boolean applyFilterHttp = false;
    	 
        // get debug state in web.xml
        //debug = new Boolean(filterConfig.getInitParameter("debug")).booleanValue();       	          
        String forwardMethod = filterConfig.getInitParameter("forwardMethod");                              
        
        // make a wrapper to manipulate request
        RequestWrapper requestWrapper = new RequestWrapper(hreq);       
        
        // make a wrapper to manipulate response
        ResponseCharArrayWrapper responseWrapper = new ResponseCharArrayWrapper(hres);	    
                
        if(logger.isDebugEnabled()){ 
            logger.debug("requestUri : "+requestUri);
            logger.debug("contextPath : "+contextPath);            
        }// if(logger.isDebugEnabled())
        
        int contextLength = contextPath.length();
		if (requestUri.length() > contextLength+4) {
			// If there is something after /context, looking for the username in the URL /context/~username
			if (requestUri.substring(contextLength+1, contextLength+2).equals("~")) {			    			  				  
				// There are two kinds of URL :
				// /context/~user
				// /context/~user/subdirectory/..
				int slashPos = requestUri.indexOf("/", contextLength+2);
				if (slashPos < 0) {
					username = requestUri.substring(contextLength+2);					
				}
				else {
					username = requestUri.substring(contextLength+2, requestUri.indexOf("/", contextLength+2));										
				}
				
				if(logger.isDebugEnabled()){         
		            logger.debug("username : "+username);          
		        }// if(logger.isDebugEnabled())
				
				int usernameLength = username.length();
	    		// Looking for hashType (filter parameter in web.xml)
	        	String hashType = filterConfig.getInitParameter("hashType");
	        	String homedirsRootDirectory = filterConfig.getInitParameter("homedirsRootDirectory");
	        	
	        	if(logger.isDebugEnabled()){ 
	        	    logger.debug("hashType : "+hashType);
	        	    logger.debug("homedirsRootDirectory : "+homedirsRootDirectory);
	        	}// if(logger.isDebugEnabled()){ 
	        	
	        	if (hashType.equals("with_hash")) {
	        	    userPath = "/"+homedirsRootDirectory+"/"+username.substring(0,1)+"/"+username.substring(0,2)+"/"+username;
	        	    
	        	    newPath = userPath+requestUri.substring(contextLength+2+usernameLength);	    							
	        	    requestWrapper.setDestination(contextPath+"/~"+username,contextPath+userPath);
	        	    
					if(forwardMethod.equals("direct")){
					    filterConfig.getServletContext().getRequestDispatcher(newPath).forward(requestWrapper, responseWrapper);
						applyFilterDirect=true;						
					}
					else if(forwardMethod.equals("http")){
					    hres.sendRedirect(contextPath+newPath);	
					    applyFilterHttp = true;
					}					
	        	}
	    		else if (hashType.equals("with_hash_reverse")) {
	    		    userPath = "/"+homedirsRootDirectory+"/"+username.substring(usernameLength-1)+"/"+username.substring(usernameLength-1)+username.substring(usernameLength-2,usernameLength-1)+"/"+username;
	        	    
	    		    newPath = userPath+requestUri.substring(contextLength+2+username.length());
	    		    requestWrapper.setDestination(contextPath+"/~"+username,contextPath+userPath);
	        	    
	    			if(forwardMethod.equals("direct")){
	    			    filterConfig.getServletContext().getRequestDispatcher(newPath).forward(requestWrapper, responseWrapper);
						applyFilterDirect=true;
					}
					else if(forwardMethod.equals("http")){
					    hres.sendRedirect(contextPath+newPath);
					    applyFilterHttp = true;
					}
	    		}
	    		else {
	    			if(logger.isDebugEnabled()){ 
	    				logger.debug("Undefine hashType : "+hashType+" please view the configuration file");
	    			}// if(logger.isDebugEnabled()){ 
	    	    	chain.doFilter(request, response);
	    		}
			}
			else {
				chain.doFilter(request, response);
			}
		}
		else {
			chain.doFilter(request, response);
		}
		
		String responseString = responseWrapper.toString();
		
		if(applyFilterDirect){
			if(logger.isDebugEnabled()){       
		        logger.debug("Filter (direct) was applied");	
		        logger.debug("Replace in response : from "+ contextPath+userPath + " to "+ contextPath+"/~"+username);	
	        }// if(logger.isDebugEnabled()){ 
		    responseString = responseString.replaceAll(contextPath+userPath,contextPath+"/~"+username);
		    if(logger.isDebugEnabled()){     
		    	logger.debug("New response : ");	
		    	logger.debug(responseString);	
	        }// if(logger.isDebugEnabled()){ 
		}else if(applyFilterHttp && debug){
			 if(logger.isDebugEnabled()){ 
			 	logger.debug("Filter (http) was applying");	
			 }//  if(logger.isDebugEnabled()){ 
        }else if(debug){         
        	 if(logger.isDebugEnabled()){ 
        	 	logger.debug("Filter was not applying");	
        	 }//  if(logger.isDebugEnabled()){ 
        }
		
        
		response.setContentLength(responseString.length());
	
		if(!response.isCommitted()){
		    PrintWriter out = response.getWriter();
		    out.write(responseString);
		}
		
		if(debug && (applyFilterHttp || applyFilterDirect)){  
			 if(logger.isDebugEnabled()){ 
			 	logger.debug("Redirection : "+requestUri+" > "+newPath);
			 	logger.debug(""); 
			 }//  if(logger.isDebugEnabled()){ 
        }
	}

	/* (non-Javadoc)
	 * @see javax.servlet.Filter#destroy()
	 */	
	public void destroy() {
	    filterConfig = null;
	    debug = false;
	}

	public class RequestWrapper extends HttpServletRequestWrapper {
	    private HttpServletRequest request = null;
	    private String desination = null;
	    
		/** Initializes wrapper.
		*  <P>
		*  First, this constructor calls the parent
		*  constructor. That call is crucial so that the response
		*  is stored and thus setHeader, setStatus, addCookie,
		*  and so forth work normally.
		*  <P>
		*  Second, this constructor creates a CharArrayWriter
		*  that will be used to accumulate the response.
		 * @throws IOException
		*/
		
		public RequestWrapper(HttpServletRequest req) {		  
		    super(req);
		    request=req;
		}
		
		public String getHeader(String header){
		    if(header.equals("Destination")){
		        return(desination);
		    }else{
		        return(super.getHeader(header));
		    }
		}
		
		public void setDestination(String before, String after){
		    if((desination= request.getHeader("destination")) != null){
		        desination=desination.replaceAll(before,after);
		    }
		}
	}
	
	public class ResponseCharArrayWrapper extends HttpServletResponseWrapper {
	    private CharArrayWriter charWriter;

		/** Initializes wrapper.
		*  <P>
		*  First, this constructor calls the parent
		*  constructor. That call is crucial so that the response
		*  is stored and thus setHeader, setStatus, addCookie,
		*  and so forth work normally.
		*  <P>
		*  Second, this constructor creates a CharArrayWriter
		*  that will be used to accumulate the response.
		*/
		
		public ResponseCharArrayWrapper(HttpServletResponse response) {
		    super(response);
		    charWriter = new CharArrayWriter();
		}

		/** When servlets or JSP pages ask for the Writer, 
		*  don't give them the real one. Instead, give them
		*  a version that writes into the character array.
		*  The filter needs to send the contents of the
		*  array to the client (perhaps after modifying it).
		*/
		
		public PrintWriter getWriter() {
		    return(new PrintWriter(charWriter));
		}

		/** Get a String representation of the entire buffer.
		*  <P>
		*  Be sure <B>not</B> to call this method multiple times
		*  on the same wrapper. The API for CharArrayWriter
		*  does not guarantee that it "remembers" the previous
		*  value, so the call is likely to make a new String
		*  every time.
		*/
		
		public String toString() {
		    return(charWriter.toString());
		}
	}

}
