2 // ========================================================================
3 // Copyright (c) 1995-2016 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.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.server.handler;
21 import java.io.IOException;
22 import java.lang.reflect.Array;
23 import java.util.Arrays;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.List;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.ConcurrentMap;
32 import javax.servlet.ServletException;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
36 import org.eclipse.jetty.server.Handler;
37 import org.eclipse.jetty.server.HandlerContainer;
38 import org.eclipse.jetty.server.HttpChannelState;
39 import org.eclipse.jetty.server.Request;
40 import org.eclipse.jetty.util.ArrayTernaryTrie;
41 import org.eclipse.jetty.util.ArrayUtil;
42 import org.eclipse.jetty.util.Trie;
43 import org.eclipse.jetty.util.annotation.ManagedObject;
44 import org.eclipse.jetty.util.annotation.ManagedOperation;
45 import org.eclipse.jetty.util.log.Log;
46 import org.eclipse.jetty.util.log.Logger;
48 /* ------------------------------------------------------------ */
49 /** ContextHandlerCollection.
51 * This {@link org.eclipse.jetty.server.handler.HandlerCollection} is creates a
52 * {@link org.eclipse.jetty.http.PathMap} to it's contained handlers based
53 * on the context path and virtual hosts of any contained {@link org.eclipse.jetty.server.handler.ContextHandler}s.
54 * The contexts do not need to be directly contained, only children of the contained handlers.
55 * Multiple contexts may have the same context path and they are called in order until one
56 * handles the request.
59 @ManagedObject("Context Handler Collection")
60 public class ContextHandlerCollection extends HandlerCollection
62 private static final Logger LOG = Log.getLogger(ContextHandlerCollection.class);
64 private final ConcurrentMap<ContextHandler,Handler> _contextBranches = new ConcurrentHashMap<>();
65 private volatile Trie<Map.Entry<String,Branch[]>> _pathBranches;
66 private Class<? extends ContextHandler> _contextClass = ContextHandler.class;
68 /* ------------------------------------------------------------ */
69 public ContextHandlerCollection()
75 /* ------------------------------------------------------------ */
77 * Remap the context paths.
79 @ManagedOperation("update the mapping of context path to context")
80 public void mapContexts()
82 _contextBranches.clear();
84 if (getHandlers()==null)
86 _pathBranches=new ArrayTernaryTrie<>(false,16);
90 // Create map of contextPath to handler Branch
91 Map<String,Branch[]> map = new HashMap<>();
92 for (Handler handler:getHandlers())
94 Branch branch=new Branch(handler);
95 for (String contextPath : branch.getContextPaths())
97 Branch[] branches=map.get(contextPath);
98 map.put(contextPath, ArrayUtil.addToArray(branches, branch, Branch.class));
101 for (ContextHandler context : branch.getContextHandlers())
102 _contextBranches.putIfAbsent(context, branch.getHandler());
105 // Sort the branches so those with virtual hosts are considered before those without
106 for (Map.Entry<String,Branch[]> entry: map.entrySet())
108 Branch[] branches=entry.getValue();
109 Branch[] sorted=new Branch[branches.length];
111 for (Branch branch:branches)
112 if (branch.hasVirtualHost())
114 for (Branch branch:branches)
115 if (!branch.hasVirtualHost())
117 entry.setValue(sorted);
120 // Loop until we have a big enough trie to hold all the context paths
122 Trie<Map.Entry<String,Branch[]>> trie;
125 trie=new ArrayTernaryTrie<>(false,capacity);
126 for (Map.Entry<String,Branch[]> entry: map.entrySet())
128 if (!trie.put(entry.getKey().substring(1),entry))
138 if (LOG.isDebugEnabled())
140 for (String ctx : trie.keySet())
141 LOG.debug("{}->{}",ctx,Arrays.asList(trie.get(ctx).getValue()));
146 /* ------------------------------------------------------------ */
148 * @see org.eclipse.jetty.server.server.handler.HandlerCollection#setHandlers(org.eclipse.jetty.server.server.Handler[])
151 public void setHandlers(Handler[] handlers)
153 super.setHandlers(handlers);
158 /* ------------------------------------------------------------ */
160 protected void doStart() throws Exception
167 /* ------------------------------------------------------------ */
169 * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
172 public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
174 Handler[] handlers = getHandlers();
175 if (handlers==null || handlers.length==0)
178 HttpChannelState async = baseRequest.getHttpChannelState();
181 ContextHandler context=async.getContextHandler();
184 Handler branch = _contextBranches.get(context);
187 context.handle(target,baseRequest,request, response);
189 branch.handle(target, baseRequest, request, response);
194 // data structure which maps a request to a context; first-best match wins
195 // { context path => [ context ] }
197 if (target.startsWith("/"))
199 int limit = target.length()-1;
204 Map.Entry<String,Branch[]> branches = _pathBranches.getBest(target,1,limit);
210 int l=branches.getKey().length();
211 if (l==1 || target.length()==l || target.charAt(l)=='/')
213 for (Branch branch : branches.getValue())
215 branch.getHandler().handle(target,baseRequest, request, response);
216 if (baseRequest.isHandled())
226 // This may not work in all circumstances... but then I think it should never be called
227 for (int i=0;i<handlers.length;i++)
229 handlers[i].handle(target,baseRequest, request, response);
230 if ( baseRequest.isHandled())
236 /* ------------------------------------------------------------ */
237 /** Add a context handler.
238 * @param contextPath The context path to add
239 * @return the ContextHandler just added
241 public ContextHandler addContext(String contextPath,String resourceBase)
245 ContextHandler context = _contextClass.newInstance();
246 context.setContextPath(contextPath);
247 context.setResourceBase(resourceBase);
260 /* ------------------------------------------------------------ */
262 * @return The class to use to add new Contexts
264 public Class<?> getContextClass()
266 return _contextClass;
270 /* ------------------------------------------------------------ */
272 * @param contextClass The class to use to add new Contexts
274 public void setContextClass(Class<? extends ContextHandler> contextClass)
276 if (contextClass ==null || !(ContextHandler.class.isAssignableFrom(contextClass)))
277 throw new IllegalArgumentException();
278 _contextClass = contextClass;
281 /* ------------------------------------------------------------ */
282 /* ------------------------------------------------------------ */
283 /* ------------------------------------------------------------ */
284 private final static class Branch
286 private final Handler _handler;
287 private final ContextHandler[] _contexts;
289 Branch(Handler handler)
293 if (handler instanceof ContextHandler)
295 _contexts = new ContextHandler[]{(ContextHandler)handler};
297 else if (handler instanceof HandlerContainer)
299 Handler[] contexts=((HandlerContainer)handler).getChildHandlersByClass(ContextHandler.class);
300 _contexts = new ContextHandler[contexts.length];
301 System.arraycopy(contexts, 0, _contexts, 0, contexts.length);
304 _contexts = new ContextHandler[0];
307 Set<String> getContextPaths()
309 Set<String> set = new HashSet<String>();
310 for (ContextHandler context:_contexts)
311 set.add(context.getContextPath());
315 boolean hasVirtualHost()
317 for (ContextHandler context:_contexts)
318 if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
323 ContextHandler[] getContextHandlers()
334 public String toString()
336 return String.format("{%s,%s}",_handler,Arrays.asList(_contexts));