View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server.handler;
20  
21  import java.io.IOException;
22  import java.util.Arrays;
23  
24  import javax.servlet.ServletException;
25  import javax.servlet.http.HttpServletRequest;
26  import javax.servlet.http.HttpServletResponse;
27  
28  import org.eclipse.jetty.server.Handler;
29  import org.eclipse.jetty.server.HandlerContainer;
30  import org.eclipse.jetty.server.HttpChannelState;
31  import org.eclipse.jetty.server.Request;
32  import org.eclipse.jetty.util.ArrayTernaryTrie;
33  import org.eclipse.jetty.util.ArrayUtil;
34  import org.eclipse.jetty.util.Trie;
35  import org.eclipse.jetty.util.annotation.ManagedObject;
36  import org.eclipse.jetty.util.annotation.ManagedOperation;
37  import org.eclipse.jetty.util.log.Log;
38  import org.eclipse.jetty.util.log.Logger;
39  
40  /* ------------------------------------------------------------ */
41  /** ContextHandlerCollection.
42   *
43   * This {@link org.eclipse.jetty.server.handler.HandlerCollection} is creates a
44   * {@link org.eclipse.jetty.http.PathMap} to it's contained handlers based
45   * on the context path and virtual hosts of any contained {@link org.eclipse.jetty.server.handler.ContextHandler}s.
46   * The contexts do not need to be directly contained, only children of the contained handlers.
47   * Multiple contexts may have the same context path and they are called in order until one
48   * handles the request.
49   *
50   */
51  @ManagedObject("Context Handler Collection")
52  public class ContextHandlerCollection extends HandlerCollection
53  {
54      private static final Logger LOG = Log.getLogger(ContextHandlerCollection.class);
55  
56      private volatile Trie<ContextHandler[]> _contexts;
57      private Class<? extends ContextHandler> _contextClass = ContextHandler.class;
58  
59      /* ------------------------------------------------------------ */
60      public ContextHandlerCollection()
61      {
62          super(true);
63      }
64  
65  
66      /* ------------------------------------------------------------ */
67      /**
68       * Remap the context paths.
69       */
70      @ManagedOperation("update the mapping of context path to context")
71      public void mapContexts()
72      {
73          int capacity=512;
74          
75          // Loop until we have a big enough trie to hold all the context paths
76          Trie<ContextHandler[]> trie;
77          loop: while(true)
78          {
79              trie=new ArrayTernaryTrie<>(false,capacity);
80  
81              Handler[] branches = getHandlers();
82  
83              // loop over each group of contexts
84              for (int b=0;branches!=null && b<branches.length;b++)
85              {
86                  Handler[] handlers=null;
87  
88                  if (branches[b] instanceof ContextHandler)
89                  {
90                      handlers = new Handler[]{ branches[b] };
91                  }
92                  else if (branches[b] instanceof HandlerContainer)
93                  {
94                      handlers = ((HandlerContainer)branches[b]).getChildHandlersByClass(ContextHandler.class);
95                  }
96                  else
97                      continue;
98  
99                  // for each context handler in a group
100                 for (int i=0;handlers!=null && i<handlers.length;i++)
101                 {
102                     ContextHandler handler=(ContextHandler)handlers[i];
103                     String contextPath=handler.getContextPath().substring(1);
104                     ContextHandler[] contexts=trie.get(contextPath);
105                     
106                     if (!trie.put(contextPath,ArrayUtil.addToArray(contexts,handler,ContextHandler.class)))
107                     {
108                         capacity+=512;
109                         continue loop;
110                     }
111                 }
112             }
113             
114             break;
115         }
116         
117         // Sort the contexts so those with virtual hosts are considered before those without
118         for (String ctx : trie.keySet())
119         {
120             ContextHandler[] contexts=trie.get(ctx);
121             ContextHandler[] sorted=new ContextHandler[contexts.length];
122             int i=0;
123             for (ContextHandler handler:contexts)
124                 if (handler.getVirtualHosts()!=null && handler.getVirtualHosts().length>0)
125                     sorted[i++]=handler;
126             for (ContextHandler handler:contexts)
127                 if (handler.getVirtualHosts()==null || handler.getVirtualHosts().length==0)
128                     sorted[i++]=handler;
129             trie.put(ctx,sorted);
130         }
131 
132         //for (String ctx : trie.keySet())
133         //    System.err.printf("'%s'->%s%n",ctx,Arrays.asList(trie.get(ctx)));
134         _contexts=trie;
135     }
136 
137 
138     /* ------------------------------------------------------------ */
139     /*
140      * @see org.eclipse.jetty.server.server.handler.HandlerCollection#setHandlers(org.eclipse.jetty.server.server.Handler[])
141      */
142     @Override
143     public void setHandlers(Handler[] handlers)
144     {
145         super.setHandlers(handlers);
146         if (isStarted())
147             mapContexts();
148     }
149 
150     /* ------------------------------------------------------------ */
151     @Override
152     protected void doStart() throws Exception
153     {
154         mapContexts();
155         super.doStart();
156     }
157 
158 
159     /* ------------------------------------------------------------ */
160     /*
161      * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
162      */
163     @Override
164     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
165     {
166         Handler[] handlers = getHandlers();
167         if (handlers==null || handlers.length==0)
168 	    return;
169 
170 	HttpChannelState async = baseRequest.getHttpChannelState();
171 	if (async.isAsync())
172 	{
173 	    ContextHandler context=async.getContextHandler();
174 	    if (context!=null)
175 	    {
176 	        context.handle(target,baseRequest,request, response);
177 	        return;
178 	    }
179 	}
180 
181 	// data structure which maps a request to a context; first-best match wins
182 	// { context path => [ context ] }
183 	// }
184 	if (target.startsWith("/"))
185 	{
186 	    int limit = target.length()-1;
187 
188 	    while (limit>=0)
189 	    {
190 	        // Get best match
191 	        ContextHandler[] contexts = _contexts.getBest(target,1,limit);
192 	        if (contexts==null)
193 	            break;
194 
195 	        int l=contexts[0].getContextPath().length();
196 	        if (l==1 || target.length()==l || target.charAt(l)=='/')
197 	        {
198 	            for (ContextHandler handler : contexts)
199 	            {
200 	                handler.handle(target,baseRequest, request, response);
201 	                if (baseRequest.isHandled())
202 	                    return;
203 	            }
204 	        }
205 	        
206 	        limit=l-2;
207 	    }
208 	}
209 	else
210 	{
211             // This may not work in all circumstances... but then I think it should never be called
212 	    for (int i=0;i<handlers.length;i++)
213 	    {
214 		handlers[i].handle(target,baseRequest, request, response);
215 		if ( baseRequest.isHandled())
216 		    return;
217 	    }
218 	}
219     }
220 
221 
222     /* ------------------------------------------------------------ */
223     /** Add a context handler.
224      * @param contextPath  The context path to add
225      * @return the ContextHandler just added
226      */
227     public ContextHandler addContext(String contextPath,String resourceBase)
228     {
229         try
230         {
231             ContextHandler context = _contextClass.newInstance();
232             context.setContextPath(contextPath);
233             context.setResourceBase(resourceBase);
234             addHandler(context);
235             return context;
236         }
237         catch (Exception e)
238         {
239             LOG.debug(e);
240             throw new Error(e);
241         }
242     }
243 
244 
245 
246     /* ------------------------------------------------------------ */
247     /**
248      * @return The class to use to add new Contexts
249      */
250     public Class<?> getContextClass()
251     {
252         return _contextClass;
253     }
254 
255 
256     /* ------------------------------------------------------------ */
257     /**
258      * @param contextClass The class to use to add new Contexts
259      */
260     public void setContextClass(Class<? extends ContextHandler> contextClass)
261     {
262         if (contextClass ==null || !(ContextHandler.class.isAssignableFrom(contextClass)))
263             throw new IllegalArgumentException();
264         _contextClass = contextClass;
265     }
266 
267 
268 }