alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *  Copyright 1999-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.tomcat.facade;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.tomcat.core.Context;
import org.apache.tomcat.core.Handler;
import org.apache.tomcat.core.Request;
import org.apache.tomcat.core.Response;
import org.apache.tomcat.util.compat.Action;
import org.apache.tomcat.util.compat.Jdk11Compat;
import org.apache.tomcat.util.http.Parameters;
import org.apache.tomcat.util.res.StringManager;

/* This code needs a re-write, it's very ugly.
   The hardest problem is the requirement to pass the "same" request, but with
   small modifications. One solution is to use a facade ( was used in tomcat
   origianlly ). The current solution is to save the modified attributes
   and restore after the method returns. This saves one object creation -
   since the subRequest object still has to be created.

   The details are facade-specific, shouldn't affect the core.
*/

/*
  We do a new sub-request for each include() or forward().
  Even if today we take all decisions based only on path, that may
  change ( i.e. a request can take different paths based on authentication,
  headers, etc - other Interceptors may affect it), that means we need to
  call CM.

  I think this is the correct action - instead of doing a lookup when
  we construct the dispatcher. ( costin )
 */

/**
 *
 *
 * @author James Duncan Davidson [duncan@eng.sun.com]
 * @author Jason Hunter [jch@eng.sun.com]
 * @author James Todd [gonzo@eng.sun.com]
 * @author Alex Cruikshank [alex@epitonic.com]
 * @author costin@dnt.ro
 */
final class RequestDispatcherImpl implements RequestDispatcher {
    static final boolean debug=false;
    // Use the strings from core
    private static StringManager sm = StringManager.
	getManager("org.apache.tomcat.resources");

    // Attributes that will be replaced during include
    private static final String A_REQUEST_URI=
	"javax.servlet.include.request_uri";
    private static final String A_CONTEXT_PATH=
    	"javax.servlet.include.context_path";
    private static final String A_SERVLET_PATH=
	"javax.servlet.include.servlet_path";
    private static final String A_PATH_INFO=
	"javax.servlet.include.path_info";
    private static final String A_QUERY_STRING=
	"javax.servlet.include.query_string";
    
    
    Context context;

    // path dispatchers
    String path;
    String queryString;

    // name dispatchers
    String name;
    private Object accessControlContext=null;
    
    /** Used for Context.getRD( path )
     */
    public RequestDispatcherImpl(Context context, Object acc) {
        this.context = context;
	accessControlContext=acc;
    }

    public void setPath( String urlPath ) {
	// separate the query string
	int i = urlPath.indexOf("?");
	if( i<0 )
	    this.path=urlPath;
	else {
	    this.path=urlPath.substring( 0,i );
	    int len=urlPath.length();
	    if( i< len )
		this.queryString =urlPath.substring(i + 1);
        }
    }

    public void setName( String name ) {
	this.name=name;
    }

    // -------------------- Public methods --------------------

    // Wrappers for jdk1.2 priviledged actions
    Jdk11Compat jdk11Compat=Jdk11Compat.getJdkCompat();
    RDIAction forwardAction=new RDIAction( this,false);
    RDIAction includeAction=new RDIAction( this,true);
    
    public void forward(ServletRequest request, ServletResponse response)
	throws ServletException, IOException
    {
	if( System.getSecurityManager() != null ) {
	    try {
		forwardAction.prepare( request, response );
		jdk11Compat.doPrivileged( forwardAction, accessControlContext );
	    } catch( Exception e) {
		wrapException( e, null );
	    }
	} else {
	    doForward(request,response);
	}
    }

    public void include(ServletRequest request, ServletResponse response)
	throws ServletException, IOException
    {
	if( System.getSecurityManager() != null ) {
	    try {
		includeAction.prepare( request, response );
		jdk11Compat.doPrivileged( includeAction, accessControlContext );
	    } catch( Exception e) {
		wrapException( e, null );
	    }
	} else {
	    doInclude(request,response);
	}
    }

    // -------------------- Actual forward/include impl --------------------
    
    private void doForward(ServletRequest request, ServletResponse response)
	throws ServletException, IOException
    {
	/** We need to find the request/response. The servlet API
	 *  guarantees that we will receive the original request as parameter.
	 */
	Request realRequest = ((HttpServletRequestFacade)request).
	    getRealRequest();
        Response realResponse = realRequest.getResponse();

	// according to specs (as of 2.2: started is OK, just not committed)
	if (realResponse.isBufferCommitted()) 
	    throw new IllegalStateException(sm.getString("rdi.forward.ise"));

	// reset the output buffer but not the headers and cookies
	realResponse.resetBuffer();

	// the strange case in a separate method.
	if( name!=null) {
	    forwardNamed( request, response );
	    return;
	}
	
	// from strange spec reasons, forward and include are very different in
	// the way they process the request - if you don't understand the code
	// try to understand the spec.
	
	// in forward case, the Path parametrs of the request are what you 
	// expect, so we just do a new processRequest on the modified request

	// set the context - no need to fire context parsing again
	realRequest.setContext( context );
	
	realRequest.requestURI().setString( context.getPath() + path );
	// # 3162
	realRequest.unparsedURI().recycle();
	//realRequest.query().recycle();
	realRequest.servletPath().recycle();
	realRequest.pathInfo().recycle();
    realRequest.setChild(null);

	// merge query string as specified in specs - before, it may affect
	// the way the request is handled by special interceptors
	if( queryString != null ) {
	    // Process existing parameters, if not already done so
	    // ( otherwise we'll process some twice )
	    realRequest.parameters().handleQueryParameters();
	    // Set the query string - the sum of the new one and old one.
	    String oldQS=realRequest.queryString().toString();
	    String newQS=(oldQS==null ) ? queryString : queryString + "&" +
		oldQS;
	    realRequest.queryString().setString(newQS);

	    // Process the additional parsm. We don't know if the old
	    // params were processed ( so we need to make sure they are,
	    // i.e. a known state ).
	    realRequest.parameters().push();
	    Parameters child=realRequest.parameters().getCurrentSet();

	    child.processParameters( queryString );
	    //realRequest.parameters().processParameters( queryString ); 
	}
	
	// run the new request through the context manager
	// not that this is a very particular case of forwarding
	context.getContextManager().processRequest(realRequest);

	// unset "included" attribute if any - we may be in a servlet
	// included from another servlet,
	// in which case the attribute will create problems
	realRequest.removeAttribute( A_REQUEST_URI);
	realRequest.removeAttribute( A_SERVLET_PATH);

	// CM should have set the wrapper - call it
	Handler wr=realRequest.getHandler();
	if( wr!=null ) {
	    try {
		wr.service(realRequest, realResponse);
	    } catch( Exception ex ) {
		realResponse.setErrorException(ex);
	    }
	}

	// Clean up the request and response as needed
	// No action required

	if ( realResponse.isExceptionPresent() ) {
	    // if error URI not set, set our URI
	    if ( null == realResponse.getErrorURI() )
		realResponse.setErrorURI( context.getPath() + path );
	    Exception ex = realResponse.getErrorException();
	    wrapException( ex,
			   sm.getString("dispatcher.forwardException"));
	}
	
	// close the response - output after this point will be discarded.
	// XXX XXX Maybe this is Henri's bug !!!
	realResponse.finish();
    }

    // -------------------- Include --------------------

    private void doInclude(ServletRequest request, ServletResponse response)
	throws ServletException, IOException
    {
        Request realRequest = ((HttpServletRequestFacade)request).
	    getRealRequest();
	Response realResponse = realRequest.getResponse();
	

	if( debug ) {
	    System.out.println("RDI: doInclude: " + path + " " + name +
			       " " + queryString );
	}
	
	// the strange case in a separate method
	if( name!=null) {
	    includeNamed( request, response );
	    return;
	}

	// Implement the spec that "no changes in response, only write"
	// can also be done by setting the response to 0.9 mode
	//	IncludedResponse iResponse = new IncludedResponse(realResponse);
	boolean old_included=realResponse.isIncluded();
	if( ! old_included ) {
	    realResponse.setIncluded( true );
	}

	// We need to pass the original request, with all the paths -
	// and the new paths in special attributes.

	// We still need to find out where do we want to go ( today )
	// That means we create a subRequest with the new paths ( since
	// the mapping and aliasing is done on Requests), and run it
	// through prepare.

	// That also means that some special cases ( like the invoker !! )
	// will have to pay attention to the attributes, or we'll get a loop

	Request subRequest=context.getContextManager().
	    createRequest( context, path );
	subRequest.setParent( realRequest );
	subRequest.getTop(); // control inclusion depth

	// I hope no interceptor (or code) in processRequest use any
	// of the original request info ( like Auth headers )
	//
	// XXX We need to clone the request, so that processRequest can
	// make an informed mapping ( Auth, Authorization, etc)
	//
	// This will never work corectly unless we do a full clone - but
	// for simple cases ( no auth, etc) it does

	// note that we also need a dummy response - SessionInterceptors may
	// change something !
	subRequest.setResponse( realResponse );
	
	context.getContextManager().processRequest(subRequest);
	
	// Now subRequest containse the processed and aliased paths, plus
	// the wrapper that will handle the request.

	// We will use the stack a bit - save all path attributes, set the
	// new values, and after return from wrapper revert to the original
	Object old_request_uri=replaceAttribute(realRequest, A_REQUEST_URI,
						context.getPath() + path );
	Object old_context_path=replaceAttribute(realRequest, A_CONTEXT_PATH,
						 context.getPath());
	Object old_servlet_path=replaceAttribute(realRequest, A_SERVLET_PATH,
					 subRequest.servletPath().toString());
	Object old_path_info=replaceAttribute(realRequest, A_PATH_INFO,
					  subRequest.pathInfo().toString());
	Object old_query_string=replaceAttribute(realRequest, A_QUERY_STRING,
						 queryString);

	if( debug ) {
	    System.out.println("RDI: old " + old_request_uri + " " +
			       old_context_path + " " + old_servlet_path +
			       " " + old_path_info + " " + old_query_string);
	    System.out.println("RDI: new "+context.getPath() + " " + path + " "
			       + subRequest.servletPath().toString() + " " +
			       subRequest.pathInfo().toString() + " " +
			       queryString);
	}

	if( queryString != null ) {
	    // the original parameters will be preserved, and a new
	    // child Parameters will be used for the included request.
	    realRequest.parameters().push();
	    Parameters child=realRequest.parameters().getCurrentSet();

	    child.processParameters( queryString );
	    
	}

	Request old_child = realRequest.getChild();
	subRequest.setParent( old_child );
	realRequest.setChild( subRequest );
	
 	// now it's really strange: we call the wrapper on the subrequest
	// for the realRequest ( since the real request will still have the
	// original handler/wrapper )

	Handler wr=subRequest.getHandler();
	if( wr!=null ) {
	    try {
		wr.service(realRequest, realResponse);
	    } catch( Exception ex ) {
		realResponse.setErrorException(ex);
	    }
	}

	
	// After request, we want to restore the include attributes - for
	// chained includes.

	realRequest.setChild( old_child );

	if( queryString != null ) {
	    // restore the parameters
	    realRequest.parameters().pop();
	}
	//realRequest.setParameters( old_parameters);

	replaceAttribute( realRequest, A_REQUEST_URI, old_request_uri);
	replaceAttribute( realRequest, A_CONTEXT_PATH,old_context_path); 
	replaceAttribute( realRequest, A_SERVLET_PATH, old_servlet_path);
	replaceAttribute( realRequest, A_PATH_INFO, old_path_info);
	replaceAttribute( realRequest, A_QUERY_STRING, old_query_string);
	
	// revert to the response behavior
	if( ! old_included ) {
	    realResponse.setIncluded( false );
	}

	// Rethrow original error if present
	if ( realResponse.isExceptionPresent() ) {
	    // if error URI not set, set our URI
	    if ( null == realResponse.getErrorURI() )
		realResponse.setErrorURI( context.getPath() + path );
	    Exception ex = realResponse.getErrorException();
	    wrapException( ex, sm.getString("dispatcher.includeException"));
	}
    }

    // -------------------- Special case of "named" dispatcher -------------

    /** Named dispatcher include
     *  Separate from normal include - which is still too messy
     */
    private void includeNamed(ServletRequest request, ServletResponse response)
	throws ServletException, IOException
    {
	// We got here if name!=null, so assert it
	Handler wr = context.getServletByName( name );
	// Use the original request - as in specification !
	Request realRequest=((HttpServletRequestFacade)request).getRealRequest();
	Response realResponse = realRequest.getResponse();

	// Set the "included" flag so that things like header setting in the
	// included servlet will be correctly ignored
	boolean old_included=realResponse.isIncluded();
	if( ! old_included ) realResponse.setIncluded( true );

	if( wr!=null) {
	    try {
		wr.service(realRequest, realResponse);
	    } catch( Exception ex ) {
		realResponse.setErrorException( ex );
	    }
	}

        // Clean up the request and response as needed
	if( ! old_included ) {
	    realResponse.setIncluded( false );
	}

	// Rethrow original error if present
	if ( realResponse.isExceptionPresent() ) {
	    // if error URI not set, set our URI
	    if ( null == realResponse.getErrorURI() )
		realResponse.setErrorURI( "named servlet: " + name );
	    wrapException( realResponse.getErrorException(),
			   sm.getString("dispatcher.includeException"));
	}
    }

    /** Named forward
     */
    private void forwardNamed(ServletRequest request, ServletResponse response)
	throws ServletException, IOException
    {
	// We got here if name!=null, so assert it
	Handler wr = context.getServletByName( name );

	// Use the original request - as in specification !
	Request realRequest=((HttpServletRequestFacade)request).
	    getRealRequest();
	Response realResponse = realRequest.getResponse();

	// Don't set included #3726
	// 	boolean old_included=realResponse.isIncluded();
	// 	if( ! old_included ) realResponse.setIncluded( true );

	if( wr!=null) {
	    try {
		wr.service(realRequest, realResponse);
	    } catch( Exception ex ) {
		wrapException( ex, null );
	    }
	}

	// Clean up the request and response as needed
	// No action required

	// Rethrow original error if present
	if ( realResponse.isExceptionPresent() ) {
	    // if error URI not set, set our URI
	    if ( null == realResponse.getErrorURI() )
		realResponse.setErrorURI( "named servlet: " + name );
	    wrapException( realResponse.getErrorException(),
			   sm.getString("dispatcher.forwardException"));
	}
    }    

    // -------------------- Special methods --------------------

    /** Restore attribute - if value is null, remove the attribute.
     *  ( or it is - null means no value in getAttribute, so setting to
     *    null should mean setting to no value. ?)
     */
    private Object replaceAttribute( Request realRequest, String name,
				     Object value)
    {
	Object oldAttribute=realRequest.getAttribute(name);
	if( value == null )
	    realRequest.removeAttribute( name );
	else
	    realRequest.setAttribute( name, value );
	return oldAttribute;
    }

    // Rethrow original error if present 
    private void wrapException(Exception ex, String msg)
	throws IOException, ServletException, RuntimeException
    {
	if ( ex instanceof IOException )
	    throw (IOException) ex;
	if ( ex instanceof RuntimeException )
	    throw (RuntimeException) ex;
	else if ( ex instanceof ServletException )
	    throw (ServletException) ex;
	else
	    if( msg==null )
		throw new ServletException(ex );
	    else
		throw new ServletException(msg, ex );
    }

    // -------------------- Used for doPriviledged in JDK1.2 ----------
    static class RDIAction extends Action {
	ServletRequest req;
	ServletResponse res;
	RequestDispatcherImpl rdi;
	boolean include;
	RDIAction(RequestDispatcherImpl rdi, boolean incl) {
	    this.rdi=rdi;
	    include=incl;
	}
	public void prepare( ServletRequest req, ServletResponse res ) {
	    this.req=req;
	    this.res=res;
	}
	public Object run() throws Exception {
	    if( include )
		rdi.doInclude( req, res );
	    else
		rdi.doForward( req, res );
	    return null;
	}
    }
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.