1   // ========================================================================
2   // Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.jetty;
16  
17  import java.io.IOException;
18  import java.util.Collections;
19  import java.util.Enumeration;
20  import java.util.HashSet;
21  import java.util.Iterator;
22  import java.util.Map;
23  
24  import javax.servlet.DispatcherType;
25  import javax.servlet.RequestDispatcher;
26  import javax.servlet.ServletException;
27  import javax.servlet.ServletRequest;
28  import javax.servlet.ServletResponse;
29  import javax.servlet.http.HttpServletRequest;
30  import javax.servlet.http.HttpServletResponse;
31  
32  import org.mortbay.jetty.handler.ContextHandler;
33  import org.mortbay.util.Attributes;
34  import org.mortbay.util.LazyList;
35  import org.mortbay.util.MultiMap;
36  import org.mortbay.util.UrlEncoded;
37  
38  /* ------------------------------------------------------------ */
39  /** Servlet RequestDispatcher.
40   * 
41   * @author Greg Wilkins (gregw)
42   */
43  public class Dispatcher implements RequestDispatcher
44  {
45      /** Dispatch include attribute names */
46      public final static String __INCLUDE_JETTY="org.mortbay.jetty.included";
47      public final static String __INCLUDE_PREFIX="javax.servlet.include.";
48      public final static String __INCLUDE_REQUEST_URI= "javax.servlet.include.request_uri";
49      public final static String __INCLUDE_CONTEXT_PATH= "javax.servlet.include.context_path";
50      public final static String __INCLUDE_SERVLET_PATH= "javax.servlet.include.servlet_path";
51      public final static String __INCLUDE_PATH_INFO= "javax.servlet.include.path_info";
52      public final static String __INCLUDE_QUERY_STRING= "javax.servlet.include.query_string";
53  
54      /** Dispatch include attribute names */
55      public final static String __FORWARD_JETTY="org.mortbay.jetty.forwarded";
56      public final static String __FORWARD_PREFIX="javax.servlet.forward.";
57      public final static String __FORWARD_REQUEST_URI= "javax.servlet.forward.request_uri";
58      public final static String __FORWARD_CONTEXT_PATH= "javax.servlet.forward.context_path";
59      public final static String __FORWARD_SERVLET_PATH= "javax.servlet.forward.servlet_path";
60      public final static String __FORWARD_PATH_INFO= "javax.servlet.forward.path_info";
61      public final static String __FORWARD_QUERY_STRING= "javax.servlet.forward.query_string";
62  
63      /** JSP attributes */
64      public final static String __JSP_FILE="org.apache.catalina.jsp_file";
65  
66      /* ------------------------------------------------------------ */
67      /** Dispatch type from name
68       */
69      public static int type(String type)
70      {
71          if ("request".equalsIgnoreCase(type))
72              return Handler.REQUEST;
73          if ("forward".equalsIgnoreCase(type))
74              return Handler.FORWARD;
75          if ("include".equalsIgnoreCase(type))
76              return Handler.INCLUDE;
77          if ("error".equalsIgnoreCase(type))
78              return Handler.ERROR;
79          throw new IllegalArgumentException(type);
80      }
81  
82      
83      public static int type (DispatcherType type)
84      {
85          if (type.equals(DispatcherType.REQUEST))
86              return Handler.REQUEST;
87          if (type.equals(DispatcherType.FORWARD))
88              return Handler.FORWARD;
89          if (type.equals(DispatcherType.INCLUDE))
90              return Handler.INCLUDE;
91          if (type.equals(DispatcherType.ERROR))
92              return Handler.ERROR;
93          throw new IllegalArgumentException(type.toString());
94      }
95  
96      /* ------------------------------------------------------------ */
97      private ContextHandler _contextHandler;
98      private String _uri;
99      private String _path;
100     private String _dQuery;
101     private String _named;
102     
103     /* ------------------------------------------------------------ */
104     /**
105      * @param contextHandler
106      * @param uriInContext
107      * @param pathInContext
108      * @param query
109      */
110     public Dispatcher(ContextHandler contextHandler, String uri, String pathInContext, String query)
111     {
112         _contextHandler=contextHandler;
113         _uri=uri;
114         _path=pathInContext;
115         _dQuery=query;
116     }
117 
118 
119     /* ------------------------------------------------------------ */
120     /** Constructor. 
121      * @param servletHandler
122      * @param name
123      */
124     public Dispatcher(ContextHandler contextHandler,String name)
125         throws IllegalStateException
126     {
127         _contextHandler=contextHandler;
128         _named=name;
129     }
130     
131     /* ------------------------------------------------------------ */
132     /* 
133      * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
134      */
135     public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException
136     {
137         forward(request, response, Handler.FORWARD);
138     }
139     
140     /* ------------------------------------------------------------ */
141     /* 
142      * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
143      */
144     public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException
145     {
146         forward(request, response, Handler.ERROR);
147     }
148     
149     /* ------------------------------------------------------------ */
150     /* 
151      * @see javax.servlet.RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
152      */
153     public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException
154     {
155         Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest();
156         request.removeAttribute(__JSP_FILE); // TODO remove when glassfish 1044 is fixed
157         
158         // TODO - allow stream or writer????
159         
160         Attributes old_attr=base_request.getAttributes();
161         MultiMap old_params=base_request.getParameters();
162         try
163         {
164             base_request.getConnection().include();
165             if (_named!=null)
166                 _contextHandler.handle(_named, (HttpServletRequest)request, (HttpServletResponse)response, Handler.INCLUDE);
167             else 
168             {
169                 String query=_dQuery;
170                 
171                 if (query!=null)
172                 {
173                     MultiMap parameters=new MultiMap();
174                     UrlEncoded.decodeTo(query,parameters,request.getCharacterEncoding());
175                     
176                     if (old_params!=null && old_params.size()>0)
177                     {
178                         // Merge parameters.
179                         Iterator iter = old_params.entrySet().iterator();
180                         while (iter.hasNext())
181                         {
182                             Map.Entry entry = (Map.Entry)iter.next();
183                             String name=(String)entry.getKey();
184                             Object values=entry.getValue();
185                             for (int i=0;i<LazyList.size(values);i++)
186                                 parameters.add(name, LazyList.get(values, i));
187                         }
188                         
189                     }
190                     base_request.setParameters(parameters);
191                 }
192                 
193                 IncludeAttributes attr = new IncludeAttributes(old_attr); 
194                 
195                 attr._requestURI=_uri;
196                 attr._contextPath=_contextHandler.getContextPath();
197                 attr._servletPath=null; // set by ServletHandler
198                 attr._pathInfo=_path;
199                 attr._query=query;
200                 
201                 base_request.setAttributes(attr);
202                 
203                 _contextHandler.handle(_named==null?_path:_named, (HttpServletRequest)request, (HttpServletResponse)response, Handler.INCLUDE);
204             }
205         }
206         finally
207         {
208             base_request.setAttributes(old_attr);
209             base_request.getConnection().included();
210             base_request.setParameters(old_params);
211         }
212     }
213 
214     
215     /* ------------------------------------------------------------ */
216     /* 
217      * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
218      */
219     protected void forward(ServletRequest request, ServletResponse response, int dispatch) throws ServletException, IOException
220     {
221         Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest();
222         response.resetBuffer(); 
223         request.removeAttribute(__JSP_FILE); // TODO remove when glassfish 1044 is fixed
224         
225         String old_uri=base_request.getRequestURI();
226         String old_context_path=base_request.getContextPath();
227         String old_servlet_path=base_request.getServletPath();
228         String old_path_info=base_request.getPathInfo();
229         String old_query=base_request.getQueryString();
230         Attributes old_attr=base_request.getAttributes();
231         MultiMap old_params=base_request.getParameters();
232         try
233         {
234             if (_named!=null)
235                 _contextHandler.handle(_named, (HttpServletRequest)request, (HttpServletResponse)response, dispatch);
236             else 
237             {
238                 String query=_dQuery;
239                 
240                 if (query!=null)
241                 {
242                     MultiMap parameters=new MultiMap();
243                     UrlEncoded.decodeTo(query,parameters,request.getCharacterEncoding());
244                  
245                     boolean rewrite_old_query = false;
246 
247                     if( old_params == null )
248                     {
249                         base_request.getParameterNames();    // force parameters to be evaluated
250                         old_params = base_request.getParameters();
251                     }
252                     
253                     if (old_params!=null && old_params.size()>0)
254                     {
255                         // Merge parameters; new parameters of the same name take precedence.
256                         Iterator iter = old_params.entrySet().iterator();
257                         while (iter.hasNext())
258                         {
259                             Map.Entry entry = (Map.Entry)iter.next();
260                             String name=(String)entry.getKey();
261                             
262                             if (parameters.containsKey(name))
263                             {
264                                 rewrite_old_query = true;
265                             }
266                             else
267                             {
268                                 Object values=entry.getValue();
269                                 for (int i=0;i<LazyList.size(values);i++)
270                                 {
271                                     parameters.add(name, LazyList.get(values, i));
272                                 }
273                             }
274                         }
275                     }
276                     
277                     if (old_query != null && old_query.length()>0)
278                     {
279                         if ( rewrite_old_query )
280                         {
281                             StringBuilder overridden_query_string = new StringBuilder();
282                             MultiMap overridden_old_query = new MultiMap();
283                             UrlEncoded.decodeTo(old_query,overridden_old_query,request.getCharacterEncoding());
284     
285                             MultiMap overridden_new_query = new MultiMap(); 
286                             UrlEncoded.decodeTo(query,overridden_new_query,request.getCharacterEncoding());
287 
288                             Iterator iter = overridden_old_query.entrySet().iterator();
289                             while (iter.hasNext())
290                             {
291                                 Map.Entry entry = (Map.Entry)iter.next();
292                                 String name=(String)entry.getKey();
293                                 if(!overridden_new_query.containsKey(name))
294                                 {
295                                     Object values=entry.getValue();
296                                     for (int i=0;i<LazyList.size(values);i++)
297                                     {
298                                         overridden_query_string.append("&"+name+"="+LazyList.get(values, i));
299                                     }
300                                 }
301                             }
302                             
303                             query = query + overridden_query_string;
304                         }
305                         else 
306                         {
307                             query=query+"&"+old_query;
308                         }
309                    }
310 
311                     base_request.setParameters(parameters);
312                     base_request.setQueryString(query);
313                 }
314                 
315                 ForwardAttributes attr = new ForwardAttributes(old_attr); 
316                 
317                 //If we have already been forwarded previously, then keep using the established 
318                 //original value. Otherwise, this is the first forward and we need to establish the values.
319                 //Note: the established value on the original request for pathInfo and
320                 //for queryString is allowed to be null, but cannot be null for the other values.
321                 if ((String)old_attr.getAttribute(__FORWARD_REQUEST_URI) != null)
322                 {
323                     attr._pathInfo=(String)old_attr.getAttribute(__FORWARD_PATH_INFO);
324                     attr._query=(String)old_attr.getAttribute(__FORWARD_QUERY_STRING);
325                     attr._requestURI=(String)old_attr.getAttribute(__FORWARD_REQUEST_URI);
326                     attr._contextPath=(String)old_attr.getAttribute(__FORWARD_CONTEXT_PATH);
327                     attr._servletPath=(String)old_attr.getAttribute(__FORWARD_SERVLET_PATH);
328                 }
329                 else
330                 {
331                     attr._pathInfo=old_path_info;
332                     attr._query=old_query;
333                     attr._requestURI=old_uri;
334                     attr._contextPath=old_context_path;
335                     attr._servletPath=old_servlet_path;
336                 }                
337    
338               
339                 
340                 base_request.setRequestURI(_uri);
341                 base_request.setContextPath(_contextHandler.getContextPath());
342                 base_request.setAttributes(attr);
343                 base_request.setQueryString(query);
344                 
345                 _contextHandler.handle(_path, (HttpServletRequest)request, (HttpServletResponse)response, dispatch);
346                 
347                 if (base_request.getConnection().getResponse().isWriting())
348                 {
349                     try {response.getWriter().close();}
350                     catch(IllegalStateException e) { response.getOutputStream().close(); }
351                 }
352                 else
353                 {
354                     try {response.getOutputStream().close();}
355                     catch(IllegalStateException e) { response.getWriter().close(); }
356                 }
357             }
358         }
359         finally
360         {
361             base_request.setRequestURI(old_uri);
362             base_request.setContextPath(old_context_path);
363             base_request.setServletPath(old_servlet_path);
364             base_request.setPathInfo(old_path_info);
365             base_request.setAttributes(old_attr);
366             base_request.setParameters(old_params);
367             base_request.setQueryString(old_query);
368         }
369     }
370 
371 
372     /* ------------------------------------------------------------ */
373     /* ------------------------------------------------------------ */
374     /* ------------------------------------------------------------ */
375     private class ForwardAttributes implements Attributes
376     {
377         Attributes _attr;
378         
379         String _requestURI;
380         String _contextPath;
381         String _servletPath;
382         String _pathInfo;
383         String _query;
384         
385         ForwardAttributes(Attributes attributes)
386         {
387             _attr=attributes;
388         }
389         
390         /* ------------------------------------------------------------ */
391         public Object getAttribute(String key)
392         {
393             if (Dispatcher.this._named==null)
394             {
395                 if (key.equals(__FORWARD_PATH_INFO))    return _pathInfo;
396                 if (key.equals(__FORWARD_REQUEST_URI))  return _requestURI;
397                 if (key.equals(__FORWARD_SERVLET_PATH)) return _servletPath;
398                 if (key.equals(__FORWARD_CONTEXT_PATH)) return _contextPath;
399                 if (key.equals(__FORWARD_QUERY_STRING)) return _query;
400             }
401             
402             if (key.startsWith(__INCLUDE_PREFIX) || key.equals(__INCLUDE_JETTY) )
403                 return null;
404 
405             if (key.equals(__FORWARD_JETTY)) 
406                 return Boolean.TRUE;
407             
408             return _attr.getAttribute(key);
409         }
410         
411         /* ------------------------------------------------------------ */
412         public Enumeration getAttributeNames()
413         {
414             HashSet set=new HashSet();
415             Enumeration e=_attr.getAttributeNames();
416             while(e.hasMoreElements())
417             {
418                 String name=(String)e.nextElement();
419                 if (!name.startsWith(__INCLUDE_PREFIX) &&
420                     !name.startsWith(__FORWARD_PREFIX))
421                     set.add(name);
422             }
423             
424             if (_named==null)
425             {
426                 if (_pathInfo!=null)
427                     set.add(__FORWARD_PATH_INFO);
428                 else
429                     set.remove(__FORWARD_PATH_INFO);
430                 set.add(__FORWARD_REQUEST_URI);
431                 set.add(__FORWARD_SERVLET_PATH);
432                 set.add(__FORWARD_CONTEXT_PATH);
433                 if (_query!=null)
434                     set.add(__FORWARD_QUERY_STRING);
435                 else
436                     set.remove(__FORWARD_QUERY_STRING);
437             }
438 
439             return Collections.enumeration(set);
440         }
441         
442         /* ------------------------------------------------------------ */
443         public void setAttribute(String key, Object value)
444         {
445             if (_named==null && key.startsWith("javax.servlet."))
446             {
447                 if (key.equals(__FORWARD_PATH_INFO))         _pathInfo=(String)value;
448                 else if (key.equals(__FORWARD_REQUEST_URI))  _requestURI=(String)value;
449                 else if (key.equals(__FORWARD_SERVLET_PATH)) _servletPath=(String)value;
450                 else if (key.equals(__FORWARD_CONTEXT_PATH)) _contextPath=(String)value;
451                 else if (key.equals(__FORWARD_QUERY_STRING)) _query=(String)value;
452                 
453                 else if (value==null)
454                     _attr.removeAttribute(key);
455                 else
456                     _attr.setAttribute(key,value); 
457             }
458             else if (value==null)
459                 _attr.removeAttribute(key);
460             else
461                 _attr.setAttribute(key,value);
462         }
463         
464         /* ------------------------------------------------------------ */
465         public String toString() 
466         {
467             return "FORWARD+"+_attr.toString();
468         }
469 
470         /* ------------------------------------------------------------ */
471         public void clearAttributes()
472         {
473             throw new IllegalStateException();
474         }
475 
476         /* ------------------------------------------------------------ */
477         public void removeAttribute(String name)
478         {
479             setAttribute(name,null);
480         }
481     }
482 
483     /* ------------------------------------------------------------ */
484     private class IncludeAttributes implements Attributes
485     {
486         Attributes _attr;
487         
488         String _requestURI;
489         String _contextPath;
490         String _servletPath;
491         String _pathInfo;
492         String _query;
493         
494         IncludeAttributes(Attributes attributes)
495         {
496             _attr=attributes;
497         }
498         
499         /* ------------------------------------------------------------ */
500         /* ------------------------------------------------------------ */
501         /* ------------------------------------------------------------ */
502         public Object getAttribute(String key)
503         {
504             if (Dispatcher.this._named==null)
505             {
506                 if (key.equals(__INCLUDE_PATH_INFO))    return _pathInfo;
507                 if (key.equals(__INCLUDE_SERVLET_PATH)) return _servletPath;
508                 if (key.equals(__INCLUDE_CONTEXT_PATH)) return _contextPath;
509                 if (key.equals(__INCLUDE_QUERY_STRING)) return _query;
510                 if (key.equals(__INCLUDE_REQUEST_URI))  return _requestURI;
511             }
512             else if (key.startsWith(__INCLUDE_PREFIX)) 
513                     return null;
514             
515             if (key.equals(__INCLUDE_JETTY)) 
516                 return Boolean.TRUE;
517             
518             return _attr.getAttribute(key);
519         }
520         
521         /* ------------------------------------------------------------ */
522         public Enumeration getAttributeNames()
523         {
524             HashSet set=new HashSet();
525             Enumeration e=_attr.getAttributeNames();
526             while(e.hasMoreElements())
527             {
528                 String name=(String)e.nextElement();
529                 if (!name.startsWith(__INCLUDE_PREFIX))
530                     set.add(name);
531             }
532             
533             if (_named==null)
534             {
535                 if (_pathInfo!=null)
536                     set.add(__INCLUDE_PATH_INFO);
537                 else
538                     set.remove(__INCLUDE_PATH_INFO);
539                 set.add(__INCLUDE_REQUEST_URI);
540                 set.add(__INCLUDE_SERVLET_PATH);
541                 set.add(__INCLUDE_CONTEXT_PATH);
542                 if (_query!=null)
543                     set.add(__INCLUDE_QUERY_STRING);
544                 else
545                     set.remove(__INCLUDE_QUERY_STRING);
546             }
547             
548             return Collections.enumeration(set);
549         }
550         
551         /* ------------------------------------------------------------ */
552         public void setAttribute(String key, Object value)
553         {
554             if (_named==null && key.startsWith("javax.servlet."))
555             {
556                 if (key.equals(__INCLUDE_PATH_INFO))         _pathInfo=(String)value;
557                 else if (key.equals(__INCLUDE_REQUEST_URI))  _requestURI=(String)value;
558                 else if (key.equals(__INCLUDE_SERVLET_PATH)) _servletPath=(String)value;
559                 else if (key.equals(__INCLUDE_CONTEXT_PATH)) _contextPath=(String)value;
560                 else if (key.equals(__INCLUDE_QUERY_STRING)) _query=(String)value;
561                 else if (value==null)
562                     _attr.removeAttribute(key);
563                 else
564                     _attr.setAttribute(key,value); 
565             }
566             else if (value==null)
567                 _attr.removeAttribute(key);
568             else
569                 _attr.setAttribute(key,value);
570         }
571         
572         /* ------------------------------------------------------------ */
573         public String toString() 
574         {
575             return "INCLUDE+"+_attr.toString();
576         }
577 
578         /* ------------------------------------------------------------ */
579         public void clearAttributes()
580         {
581             throw new IllegalStateException();
582         }
583 
584         /* ------------------------------------------------------------ */
585         public void removeAttribute(String name)
586         {
587             setAttribute(name,null);
588         }
589     }
590 };