1   //========================================================================
2   //Copyright 2006 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.handler;
16  
17  import java.io.IOException;
18  import java.util.HashMap;
19  import java.util.Map;
20  
21  import javax.servlet.ServletException;
22  import javax.servlet.http.HttpServletRequest;
23  import javax.servlet.http.HttpServletResponse;
24  
25  import org.mortbay.jetty.Handler;
26  import org.mortbay.jetty.HandlerContainer;
27  import org.mortbay.jetty.HttpConnection;
28  import org.mortbay.jetty.Request;
29  import org.mortbay.jetty.servlet.PathMap;
30  import org.mortbay.log.Log;
31  import org.mortbay.util.LazyList;
32  
33  /* ------------------------------------------------------------ */
34  /** ContextHandlerCollection.
35   * 
36   * This {@link org.mortbay.jetty.handler.HandlerCollection} is creates a 
37   * {@link org.mortbay.jetty.servlet.PathMap} to it's contained handlers based
38   * on the context path and virtual hosts of any contained {@link org.mortbay.jetty.handler.ContextHandler}s.
39   * The contexts do not need to be directly contained, only children of the contained handlers.
40   * Multiple contexts may have the same context path and they are called in order until one
41   * handles the request.  
42   * 
43   * @org.apache.xbean.XBean element="contexts"
44   */
45  public class ContextHandlerCollection extends HandlerCollection
46  { 
47      private PathMap _contextMap;
48      private Class _contextClass = ContextHandler.class;
49      
50      /* ------------------------------------------------------------ */
51      /**
52       * Remap the context paths.
53       */
54      public void mapContexts()
55      {
56          PathMap contextMap = new PathMap();
57          Handler[] branches = getHandlers();
58          
59          
60          for (int b=0;branches!=null && b<branches.length;b++)
61          {
62              Handler[] handlers=null;
63              
64              if (branches[b] instanceof ContextHandler)
65              {
66                  handlers = new Handler[]{ branches[b] };
67              }
68              else if (branches[b] instanceof HandlerContainer)
69              {
70                  handlers = ((HandlerContainer)branches[b]).getChildHandlersByClass(ContextHandler.class);
71              }
72              else 
73                  continue;
74              
75              for (int i=0;i<handlers.length;i++)
76              {
77                  ContextHandler handler=(ContextHandler)handlers[i];
78  
79                  String contextPath=handler.getContextPath();
80  
81                  if (contextPath==null || contextPath.indexOf(',')>=0 || contextPath.startsWith("*"))
82                      throw new IllegalArgumentException ("Illegal context spec:"+contextPath);
83  
84                  if(!contextPath.startsWith("/"))
85                      contextPath='/'+contextPath;
86  
87                  if (contextPath.length()>1)
88                  {
89                      if (contextPath.endsWith("/"))
90                          contextPath+="*";
91                      else if (!contextPath.endsWith("/*"))
92                          contextPath+="/*";
93                  }
94  
95                  Object contexts=contextMap.get(contextPath);
96                  String[] vhosts=handler.getVirtualHosts();
97  
98                  
99                  if (vhosts!=null && vhosts.length>0)
100                 {
101                     Map hosts;
102 
103                     if (contexts instanceof Map)
104                         hosts=(Map)contexts;
105                     else
106                     {
107                         hosts=new HashMap(); 
108                         hosts.put("*",contexts);
109                         contextMap.put(contextPath, hosts);
110                     }
111 
112                     for (int j=0;j<vhosts.length;j++)
113                     {
114                         String vhost=vhosts[j];
115                         contexts=hosts.get(vhost);
116                         contexts=LazyList.add(contexts,branches[b]);
117                         hosts.put(vhost,contexts);
118                     }
119                 }
120                 else if (contexts instanceof Map)
121                 {
122                     Map hosts=(Map)contexts;
123                     contexts=hosts.get("*");
124                     contexts= LazyList.add(contexts, branches[b]);
125                     hosts.put("*",contexts);
126                 }
127                 else
128                 {
129                     contexts= LazyList.add(contexts, branches[b]);
130                     contextMap.put(contextPath, contexts);
131                 }
132             }
133         }
134         _contextMap=contextMap;
135 
136     }
137     
138 
139     
140     /* ------------------------------------------------------------ */
141     /* 
142      * @see org.mortbay.jetty.handler.HandlerCollection#setHandlers(org.mortbay.jetty.Handler[])
143      */
144     public void setHandlers(Handler[] handlers)
145     {
146         _contextMap=null;
147         super.setHandlers(handlers);
148         if (isStarted())
149             mapContexts();
150     }
151 
152     /* ------------------------------------------------------------ */
153     protected void doStart() throws Exception
154     {
155         mapContexts();
156         super.doStart();
157     }
158     
159 
160     /* ------------------------------------------------------------ */
161     /* 
162      * @see org.mortbay.jetty.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
163      */
164     public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException
165     {
166         Handler[] handlers = getHandlers();
167         if (handlers==null || handlers.length==0)
168 	    return;
169 
170 	Request base_request = HttpConnection.getCurrentConnection().getRequest();
171 	
172 	// data structure which maps a request to a context; first-best match wins
173 	// { context path => 
174 	//     { virtual host => context } 
175 	// }
176 	PathMap map = _contextMap;
177 	if (map!=null && target!=null && target.startsWith("/"))
178 	{
179 	    // first, get all contexts matched by context path
180 	    Object contexts = map.getLazyMatches(target);
181 
182             for (int i=0; i<LazyList.size(contexts); i++)
183             {
184                 // then, match against the virtualhost of each context
185                 Map.Entry entry = (Map.Entry)LazyList.get(contexts, i);
186                 Object list = entry.getValue();
187 
188                 if (list instanceof Map)
189                 {
190                     Map hosts = (Map)list;
191                     String host = normalizeHostname(request.getServerName());
192            
193                     // explicitly-defined virtual hosts, most specific
194                     list=hosts.get(host);
195                     for (int j=0; j<LazyList.size(list); j++)
196                     {
197                         Handler handler = (Handler)LazyList.get(list,j);
198                         handler.handle(target,request, response, dispatch);
199                         if (base_request.isHandled())
200                             return;
201                     }
202                     
203                     // wildcard for one level of names 
204                     list=hosts.get("*."+host.substring(host.indexOf(".")+1));
205                     for (int j=0; j<LazyList.size(list); j++)
206                     {
207                         Handler handler = (Handler)LazyList.get(list,j);
208                         handler.handle(target,request, response, dispatch);
209                         if (base_request.isHandled())
210                             return;
211                     }
212                     
213                     // no virtualhosts defined for the context, least specific
214                     // will handle any request that does not match to a specific virtual host above
215                     list=hosts.get("*");
216                     for (int j=0; j<LazyList.size(list); j++)
217                     {
218                         Handler handler = (Handler)LazyList.get(list,j);
219                         handler.handle(target,request, response, dispatch);
220                         if (base_request.isHandled())
221                             return;
222                     }
223                 }
224                 else
225                 {
226                     for (int j=0; j<LazyList.size(list); j++)
227                     {
228                         Handler handler = (Handler)LazyList.get(list,j);
229                         handler.handle(target,request, response, dispatch);
230                         if (base_request.isHandled())
231                             return;
232                     }
233                 }
234 	    }
235 	}
236 	else
237 	{
238             // This may not work in all circumstances... but then I think it should never be called
239 	    for (int i=0;i<handlers.length;i++)
240 	    {
241 		handlers[i].handle(target,request, response, dispatch);
242 		if ( base_request.isHandled())
243 		    return;
244 	    }
245 	}
246     }
247     
248     
249     /* ------------------------------------------------------------ */
250     /** Add a context handler.
251      * @param contextPath  The context path to add
252      * @return
253      * @throws IllegalAccessException 
254      * @throws InstantiationException 
255      */
256     public ContextHandler addContext(String contextPath,String resourceBase) 
257     {
258         try
259         {
260             ContextHandler context = (ContextHandler)_contextClass.newInstance();
261             context.setContextPath(contextPath);
262             context.setResourceBase(resourceBase);
263             addHandler(context);
264             return context;
265         }
266         catch (Exception e)
267         {
268             Log.warn(e);
269             throw new Error(e);
270         }
271     }
272 
273 
274 
275     /* ------------------------------------------------------------ */
276     /**
277      * @return The class to use to add new Contexts
278      */
279     public Class getContextClass()
280     {
281         return _contextClass;
282     }
283 
284 
285     /* ------------------------------------------------------------ */
286     /**
287      * @param contextClass The class to use to add new Contexts
288      */
289     public void setContextClass(Class contextClass)
290     {
291         if (contextClass ==null || !(ContextHandler.class.isAssignableFrom(contextClass)))
292             throw new IllegalArgumentException();
293         _contextClass = contextClass;
294     }
295     
296     /* ------------------------------------------------------------ */
297     private String normalizeHostname( String host )
298     {
299         if ( host == null )
300             return null;
301         
302         if ( host.endsWith( "." ) )
303             return host.substring( 0, host.length() -1);
304       
305         return host;
306     }
307     
308 }