Jake Slack | 03928ae | 2014-05-13 18:41:56 -0700 | [diff] [blame] | 1 | // |
| 2 | // ======================================================================== |
| 3 | // Copyright (c) 1995-2014 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.servlet; |
| 20 | |
| 21 | import java.io.IOException; |
| 22 | import java.util.ArrayList; |
| 23 | import java.util.Arrays; |
| 24 | import java.util.Collections; |
| 25 | import java.util.EnumSet; |
| 26 | import java.util.HashMap; |
| 27 | import java.util.HashSet; |
| 28 | import java.util.ListIterator; |
| 29 | import java.util.Set; |
| 30 | import java.util.List; |
| 31 | import java.util.Map; |
| 32 | import java.util.Queue; |
| 33 | import java.util.concurrent.ConcurrentHashMap; |
| 34 | import java.util.concurrent.ConcurrentLinkedQueue; |
| 35 | import java.util.concurrent.ConcurrentMap; |
| 36 | |
| 37 | import javax.servlet.AsyncContext; |
| 38 | import javax.servlet.DispatcherType; |
| 39 | import javax.servlet.Filter; |
| 40 | import javax.servlet.FilterChain; |
| 41 | import javax.servlet.RequestDispatcher; |
| 42 | import javax.servlet.Servlet; |
| 43 | import javax.servlet.ServletContext; |
| 44 | import javax.servlet.ServletException; |
| 45 | import javax.servlet.ServletRegistration; |
| 46 | import javax.servlet.ServletRequest; |
| 47 | import javax.servlet.ServletResponse; |
| 48 | import javax.servlet.ServletSecurityElement; |
| 49 | import javax.servlet.UnavailableException; |
| 50 | import javax.servlet.http.HttpServletRequest; |
| 51 | import javax.servlet.http.HttpServletResponse; |
| 52 | |
| 53 | import org.eclipse.jetty.continuation.ContinuationThrowable; |
| 54 | import org.eclipse.jetty.http.HttpException; |
| 55 | import org.eclipse.jetty.http.PathMap; |
| 56 | import org.eclipse.jetty.io.EofException; |
| 57 | import org.eclipse.jetty.io.RuntimeIOException; |
| 58 | import org.eclipse.jetty.security.IdentityService; |
| 59 | import org.eclipse.jetty.security.SecurityHandler; |
| 60 | import org.eclipse.jetty.server.AbstractHttpConnection; |
| 61 | import org.eclipse.jetty.server.AsyncContinuation; |
| 62 | import org.eclipse.jetty.server.Dispatcher; |
| 63 | import org.eclipse.jetty.server.AbstractHttpConnection; |
| 64 | import org.eclipse.jetty.server.Request; |
| 65 | import org.eclipse.jetty.server.Server; |
| 66 | import org.eclipse.jetty.server.ServletRequestHttpWrapper; |
| 67 | import org.eclipse.jetty.server.ServletResponseHttpWrapper; |
| 68 | import org.eclipse.jetty.server.UserIdentity; |
| 69 | import org.eclipse.jetty.server.handler.ContextHandler; |
| 70 | import org.eclipse.jetty.server.handler.ScopedHandler; |
| 71 | import org.eclipse.jetty.servlet.Holder.Source; |
| 72 | import org.eclipse.jetty.util.LazyList; |
| 73 | import org.eclipse.jetty.util.MultiException; |
| 74 | import org.eclipse.jetty.util.MultiMap; |
| 75 | import org.eclipse.jetty.util.TypeUtil; |
| 76 | import org.eclipse.jetty.util.URIUtil; |
| 77 | import org.eclipse.jetty.util.log.Log; |
| 78 | import org.eclipse.jetty.util.log.Logger; |
| 79 | |
| 80 | /* --------------------------------------------------------------------- */ |
| 81 | /** Servlet HttpHandler. |
| 82 | * This handler maps requests to servlets that implement the |
| 83 | * javax.servlet.http.HttpServlet API. |
| 84 | * <P> |
| 85 | * This handler does not implement the full J2EE features and is intended to |
| 86 | * be used directly when a full web application is not required. If a Web application is required, |
| 87 | * then this handler should be used as part of a <code>org.eclipse.jetty.webapp.WebAppContext</code>. |
| 88 | * <p> |
| 89 | * Unless run as part of a {@link ServletContextHandler} or derivative, the {@link #initialize()} |
| 90 | * method must be called manually after start(). |
| 91 | */ |
| 92 | public class ServletHandler extends ScopedHandler |
| 93 | { |
| 94 | private static final Logger LOG = Log.getLogger(ServletHandler.class); |
| 95 | |
| 96 | /* ------------------------------------------------------------ */ |
| 97 | public static final String __DEFAULT_SERVLET="default"; |
| 98 | |
| 99 | /* ------------------------------------------------------------ */ |
| 100 | private ServletContextHandler _contextHandler; |
| 101 | private ContextHandler.Context _servletContext; |
| 102 | private FilterHolder[] _filters=new FilterHolder[0]; |
| 103 | private FilterMapping[] _filterMappings; |
| 104 | private int _matchBeforeIndex = -1; //index of last programmatic FilterMapping with isMatchAfter=false |
| 105 | private int _matchAfterIndex = -1; //index of 1st programmatic FilterMapping with isMatchAfter=true |
| 106 | private boolean _filterChainsCached=true; |
| 107 | private int _maxFilterChainsCacheSize=512; |
| 108 | private boolean _startWithUnavailable=false; |
| 109 | private IdentityService _identityService; |
| 110 | |
| 111 | private ServletHolder[] _servlets=new ServletHolder[0]; |
| 112 | private ServletMapping[] _servletMappings; |
| 113 | |
| 114 | private final Map<String,FilterHolder> _filterNameMap= new HashMap<String,FilterHolder>(); |
| 115 | private List<FilterMapping> _filterPathMappings; |
| 116 | private MultiMap<String> _filterNameMappings; |
| 117 | |
| 118 | private final Map<String,ServletHolder> _servletNameMap=new HashMap<String,ServletHolder>(); |
| 119 | private PathMap _servletPathMap; |
| 120 | |
| 121 | protected final ConcurrentMap<String,FilterChain> _chainCache[] = new ConcurrentMap[FilterMapping.ALL]; |
| 122 | protected final Queue<String>[] _chainLRU = new Queue[FilterMapping.ALL]; |
| 123 | |
| 124 | |
| 125 | /* ------------------------------------------------------------ */ |
| 126 | /** Constructor. |
| 127 | */ |
| 128 | public ServletHandler() |
| 129 | { |
| 130 | } |
| 131 | |
| 132 | /* ------------------------------------------------------------ */ |
| 133 | /* |
| 134 | * @see org.eclipse.jetty.server.handler.AbstractHandler#setServer(org.eclipse.jetty.server.Server) |
| 135 | */ |
| 136 | public void setServer(Server server) |
| 137 | { |
| 138 | Server old=getServer(); |
| 139 | if (old!=null && old!=server) |
| 140 | { |
| 141 | getServer().getContainer().update(this, _filters, null, "filter",true); |
| 142 | getServer().getContainer().update(this, _filterMappings, null, "filterMapping",true); |
| 143 | getServer().getContainer().update(this, _servlets, null, "servlet",true); |
| 144 | getServer().getContainer().update(this, _servletMappings, null, "servletMapping",true); |
| 145 | } |
| 146 | |
| 147 | super.setServer(server); |
| 148 | |
| 149 | if (server!=null && old!=server) |
| 150 | { |
| 151 | server.getContainer().update(this, null, _filters, "filter",true); |
| 152 | server.getContainer().update(this, null, _filterMappings, "filterMapping",true); |
| 153 | server.getContainer().update(this, null, _servlets, "servlet",true); |
| 154 | server.getContainer().update(this, null, _servletMappings, "servletMapping",true); |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | /* ----------------------------------------------------------------- */ |
| 159 | @Override |
| 160 | protected synchronized void doStart() |
| 161 | throws Exception |
| 162 | { |
| 163 | _servletContext=ContextHandler.getCurrentContext(); |
| 164 | _contextHandler=(ServletContextHandler)(_servletContext==null?null:_servletContext.getContextHandler()); |
| 165 | |
| 166 | if (_contextHandler!=null) |
| 167 | { |
| 168 | SecurityHandler security_handler = (SecurityHandler)_contextHandler.getChildHandlerByClass(SecurityHandler.class); |
| 169 | if (security_handler!=null) |
| 170 | _identityService=security_handler.getIdentityService(); |
| 171 | } |
| 172 | |
| 173 | updateNameMappings(); |
| 174 | updateMappings(); |
| 175 | |
| 176 | if(_filterChainsCached) |
| 177 | { |
| 178 | _chainCache[FilterMapping.REQUEST]=new ConcurrentHashMap<String,FilterChain>(); |
| 179 | _chainCache[FilterMapping.FORWARD]=new ConcurrentHashMap<String,FilterChain>(); |
| 180 | _chainCache[FilterMapping.INCLUDE]=new ConcurrentHashMap<String,FilterChain>(); |
| 181 | _chainCache[FilterMapping.ERROR]=new ConcurrentHashMap<String,FilterChain>(); |
| 182 | _chainCache[FilterMapping.ASYNC]=new ConcurrentHashMap<String,FilterChain>(); |
| 183 | |
| 184 | _chainLRU[FilterMapping.REQUEST]=new ConcurrentLinkedQueue<String>(); |
| 185 | _chainLRU[FilterMapping.FORWARD]=new ConcurrentLinkedQueue<String>(); |
| 186 | _chainLRU[FilterMapping.INCLUDE]=new ConcurrentLinkedQueue<String>(); |
| 187 | _chainLRU[FilterMapping.ERROR]=new ConcurrentLinkedQueue<String>(); |
| 188 | _chainLRU[FilterMapping.ASYNC]=new ConcurrentLinkedQueue<String>(); |
| 189 | } |
| 190 | |
| 191 | super.doStart(); |
| 192 | |
| 193 | if (_contextHandler==null || !(_contextHandler instanceof ServletContextHandler)) |
| 194 | initialize(); |
| 195 | } |
| 196 | |
| 197 | /* ----------------------------------------------------------------- */ |
| 198 | @Override |
| 199 | protected synchronized void doStop() |
| 200 | throws Exception |
| 201 | { |
| 202 | super.doStop(); |
| 203 | |
| 204 | // Stop filters |
| 205 | List<FilterHolder> filterHolders = new ArrayList<FilterHolder>(); |
| 206 | List<FilterMapping> filterMappings = LazyList.array2List(_filterMappings); |
| 207 | if (_filters!=null) |
| 208 | { |
| 209 | for (int i=_filters.length; i-->0;) |
| 210 | { |
| 211 | try { _filters[i].stop(); }catch(Exception e){LOG.warn(Log.EXCEPTION,e);} |
| 212 | if (_filters[i].getSource() != Source.EMBEDDED) |
| 213 | { |
| 214 | //remove all of the mappings that were for non-embedded filters |
| 215 | _filterNameMap.remove(_filters[i].getName()); |
| 216 | //remove any mappings associated with this filter |
| 217 | ListIterator<FilterMapping> fmitor = filterMappings.listIterator(); |
| 218 | while (fmitor.hasNext()) |
| 219 | { |
| 220 | FilterMapping fm = fmitor.next(); |
| 221 | if (fm.getFilterName().equals(_filters[i].getName())) |
| 222 | fmitor.remove(); |
| 223 | } |
| 224 | } |
| 225 | else |
| 226 | filterHolders.add(_filters[i]); //only retain embedded |
| 227 | } |
| 228 | } |
| 229 | _filters = (FilterHolder[]) LazyList.toArray(filterHolders, FilterHolder.class); |
| 230 | _filterMappings = (FilterMapping[]) LazyList.toArray(filterMappings, FilterMapping.class); |
| 231 | _matchAfterIndex = (_filterMappings == null || _filterMappings.length == 0 ? -1 : _filterMappings.length-1); |
| 232 | _matchBeforeIndex = -1; |
| 233 | |
| 234 | |
| 235 | // Stop servlets |
| 236 | List<ServletHolder> servletHolders = new ArrayList<ServletHolder>(); //will be remaining servlets |
| 237 | List<ServletMapping> servletMappings = LazyList.array2List(_servletMappings); //will be remaining mappings |
| 238 | if (_servlets!=null) |
| 239 | { |
| 240 | for (int i=_servlets.length; i-->0;) |
| 241 | { |
| 242 | try { _servlets[i].stop(); }catch(Exception e){LOG.warn(Log.EXCEPTION,e);} |
| 243 | if (_servlets[i].getSource() != Source.EMBEDDED) |
| 244 | { |
| 245 | //remove from servlet name map |
| 246 | _servletNameMap.remove(_servlets[i].getName()); |
| 247 | //remove any mappings associated with this servlet |
| 248 | ListIterator<ServletMapping> smitor = servletMappings.listIterator(); |
| 249 | while (smitor.hasNext()) |
| 250 | { |
| 251 | ServletMapping sm = smitor.next(); |
| 252 | if (sm.getServletName().equals(_servlets[i].getName())) |
| 253 | smitor.remove(); |
| 254 | } |
| 255 | } |
| 256 | else |
| 257 | servletHolders.add(_servlets[i]); //only retain embedded |
| 258 | } |
| 259 | } |
| 260 | _servlets = (ServletHolder[]) LazyList.toArray(servletHolders, ServletHolder.class); |
| 261 | _servletMappings = (ServletMapping[])LazyList.toArray(servletMappings, ServletMapping.class); |
| 262 | |
| 263 | |
| 264 | //will be regenerated on next start |
| 265 | _filterPathMappings=null; |
| 266 | _filterNameMappings=null; |
| 267 | _servletPathMap=null; |
| 268 | } |
| 269 | |
| 270 | /* ------------------------------------------------------------ */ |
| 271 | protected IdentityService getIdentityService() |
| 272 | { |
| 273 | return _identityService; |
| 274 | } |
| 275 | |
| 276 | /* ------------------------------------------------------------ */ |
| 277 | /** |
| 278 | * @return Returns the contextLog. |
| 279 | */ |
| 280 | public Object getContextLog() |
| 281 | { |
| 282 | return null; |
| 283 | } |
| 284 | |
| 285 | /* ------------------------------------------------------------ */ |
| 286 | /** |
| 287 | * @return Returns the filterMappings. |
| 288 | */ |
| 289 | public FilterMapping[] getFilterMappings() |
| 290 | { |
| 291 | return _filterMappings; |
| 292 | } |
| 293 | |
| 294 | /* ------------------------------------------------------------ */ |
| 295 | /** Get Filters. |
| 296 | * @return Array of defined servlets |
| 297 | */ |
| 298 | public FilterHolder[] getFilters() |
| 299 | { |
| 300 | return _filters; |
| 301 | } |
| 302 | |
| 303 | /* ------------------------------------------------------------ */ |
| 304 | /** ServletHolder matching path. |
| 305 | * @param pathInContext Path within _context. |
| 306 | * @return PathMap Entries pathspec to ServletHolder |
| 307 | */ |
| 308 | public PathMap.Entry getHolderEntry(String pathInContext) |
| 309 | { |
| 310 | if (_servletPathMap==null) |
| 311 | return null; |
| 312 | return _servletPathMap.getMatch(pathInContext); |
| 313 | } |
| 314 | |
| 315 | /* ------------------------------------------------------------ */ |
| 316 | public ServletContext getServletContext() |
| 317 | { |
| 318 | return _servletContext; |
| 319 | } |
| 320 | |
| 321 | /* ------------------------------------------------------------ */ |
| 322 | /** |
| 323 | * @return Returns the servletMappings. |
| 324 | */ |
| 325 | public ServletMapping[] getServletMappings() |
| 326 | { |
| 327 | return _servletMappings; |
| 328 | } |
| 329 | |
| 330 | /* ------------------------------------------------------------ */ |
| 331 | /** |
| 332 | * @return Returns the servletMappings. |
| 333 | */ |
| 334 | public ServletMapping getServletMapping(String pattern) |
| 335 | { |
| 336 | ServletMapping theMapping = null; |
| 337 | if (_servletMappings!=null) |
| 338 | { |
| 339 | for (ServletMapping m:_servletMappings) |
| 340 | { |
| 341 | String[] paths=m.getPathSpecs(); |
| 342 | if (paths!=null) |
| 343 | { |
| 344 | for (String path:paths) |
| 345 | { |
| 346 | if (pattern.equals(path)) |
| 347 | theMapping = m; |
| 348 | } |
| 349 | } |
| 350 | } |
| 351 | } |
| 352 | return theMapping; |
| 353 | } |
| 354 | |
| 355 | /* ------------------------------------------------------------ */ |
| 356 | /** Get Servlets. |
| 357 | * @return Array of defined servlets |
| 358 | */ |
| 359 | public ServletHolder[] getServlets() |
| 360 | { |
| 361 | return _servlets; |
| 362 | } |
| 363 | |
| 364 | /* ------------------------------------------------------------ */ |
| 365 | public ServletHolder getServlet(String name) |
| 366 | { |
| 367 | return (ServletHolder)_servletNameMap.get(name); |
| 368 | } |
| 369 | |
| 370 | /* ------------------------------------------------------------ */ |
| 371 | @Override |
| 372 | public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException |
| 373 | { |
| 374 | // Get the base requests |
| 375 | final String old_servlet_path=baseRequest.getServletPath(); |
| 376 | final String old_path_info=baseRequest.getPathInfo(); |
| 377 | |
| 378 | DispatcherType type = baseRequest.getDispatcherType(); |
| 379 | |
| 380 | ServletHolder servlet_holder=null; |
| 381 | UserIdentity.Scope old_scope=null; |
| 382 | |
| 383 | // find the servlet |
| 384 | if (target.startsWith("/")) |
| 385 | { |
| 386 | // Look for the servlet by path |
| 387 | PathMap.Entry entry=getHolderEntry(target); |
| 388 | if (entry!=null) |
| 389 | { |
| 390 | servlet_holder=(ServletHolder)entry.getValue(); |
| 391 | |
| 392 | String servlet_path_spec=(String)entry.getKey(); |
| 393 | String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target); |
| 394 | String path_info=PathMap.pathInfo(servlet_path_spec,target); |
| 395 | |
| 396 | if (DispatcherType.INCLUDE.equals(type)) |
| 397 | { |
| 398 | baseRequest.setAttribute(Dispatcher.INCLUDE_SERVLET_PATH,servlet_path); |
| 399 | baseRequest.setAttribute(Dispatcher.INCLUDE_PATH_INFO, path_info); |
| 400 | } |
| 401 | else |
| 402 | { |
| 403 | baseRequest.setServletPath(servlet_path); |
| 404 | baseRequest.setPathInfo(path_info); |
| 405 | } |
| 406 | } |
| 407 | } |
| 408 | else |
| 409 | { |
| 410 | // look for a servlet by name! |
| 411 | servlet_holder=(ServletHolder)_servletNameMap.get(target); |
| 412 | } |
| 413 | |
| 414 | if (LOG.isDebugEnabled()) |
| 415 | LOG.debug("servlet {}|{}|{} -> {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),servlet_holder); |
| 416 | |
| 417 | try |
| 418 | { |
| 419 | // Do the filter/handling thang |
| 420 | old_scope=baseRequest.getUserIdentityScope(); |
| 421 | baseRequest.setUserIdentityScope(servlet_holder); |
| 422 | |
| 423 | // start manual inline of nextScope(target,baseRequest,request,response); |
| 424 | if (never()) |
| 425 | nextScope(target,baseRequest,request,response); |
| 426 | else if (_nextScope!=null) |
| 427 | _nextScope.doScope(target,baseRequest,request, response); |
| 428 | else if (_outerScope!=null) |
| 429 | _outerScope.doHandle(target,baseRequest,request, response); |
| 430 | else |
| 431 | doHandle(target,baseRequest,request, response); |
| 432 | // end manual inline (pathentic attempt to reduce stack depth) |
| 433 | } |
| 434 | finally |
| 435 | { |
| 436 | if (old_scope!=null) |
| 437 | baseRequest.setUserIdentityScope(old_scope); |
| 438 | |
| 439 | if (!(DispatcherType.INCLUDE.equals(type))) |
| 440 | { |
| 441 | baseRequest.setServletPath(old_servlet_path); |
| 442 | baseRequest.setPathInfo(old_path_info); |
| 443 | } |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | /* ------------------------------------------------------------ */ |
| 448 | /* |
| 449 | * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) |
| 450 | */ |
| 451 | @Override |
| 452 | public void doHandle(String target, Request baseRequest,HttpServletRequest request, HttpServletResponse response) |
| 453 | throws IOException, ServletException |
| 454 | { |
| 455 | DispatcherType type = baseRequest.getDispatcherType(); |
| 456 | |
| 457 | ServletHolder servlet_holder=(ServletHolder) baseRequest.getUserIdentityScope(); |
| 458 | FilterChain chain=null; |
| 459 | |
| 460 | // find the servlet |
| 461 | if (target.startsWith("/")) |
| 462 | { |
| 463 | if (servlet_holder!=null && _filterMappings!=null && _filterMappings.length>0) |
| 464 | chain=getFilterChain(baseRequest, target, servlet_holder); |
| 465 | } |
| 466 | else |
| 467 | { |
| 468 | if (servlet_holder!=null) |
| 469 | { |
| 470 | if (_filterMappings!=null && _filterMappings.length>0) |
| 471 | { |
| 472 | chain=getFilterChain(baseRequest, null,servlet_holder); |
| 473 | } |
| 474 | } |
| 475 | } |
| 476 | |
| 477 | LOG.debug("chain={}",chain); |
| 478 | |
| 479 | Throwable th=null; |
| 480 | try |
| 481 | { |
| 482 | if (servlet_holder==null) |
| 483 | { |
| 484 | if (getHandler()==null) |
| 485 | notFound(request, response); |
| 486 | else |
| 487 | nextHandle(target,baseRequest,request,response); |
| 488 | } |
| 489 | else |
| 490 | { |
| 491 | // unwrap any tunnelling of base Servlet request/responses |
| 492 | ServletRequest req = request; |
| 493 | if (req instanceof ServletRequestHttpWrapper) |
| 494 | req = ((ServletRequestHttpWrapper)req).getRequest(); |
| 495 | ServletResponse res = response; |
| 496 | if (res instanceof ServletResponseHttpWrapper) |
| 497 | res = ((ServletResponseHttpWrapper)res).getResponse(); |
| 498 | |
| 499 | // Do the filter/handling thang |
| 500 | if (chain!=null) |
| 501 | chain.doFilter(req, res); |
| 502 | else |
| 503 | servlet_holder.handle(baseRequest,req,res); |
| 504 | } |
| 505 | } |
| 506 | catch(EofException e) |
| 507 | { |
| 508 | throw e; |
| 509 | } |
| 510 | catch(RuntimeIOException e) |
| 511 | { |
| 512 | throw e; |
| 513 | } |
| 514 | catch(ContinuationThrowable e) |
| 515 | { |
| 516 | throw e; |
| 517 | } |
| 518 | catch(Exception e) |
| 519 | { |
| 520 | if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type))) |
| 521 | { |
| 522 | if (e instanceof IOException) |
| 523 | throw (IOException)e; |
| 524 | if (e instanceof RuntimeException) |
| 525 | throw (RuntimeException)e; |
| 526 | if (e instanceof ServletException) |
| 527 | throw (ServletException)e; |
| 528 | } |
| 529 | |
| 530 | // unwrap cause |
| 531 | th=e; |
| 532 | if (th instanceof UnavailableException) |
| 533 | { |
| 534 | LOG.debug(th); |
| 535 | } |
| 536 | else if (th instanceof ServletException) |
| 537 | { |
| 538 | LOG.warn(th); |
| 539 | Throwable cause=((ServletException)th).getRootCause(); |
| 540 | if (cause!=null) |
| 541 | th=cause; |
| 542 | } |
| 543 | |
| 544 | // handle or log exception |
| 545 | if (th instanceof HttpException) |
| 546 | throw (HttpException)th; |
| 547 | else if (th instanceof RuntimeIOException) |
| 548 | throw (RuntimeIOException)th; |
| 549 | else if (th instanceof EofException) |
| 550 | throw (EofException)th; |
| 551 | |
| 552 | else if (LOG.isDebugEnabled()) |
| 553 | { |
| 554 | LOG.warn(request.getRequestURI(), th); |
| 555 | LOG.debug(request.toString()); |
| 556 | } |
| 557 | else if (th instanceof IOException || th instanceof UnavailableException) |
| 558 | { |
| 559 | LOG.debug(request.getRequestURI(),th); |
| 560 | } |
| 561 | else |
| 562 | { |
| 563 | LOG.warn(request.getRequestURI(),th); |
| 564 | } |
| 565 | |
| 566 | request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,th.getClass()); |
| 567 | request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,th); |
| 568 | if (!response.isCommitted()) |
| 569 | { |
| 570 | if (th instanceof UnavailableException) |
| 571 | { |
| 572 | UnavailableException ue = (UnavailableException)th; |
| 573 | if (ue.isPermanent()) |
| 574 | response.sendError(HttpServletResponse.SC_NOT_FOUND); |
| 575 | else |
| 576 | response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); |
| 577 | } |
| 578 | else |
| 579 | response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); |
| 580 | } |
| 581 | else |
| 582 | LOG.debug("Response already committed for handling "+th); |
| 583 | |
| 584 | } |
| 585 | catch(Error e) |
| 586 | { |
| 587 | if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type))) |
| 588 | throw e; |
| 589 | th=e; |
| 590 | LOG.warn("Error for "+request.getRequestURI(),e); |
| 591 | if(LOG.isDebugEnabled())LOG.debug(request.toString()); |
| 592 | |
| 593 | request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,e.getClass()); |
| 594 | request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,e); |
| 595 | if (!response.isCommitted()) |
| 596 | response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); |
| 597 | else |
| 598 | LOG.debug("Response already committed for handling ",e); |
| 599 | } |
| 600 | finally |
| 601 | { |
| 602 | if (servlet_holder!=null) |
| 603 | baseRequest.setHandled(true); |
| 604 | |
| 605 | // Complete async requests |
| 606 | if (th!=null && request.isAsyncStarted()) |
| 607 | ((AsyncContinuation)request.getAsyncContext()).errorComplete(); |
| 608 | } |
| 609 | } |
| 610 | |
| 611 | /* ------------------------------------------------------------ */ |
| 612 | protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder) |
| 613 | { |
| 614 | String key=pathInContext==null?servletHolder.getName():pathInContext; |
| 615 | int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType()); |
| 616 | |
| 617 | if (_filterChainsCached && _chainCache!=null) |
| 618 | { |
| 619 | FilterChain chain = (FilterChain)_chainCache[dispatch].get(key); |
| 620 | if (chain!=null) |
| 621 | return chain; |
| 622 | } |
| 623 | |
| 624 | // Build list of filters |
| 625 | Object filters= null; |
| 626 | // Path filters |
| 627 | if (pathInContext!=null && _filterPathMappings!=null) |
| 628 | { |
| 629 | for (int i= 0; i < _filterPathMappings.size(); i++) |
| 630 | { |
| 631 | FilterMapping mapping = (FilterMapping)_filterPathMappings.get(i); |
| 632 | if (mapping.appliesTo(pathInContext, dispatch)) |
| 633 | filters= LazyList.add(filters, mapping.getFilterHolder()); |
| 634 | } |
| 635 | } |
| 636 | |
| 637 | // Servlet name filters |
| 638 | if (servletHolder != null && _filterNameMappings!=null && _filterNameMappings.size() > 0) |
| 639 | { |
| 640 | // Servlet name filters |
| 641 | if (_filterNameMappings.size() > 0) |
| 642 | { |
| 643 | Object o= _filterNameMappings.get(servletHolder.getName()); |
| 644 | for (int i=0; i<LazyList.size(o);i++) |
| 645 | { |
| 646 | FilterMapping mapping = (FilterMapping)LazyList.get(o,i); |
| 647 | if (mapping.appliesTo(dispatch)) |
| 648 | filters=LazyList.add(filters,mapping.getFilterHolder()); |
| 649 | } |
| 650 | |
| 651 | o= _filterNameMappings.get("*"); |
| 652 | for (int i=0; i<LazyList.size(o);i++) |
| 653 | { |
| 654 | FilterMapping mapping = (FilterMapping)LazyList.get(o,i); |
| 655 | if (mapping.appliesTo(dispatch)) |
| 656 | filters=LazyList.add(filters,mapping.getFilterHolder()); |
| 657 | } |
| 658 | } |
| 659 | } |
| 660 | |
| 661 | if (filters==null) |
| 662 | return null; |
| 663 | |
| 664 | |
| 665 | FilterChain chain = null; |
| 666 | if (_filterChainsCached) |
| 667 | { |
| 668 | if (LazyList.size(filters) > 0) |
| 669 | chain= new CachedChain(filters, servletHolder); |
| 670 | |
| 671 | final Map<String,FilterChain> cache=_chainCache[dispatch]; |
| 672 | final Queue<String> lru=_chainLRU[dispatch]; |
| 673 | |
| 674 | // Do we have too many cached chains? |
| 675 | while (_maxFilterChainsCacheSize>0 && cache.size()>=_maxFilterChainsCacheSize) |
| 676 | { |
| 677 | // The LRU list is not atomic with the cache map, so be prepared to invalidate if |
| 678 | // a key is not found to delete. |
| 679 | // Delete by LRU (where U==created) |
| 680 | String k=lru.poll(); |
| 681 | if (k==null) |
| 682 | { |
| 683 | cache.clear(); |
| 684 | break; |
| 685 | } |
| 686 | cache.remove(k); |
| 687 | } |
| 688 | |
| 689 | cache.put(key,chain); |
| 690 | lru.add(key); |
| 691 | } |
| 692 | else if (LazyList.size(filters) > 0) |
| 693 | chain = new Chain(baseRequest,filters, servletHolder); |
| 694 | |
| 695 | return chain; |
| 696 | } |
| 697 | |
| 698 | /* ------------------------------------------------------------ */ |
| 699 | protected void invalidateChainsCache() |
| 700 | { |
| 701 | if (_chainLRU[FilterMapping.REQUEST]!=null) |
| 702 | { |
| 703 | _chainLRU[FilterMapping.REQUEST].clear(); |
| 704 | _chainLRU[FilterMapping.FORWARD].clear(); |
| 705 | _chainLRU[FilterMapping.INCLUDE].clear(); |
| 706 | _chainLRU[FilterMapping.ERROR].clear(); |
| 707 | _chainLRU[FilterMapping.ASYNC].clear(); |
| 708 | |
| 709 | _chainCache[FilterMapping.REQUEST].clear(); |
| 710 | _chainCache[FilterMapping.FORWARD].clear(); |
| 711 | _chainCache[FilterMapping.INCLUDE].clear(); |
| 712 | _chainCache[FilterMapping.ERROR].clear(); |
| 713 | _chainCache[FilterMapping.ASYNC].clear(); |
| 714 | } |
| 715 | } |
| 716 | |
| 717 | /* ------------------------------------------------------------ */ |
| 718 | /** |
| 719 | * @return true if the handler is started and there are no unavailable servlets |
| 720 | */ |
| 721 | public boolean isAvailable() |
| 722 | { |
| 723 | if (!isStarted()) |
| 724 | return false; |
| 725 | ServletHolder[] holders = getServlets(); |
| 726 | for (int i=0;i<holders.length;i++) |
| 727 | { |
| 728 | ServletHolder holder = holders[i]; |
| 729 | if (holder!=null && !holder.isAvailable()) |
| 730 | return false; |
| 731 | } |
| 732 | return true; |
| 733 | } |
| 734 | |
| 735 | /* ------------------------------------------------------------ */ |
| 736 | /** |
| 737 | * @param start True if this handler will start with unavailable servlets |
| 738 | */ |
| 739 | public void setStartWithUnavailable(boolean start) |
| 740 | { |
| 741 | _startWithUnavailable=start; |
| 742 | } |
| 743 | |
| 744 | /* ------------------------------------------------------------ */ |
| 745 | /** |
| 746 | * @return True if this handler will start with unavailable servlets |
| 747 | */ |
| 748 | public boolean isStartWithUnavailable() |
| 749 | { |
| 750 | return _startWithUnavailable; |
| 751 | } |
| 752 | |
| 753 | |
| 754 | |
| 755 | /* ------------------------------------------------------------ */ |
| 756 | /** Initialize filters and load-on-startup servlets. |
| 757 | * Called automatically from start if autoInitializeServlet is true. |
| 758 | */ |
| 759 | public void initialize() |
| 760 | throws Exception |
| 761 | { |
| 762 | MultiException mx = new MultiException(); |
| 763 | |
| 764 | // Start filters |
| 765 | if (_filters!=null) |
| 766 | { |
| 767 | for (int i=0;i<_filters.length; i++) |
| 768 | _filters[i].start(); |
| 769 | } |
| 770 | |
| 771 | if (_servlets!=null) |
| 772 | { |
| 773 | // Sort and Initialize servlets |
| 774 | ServletHolder[] servlets = (ServletHolder[])_servlets.clone(); |
| 775 | Arrays.sort(servlets); |
| 776 | for (int i=0; i<servlets.length; i++) |
| 777 | { |
| 778 | try |
| 779 | { |
| 780 | if (servlets[i].getClassName()==null && servlets[i].getForcedPath()!=null) |
| 781 | { |
| 782 | ServletHolder forced_holder = (ServletHolder)_servletPathMap.match(servlets[i].getForcedPath()); |
| 783 | if (forced_holder==null || forced_holder.getClassName()==null) |
| 784 | { |
| 785 | mx.add(new IllegalStateException("No forced path servlet for "+servlets[i].getForcedPath())); |
| 786 | continue; |
| 787 | } |
| 788 | servlets[i].setClassName(forced_holder.getClassName()); |
| 789 | } |
| 790 | |
| 791 | servlets[i].start(); |
| 792 | } |
| 793 | catch(Throwable e) |
| 794 | { |
| 795 | LOG.debug(Log.EXCEPTION,e); |
| 796 | mx.add(e); |
| 797 | } |
| 798 | } |
| 799 | mx.ifExceptionThrow(); |
| 800 | } |
| 801 | } |
| 802 | |
| 803 | /* ------------------------------------------------------------ */ |
| 804 | /** |
| 805 | * @return Returns the filterChainsCached. |
| 806 | */ |
| 807 | public boolean isFilterChainsCached() |
| 808 | { |
| 809 | return _filterChainsCached; |
| 810 | } |
| 811 | |
| 812 | /* ------------------------------------------------------------ */ |
| 813 | /** |
| 814 | * see also newServletHolder(Class) |
| 815 | */ |
| 816 | public ServletHolder newServletHolder(Holder.Source source) |
| 817 | { |
| 818 | return new ServletHolder(source); |
| 819 | } |
| 820 | |
| 821 | /* ------------------------------------------------------------ */ |
| 822 | /** Convenience method to add a servlet Holder. |
| 823 | public ServletHolder newServletHolder(Class<? extends Servlet> servlet) |
| 824 | { |
| 825 | return new ServletHolder(servlet); |
| 826 | } |
| 827 | |
| 828 | /* ------------------------------------------------------------ */ |
| 829 | /** Convenience method to add a servlet. |
| 830 | * @return The servlet holder. |
| 831 | */ |
| 832 | public ServletHolder addServletWithMapping (String className,String pathSpec) |
| 833 | { |
| 834 | ServletHolder holder = newServletHolder(Holder.Source.EMBEDDED); |
| 835 | holder.setClassName(className); |
| 836 | addServletWithMapping(holder,pathSpec); |
| 837 | return holder; |
| 838 | } |
| 839 | |
| 840 | /* ------------------------------------------------------------ */ |
| 841 | /** conveniance method to add a servlet. |
| 842 | * @return The servlet holder. |
| 843 | */ |
| 844 | public ServletHolder addServletWithMapping (Class<? extends Servlet> servlet,String pathSpec) |
| 845 | { |
| 846 | ServletHolder holder = newServletHolder(Holder.Source.EMBEDDED); |
| 847 | holder.setHeldClass(servlet); |
| 848 | //DUPLICATES adding servlet from addServletWithMapping(holder, pathSpec)? |
| 849 | //setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class)); |
| 850 | addServletWithMapping(holder,pathSpec); |
| 851 | |
| 852 | return holder; |
| 853 | } |
| 854 | |
| 855 | /* ------------------------------------------------------------ */ |
| 856 | /** conveniance method to add a servlet. |
| 857 | * @param servlet servlet holder to add |
| 858 | * @param pathSpec servlet mappings for the servletHolder |
| 859 | */ |
| 860 | public void addServletWithMapping (ServletHolder servlet,String pathSpec) |
| 861 | { |
| 862 | ServletHolder[] holders=getServlets(); |
| 863 | if (holders!=null) |
| 864 | holders = holders.clone(); |
| 865 | |
| 866 | try |
| 867 | { |
| 868 | setServlets((ServletHolder[])LazyList.addToArray(holders, servlet, ServletHolder.class)); |
| 869 | |
| 870 | ServletMapping mapping = new ServletMapping(); |
| 871 | mapping.setServletName(servlet.getName()); |
| 872 | mapping.setPathSpec(pathSpec); |
| 873 | setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class)); |
| 874 | } |
| 875 | catch (Exception e) |
| 876 | { |
| 877 | setServlets(holders); |
| 878 | if (e instanceof RuntimeException) |
| 879 | throw (RuntimeException)e; |
| 880 | throw new RuntimeException(e); |
| 881 | } |
| 882 | } |
| 883 | |
| 884 | |
| 885 | /* ------------------------------------------------------------ */ |
| 886 | /**Convenience method to add a pre-constructed ServletHolder. |
| 887 | * @param holder |
| 888 | */ |
| 889 | public void addServlet(ServletHolder holder) |
| 890 | { |
| 891 | setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class)); |
| 892 | } |
| 893 | |
| 894 | /* ------------------------------------------------------------ */ |
| 895 | /** Convenience method to add a pre-constructed ServletMapping. |
| 896 | * @param mapping |
| 897 | */ |
| 898 | public void addServletMapping (ServletMapping mapping) |
| 899 | { |
| 900 | setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class)); |
| 901 | } |
| 902 | |
| 903 | public Set<String> setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement) { |
| 904 | if (_contextHandler != null) { |
| 905 | return _contextHandler.setServletSecurity(registration, servletSecurityElement); |
| 906 | } |
| 907 | return Collections.emptySet(); |
| 908 | } |
| 909 | |
| 910 | /* ------------------------------------------------------------ */ |
| 911 | /** |
| 912 | * @see #newFilterHolder(Class) |
| 913 | */ |
| 914 | public FilterHolder newFilterHolder(Holder.Source source) |
| 915 | { |
| 916 | return new FilterHolder(source); |
| 917 | } |
| 918 | |
| 919 | /* ------------------------------------------------------------ */ |
| 920 | public FilterHolder getFilter(String name) |
| 921 | { |
| 922 | return (FilterHolder)_filterNameMap.get(name); |
| 923 | } |
| 924 | |
| 925 | |
| 926 | /* ------------------------------------------------------------ */ |
| 927 | /** Convenience method to add a filter. |
| 928 | * @param filter class of filter to create |
| 929 | * @param pathSpec filter mappings for filter |
| 930 | * @param dispatches see {@link FilterMapping#setDispatches(int)} |
| 931 | * @return The filter holder. |
| 932 | */ |
| 933 | public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,EnumSet<DispatcherType> dispatches) |
| 934 | { |
| 935 | FilterHolder holder = newFilterHolder(Holder.Source.EMBEDDED); |
| 936 | holder.setHeldClass(filter); |
| 937 | addFilterWithMapping(holder,pathSpec,dispatches); |
| 938 | |
| 939 | return holder; |
| 940 | } |
| 941 | |
| 942 | /* ------------------------------------------------------------ */ |
| 943 | /** Convenience method to add a filter. |
| 944 | * @param className of filter |
| 945 | * @param pathSpec filter mappings for filter |
| 946 | * @param dispatches see {@link FilterMapping#setDispatches(int)} |
| 947 | * @return The filter holder. |
| 948 | */ |
| 949 | public FilterHolder addFilterWithMapping (String className,String pathSpec,EnumSet<DispatcherType> dispatches) |
| 950 | { |
| 951 | FilterHolder holder = newFilterHolder(Holder.Source.EMBEDDED); |
| 952 | holder.setClassName(className); |
| 953 | |
| 954 | addFilterWithMapping(holder,pathSpec,dispatches); |
| 955 | return holder; |
| 956 | } |
| 957 | |
| 958 | /* ------------------------------------------------------------ */ |
| 959 | /** Convenience method to add a filter. |
| 960 | * @param holder filter holder to add |
| 961 | * @param pathSpec filter mappings for filter |
| 962 | * @param dispatches see {@link FilterMapping#setDispatches(int)} |
| 963 | */ |
| 964 | public void addFilterWithMapping (FilterHolder holder,String pathSpec,EnumSet<DispatcherType> dispatches) |
| 965 | { |
| 966 | FilterHolder[] holders = getFilters(); |
| 967 | if (holders!=null) |
| 968 | holders = (FilterHolder[])holders.clone(); |
| 969 | |
| 970 | try |
| 971 | { |
| 972 | setFilters((FilterHolder[])LazyList.addToArray(holders, holder, FilterHolder.class)); |
| 973 | |
| 974 | FilterMapping mapping = new FilterMapping(); |
| 975 | mapping.setFilterName(holder.getName()); |
| 976 | mapping.setPathSpec(pathSpec); |
| 977 | mapping.setDispatcherTypes(dispatches); |
| 978 | //setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class)); |
| 979 | addFilterMapping(mapping); |
| 980 | |
| 981 | } |
| 982 | catch (RuntimeException e) |
| 983 | { |
| 984 | setFilters(holders); |
| 985 | throw e; |
| 986 | } |
| 987 | catch (Error e) |
| 988 | { |
| 989 | setFilters(holders); |
| 990 | throw e; |
| 991 | } |
| 992 | |
| 993 | } |
| 994 | |
| 995 | /* ------------------------------------------------------------ */ |
| 996 | /** Convenience method to add a filter. |
| 997 | * @param filter class of filter to create |
| 998 | * @param pathSpec filter mappings for filter |
| 999 | * @param dispatches see {@link FilterMapping#setDispatches(int)} |
| 1000 | * @return The filter holder. |
| 1001 | */ |
| 1002 | public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,int dispatches) |
| 1003 | { |
| 1004 | FilterHolder holder = newFilterHolder(Holder.Source.EMBEDDED); |
| 1005 | holder.setHeldClass(filter); |
| 1006 | addFilterWithMapping(holder,pathSpec,dispatches); |
| 1007 | |
| 1008 | return holder; |
| 1009 | } |
| 1010 | |
| 1011 | /* ------------------------------------------------------------ */ |
| 1012 | /** Convenience method to add a filter. |
| 1013 | * @param className of filter |
| 1014 | * @param pathSpec filter mappings for filter |
| 1015 | * @param dispatches see {@link FilterMapping#setDispatches(int)} |
| 1016 | * @return The filter holder. |
| 1017 | */ |
| 1018 | public FilterHolder addFilterWithMapping (String className,String pathSpec,int dispatches) |
| 1019 | { |
| 1020 | FilterHolder holder = newFilterHolder(Holder.Source.EMBEDDED); |
| 1021 | holder.setClassName(className); |
| 1022 | |
| 1023 | addFilterWithMapping(holder,pathSpec,dispatches); |
| 1024 | return holder; |
| 1025 | } |
| 1026 | |
| 1027 | /* ------------------------------------------------------------ */ |
| 1028 | /** Convenience method to add a filter. |
| 1029 | * @param holder filter holder to add |
| 1030 | * @param pathSpec filter mappings for filter |
| 1031 | * @param dispatches see {@link FilterMapping#setDispatches(int)} |
| 1032 | */ |
| 1033 | public void addFilterWithMapping (FilterHolder holder,String pathSpec,int dispatches) |
| 1034 | { |
| 1035 | FilterHolder[] holders = getFilters(); |
| 1036 | if (holders!=null) |
| 1037 | holders = (FilterHolder[])holders.clone(); |
| 1038 | |
| 1039 | try |
| 1040 | { |
| 1041 | setFilters((FilterHolder[])LazyList.addToArray(holders, holder, FilterHolder.class)); |
| 1042 | |
| 1043 | FilterMapping mapping = new FilterMapping(); |
| 1044 | mapping.setFilterName(holder.getName()); |
| 1045 | mapping.setPathSpec(pathSpec); |
| 1046 | mapping.setDispatches(dispatches); |
| 1047 | //setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class)); |
| 1048 | addFilterMapping(mapping); |
| 1049 | } |
| 1050 | catch (RuntimeException e) |
| 1051 | { |
| 1052 | setFilters(holders); |
| 1053 | throw e; |
| 1054 | } |
| 1055 | catch (Error e) |
| 1056 | { |
| 1057 | setFilters(holders); |
| 1058 | throw e; |
| 1059 | } |
| 1060 | |
| 1061 | } |
| 1062 | |
| 1063 | /* ------------------------------------------------------------ */ |
| 1064 | /** Convenience method to add a filter with a mapping |
| 1065 | * @param className |
| 1066 | * @param pathSpec |
| 1067 | * @param dispatches |
| 1068 | * @return the filter holder created |
| 1069 | * @deprecated use {@link #addFilterWithMapping(Class, String, EnumSet<DispatcherType>)} instead |
| 1070 | */ |
| 1071 | public FilterHolder addFilter (String className,String pathSpec,EnumSet<DispatcherType> dispatches) |
| 1072 | { |
| 1073 | return addFilterWithMapping(className, pathSpec, dispatches); |
| 1074 | } |
| 1075 | |
| 1076 | /* ------------------------------------------------------------ */ |
| 1077 | /** |
| 1078 | * convenience method to add a filter and mapping |
| 1079 | * @param filter |
| 1080 | * @param filterMapping |
| 1081 | */ |
| 1082 | public void addFilter (FilterHolder filter, FilterMapping filterMapping) |
| 1083 | { |
| 1084 | if (filter != null) |
| 1085 | setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class)); |
| 1086 | if (filterMapping != null) |
| 1087 | //setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), filterMapping, FilterMapping.class)); |
| 1088 | addFilterMapping(filterMapping); |
| 1089 | } |
| 1090 | |
| 1091 | /* ------------------------------------------------------------ */ |
| 1092 | /** Convenience method to add a preconstructed FilterHolder |
| 1093 | * @param filter |
| 1094 | */ |
| 1095 | public void addFilter (FilterHolder filter) |
| 1096 | { |
| 1097 | if (filter != null) |
| 1098 | setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class)); |
| 1099 | } |
| 1100 | |
| 1101 | /* ------------------------------------------------------------ */ |
| 1102 | /** Convenience method to add a preconstructed FilterMapping |
| 1103 | * @param mapping |
| 1104 | */ |
| 1105 | public void addFilterMapping (FilterMapping mapping) |
| 1106 | { |
| 1107 | if (mapping != null) |
| 1108 | { |
| 1109 | Source source = (mapping.getFilterHolder()==null?null:mapping.getFilterHolder().getSource()); |
| 1110 | FilterMapping[] mappings =getFilterMappings(); |
| 1111 | if (mappings==null || mappings.length==0) |
| 1112 | { |
| 1113 | setFilterMappings(insertFilterMapping(mapping,0,false)); |
| 1114 | if (source != null && source == Source.JAVAX_API) |
| 1115 | _matchAfterIndex = 0; |
| 1116 | } |
| 1117 | else |
| 1118 | { |
| 1119 | //there are existing entries. If this is a programmatic filtermapping, it is added at the end of the list. |
| 1120 | //If this is a normal filtermapping, it is inserted after all the other filtermappings (matchBefores and normals), |
| 1121 | //but before the first matchAfter filtermapping. |
| 1122 | if (source != null && Source.JAVAX_API == source) |
| 1123 | { |
| 1124 | setFilterMappings(insertFilterMapping(mapping,mappings.length-1, false)); |
| 1125 | if (_matchAfterIndex < 0) |
| 1126 | _matchAfterIndex = getFilterMappings().length-1; |
| 1127 | } |
| 1128 | else |
| 1129 | { |
| 1130 | //insert non-programmatic filter mappings before any matchAfters, if any |
| 1131 | if (_matchAfterIndex < 0) |
| 1132 | setFilterMappings(insertFilterMapping(mapping,mappings.length-1, false)); |
| 1133 | else |
| 1134 | { |
| 1135 | FilterMapping[] new_mappings = insertFilterMapping(mapping, _matchAfterIndex, true); |
| 1136 | ++_matchAfterIndex; |
| 1137 | setFilterMappings(new_mappings); |
| 1138 | } |
| 1139 | } |
| 1140 | } |
| 1141 | } |
| 1142 | } |
| 1143 | |
| 1144 | |
| 1145 | /* ------------------------------------------------------------ */ |
| 1146 | /** Convenience method to add a preconstructed FilterMapping |
| 1147 | * @param mapping |
| 1148 | */ |
| 1149 | public void prependFilterMapping (FilterMapping mapping) |
| 1150 | { |
| 1151 | if (mapping != null) |
| 1152 | { |
| 1153 | Source source = mapping.getFilterHolder().getSource(); |
| 1154 | |
| 1155 | FilterMapping[] mappings = getFilterMappings(); |
| 1156 | if (mappings==null || mappings.length==0) |
| 1157 | { |
| 1158 | setFilterMappings(insertFilterMapping(mapping, 0, false)); |
| 1159 | if (source != null && Source.JAVAX_API == source) |
| 1160 | _matchBeforeIndex = 0; |
| 1161 | } |
| 1162 | else |
| 1163 | { |
| 1164 | if (source != null && Source.JAVAX_API == source) |
| 1165 | { |
| 1166 | //programmatically defined filter mappings are prepended to mapping list in the order |
| 1167 | //in which they were defined. In other words, insert this mapping at the tail of the |
| 1168 | //programmatically prepended filter mappings, BEFORE the first web.xml defined filter mapping. |
| 1169 | |
| 1170 | if (_matchBeforeIndex < 0) |
| 1171 | { |
| 1172 | //no programmatically defined prepended filter mappings yet, prepend this one |
| 1173 | _matchBeforeIndex = 0; |
| 1174 | FilterMapping[] new_mappings = insertFilterMapping(mapping, 0, true); |
| 1175 | setFilterMappings(new_mappings); |
| 1176 | } |
| 1177 | else |
| 1178 | { |
| 1179 | FilterMapping[] new_mappings = insertFilterMapping(mapping,_matchBeforeIndex, false); |
| 1180 | ++_matchBeforeIndex; |
| 1181 | setFilterMappings(new_mappings); |
| 1182 | } |
| 1183 | } |
| 1184 | else |
| 1185 | { |
| 1186 | //non programmatically defined, just prepend to list |
| 1187 | FilterMapping[] new_mappings = insertFilterMapping(mapping, 0, true); |
| 1188 | setFilterMappings(new_mappings); |
| 1189 | } |
| 1190 | |
| 1191 | //adjust matchAfterIndex ptr to take account of the mapping we just prepended |
| 1192 | if (_matchAfterIndex >= 0) |
| 1193 | ++_matchAfterIndex; |
| 1194 | } |
| 1195 | } |
| 1196 | } |
| 1197 | |
| 1198 | |
| 1199 | |
| 1200 | /** |
| 1201 | * Insert a filtermapping in the list |
| 1202 | * @param mapping the FilterMapping to add |
| 1203 | * @param pos the position in the existing arry at which to add it |
| 1204 | * @param before if true, insert before pos, if false insert after it |
| 1205 | * @return |
| 1206 | */ |
| 1207 | protected FilterMapping[] insertFilterMapping (FilterMapping mapping, int pos, boolean before) |
| 1208 | { |
| 1209 | if (pos < 0) |
| 1210 | throw new IllegalArgumentException("FilterMapping insertion pos < 0"); |
| 1211 | FilterMapping[] mappings = getFilterMappings(); |
| 1212 | |
| 1213 | if (mappings==null || mappings.length==0) |
| 1214 | { |
| 1215 | return new FilterMapping[] {mapping}; |
| 1216 | } |
| 1217 | FilterMapping[] new_mappings = new FilterMapping[mappings.length+1]; |
| 1218 | |
| 1219 | |
| 1220 | if (before) |
| 1221 | { |
| 1222 | //copy existing filter mappings up to but not including the pos |
| 1223 | System.arraycopy(mappings,0,new_mappings,0,pos); |
| 1224 | |
| 1225 | //add in the new mapping |
| 1226 | new_mappings[pos] = mapping; |
| 1227 | |
| 1228 | //copy the old pos mapping and any remaining existing mappings |
| 1229 | System.arraycopy(mappings,pos,new_mappings,pos+1, mappings.length-pos); |
| 1230 | |
| 1231 | } |
| 1232 | else |
| 1233 | { |
| 1234 | //copy existing filter mappings up to and including the pos |
| 1235 | System.arraycopy(mappings,0,new_mappings,0,pos+1); |
| 1236 | //add in the new mapping after the pos |
| 1237 | new_mappings[pos+1] = mapping; |
| 1238 | |
| 1239 | //copy the remaining existing mappings |
| 1240 | if (mappings.length > pos+1) |
| 1241 | System.arraycopy(mappings,pos+1,new_mappings,pos+2, mappings.length-(pos+1)); |
| 1242 | } |
| 1243 | return new_mappings; |
| 1244 | } |
| 1245 | |
| 1246 | |
| 1247 | /* ------------------------------------------------------------ */ |
| 1248 | protected synchronized void updateNameMappings() |
| 1249 | { |
| 1250 | // update filter name map |
| 1251 | _filterNameMap.clear(); |
| 1252 | if (_filters!=null) |
| 1253 | { |
| 1254 | for (int i=0;i<_filters.length;i++) |
| 1255 | { |
| 1256 | _filterNameMap.put(_filters[i].getName(),_filters[i]); |
| 1257 | _filters[i].setServletHandler(this); |
| 1258 | } |
| 1259 | } |
| 1260 | |
| 1261 | // Map servlet names to holders |
| 1262 | _servletNameMap.clear(); |
| 1263 | if (_servlets!=null) |
| 1264 | { |
| 1265 | // update the maps |
| 1266 | for (int i=0;i<_servlets.length;i++) |
| 1267 | { |
| 1268 | _servletNameMap.put(_servlets[i].getName(),_servlets[i]); |
| 1269 | _servlets[i].setServletHandler(this); |
| 1270 | } |
| 1271 | } |
| 1272 | } |
| 1273 | |
| 1274 | /* ------------------------------------------------------------ */ |
| 1275 | protected synchronized void updateMappings() |
| 1276 | { |
| 1277 | // update filter mappings |
| 1278 | if (_filterMappings==null) |
| 1279 | { |
| 1280 | _filterPathMappings=null; |
| 1281 | _filterNameMappings=null; |
| 1282 | } |
| 1283 | else |
| 1284 | { |
| 1285 | _filterPathMappings=new ArrayList(); |
| 1286 | _filterNameMappings=new MultiMap(); |
| 1287 | for (int i=0;i<_filterMappings.length;i++) |
| 1288 | { |
| 1289 | FilterHolder filter_holder = (FilterHolder)_filterNameMap.get(_filterMappings[i].getFilterName()); |
| 1290 | if (filter_holder==null) |
| 1291 | throw new IllegalStateException("No filter named "+_filterMappings[i].getFilterName()); |
| 1292 | _filterMappings[i].setFilterHolder(filter_holder); |
| 1293 | if (_filterMappings[i].getPathSpecs()!=null) |
| 1294 | _filterPathMappings.add(_filterMappings[i]); |
| 1295 | |
| 1296 | if (_filterMappings[i].getServletNames()!=null) |
| 1297 | { |
| 1298 | String[] names=_filterMappings[i].getServletNames(); |
| 1299 | for (int j=0;j<names.length;j++) |
| 1300 | { |
| 1301 | if (names[j]!=null) |
| 1302 | _filterNameMappings.add(names[j], _filterMappings[i]); |
| 1303 | } |
| 1304 | } |
| 1305 | } |
| 1306 | } |
| 1307 | |
| 1308 | // Map servlet paths to holders |
| 1309 | if (_servletMappings==null || _servletNameMap==null) |
| 1310 | { |
| 1311 | _servletPathMap=null; |
| 1312 | } |
| 1313 | else |
| 1314 | { |
| 1315 | PathMap pm = new PathMap(); |
| 1316 | |
| 1317 | // update the maps |
| 1318 | for (int i=0;i<_servletMappings.length;i++) |
| 1319 | { |
| 1320 | ServletHolder servlet_holder = (ServletHolder)_servletNameMap.get(_servletMappings[i].getServletName()); |
| 1321 | if (servlet_holder==null) |
| 1322 | throw new IllegalStateException("No such servlet: "+_servletMappings[i].getServletName()); |
| 1323 | else if (servlet_holder.isEnabled() && _servletMappings[i].getPathSpecs()!=null) |
| 1324 | { |
| 1325 | String[] pathSpecs = _servletMappings[i].getPathSpecs(); |
| 1326 | for (int j=0;j<pathSpecs.length;j++) |
| 1327 | if (pathSpecs[j]!=null) |
| 1328 | pm.put(pathSpecs[j],servlet_holder); |
| 1329 | } |
| 1330 | } |
| 1331 | |
| 1332 | _servletPathMap=pm; |
| 1333 | } |
| 1334 | |
| 1335 | // flush filter chain cache |
| 1336 | if (_chainCache!=null) |
| 1337 | { |
| 1338 | for (int i=_chainCache.length;i-->0;) |
| 1339 | { |
| 1340 | if (_chainCache[i]!=null) |
| 1341 | _chainCache[i].clear(); |
| 1342 | } |
| 1343 | } |
| 1344 | |
| 1345 | if (LOG.isDebugEnabled()) |
| 1346 | { |
| 1347 | LOG.debug("filterNameMap="+_filterNameMap); |
| 1348 | LOG.debug("pathFilters="+_filterPathMappings); |
| 1349 | LOG.debug("servletFilterMap="+_filterNameMappings); |
| 1350 | LOG.debug("servletPathMap="+_servletPathMap); |
| 1351 | LOG.debug("servletNameMap="+_servletNameMap); |
| 1352 | } |
| 1353 | |
| 1354 | try |
| 1355 | { |
| 1356 | if (_contextHandler!=null && _contextHandler.isStarted() || _contextHandler==null && isStarted()) |
| 1357 | initialize(); |
| 1358 | } |
| 1359 | catch (Exception e) |
| 1360 | { |
| 1361 | throw new RuntimeException(e); |
| 1362 | } |
| 1363 | } |
| 1364 | |
| 1365 | /* ------------------------------------------------------------ */ |
| 1366 | protected void notFound(HttpServletRequest request, |
| 1367 | HttpServletResponse response) |
| 1368 | throws IOException |
| 1369 | { |
| 1370 | if(LOG.isDebugEnabled()) |
| 1371 | LOG.debug("Not Found "+request.getRequestURI()); |
| 1372 | //Override to send an error back, eg with: response.sendError(HttpServletResponse.SC_NOT_FOUND); |
| 1373 | } |
| 1374 | |
| 1375 | /* ------------------------------------------------------------ */ |
| 1376 | /** |
| 1377 | * @param filterChainsCached The filterChainsCached to set. |
| 1378 | */ |
| 1379 | public void setFilterChainsCached(boolean filterChainsCached) |
| 1380 | { |
| 1381 | _filterChainsCached = filterChainsCached; |
| 1382 | } |
| 1383 | |
| 1384 | /* ------------------------------------------------------------ */ |
| 1385 | /** |
| 1386 | * @param filterMappings The filterMappings to set. |
| 1387 | */ |
| 1388 | public void setFilterMappings(FilterMapping[] filterMappings) |
| 1389 | { |
| 1390 | if (getServer()!=null) |
| 1391 | getServer().getContainer().update(this,_filterMappings,filterMappings,"filterMapping",true); |
| 1392 | _filterMappings = filterMappings; |
| 1393 | updateMappings(); |
| 1394 | invalidateChainsCache(); |
| 1395 | } |
| 1396 | |
| 1397 | /* ------------------------------------------------------------ */ |
| 1398 | public synchronized void setFilters(FilterHolder[] holders) |
| 1399 | { |
| 1400 | if (getServer()!=null) |
| 1401 | getServer().getContainer().update(this,_filters,holders,"filter",true); |
| 1402 | _filters=holders; |
| 1403 | updateNameMappings(); |
| 1404 | invalidateChainsCache(); |
| 1405 | } |
| 1406 | |
| 1407 | /* ------------------------------------------------------------ */ |
| 1408 | /** |
| 1409 | * @param servletMappings The servletMappings to set. |
| 1410 | */ |
| 1411 | public void setServletMappings(ServletMapping[] servletMappings) |
| 1412 | { |
| 1413 | if (getServer()!=null) |
| 1414 | getServer().getContainer().update(this,_servletMappings,servletMappings,"servletMapping",true); |
| 1415 | _servletMappings = servletMappings; |
| 1416 | updateMappings(); |
| 1417 | invalidateChainsCache(); |
| 1418 | } |
| 1419 | |
| 1420 | /* ------------------------------------------------------------ */ |
| 1421 | /** Set Servlets. |
| 1422 | * @param holders Array of servletsto define |
| 1423 | */ |
| 1424 | public synchronized void setServlets(ServletHolder[] holders) |
| 1425 | { |
| 1426 | if (getServer()!=null) |
| 1427 | getServer().getContainer().update(this,_servlets,holders,"servlet",true); |
| 1428 | _servlets=holders; |
| 1429 | updateNameMappings(); |
| 1430 | invalidateChainsCache(); |
| 1431 | } |
| 1432 | |
| 1433 | /* ------------------------------------------------------------ */ |
| 1434 | /* ------------------------------------------------------------ */ |
| 1435 | private class CachedChain implements FilterChain |
| 1436 | { |
| 1437 | FilterHolder _filterHolder; |
| 1438 | CachedChain _next; |
| 1439 | ServletHolder _servletHolder; |
| 1440 | |
| 1441 | /* ------------------------------------------------------------ */ |
| 1442 | CachedChain(Object filters, ServletHolder servletHolder) |
| 1443 | { |
| 1444 | if (LazyList.size(filters)>0) |
| 1445 | { |
| 1446 | _filterHolder=(FilterHolder)LazyList.get(filters, 0); |
| 1447 | filters=LazyList.remove(filters,0); |
| 1448 | _next=new CachedChain(filters,servletHolder); |
| 1449 | } |
| 1450 | else |
| 1451 | _servletHolder=servletHolder; |
| 1452 | } |
| 1453 | |
| 1454 | /* ------------------------------------------------------------ */ |
| 1455 | public void doFilter(ServletRequest request, ServletResponse response) |
| 1456 | throws IOException, ServletException |
| 1457 | { |
| 1458 | final Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest(); |
| 1459 | |
| 1460 | // pass to next filter |
| 1461 | if (_filterHolder!=null) |
| 1462 | { |
| 1463 | if (LOG.isDebugEnabled()) |
| 1464 | LOG.debug("call filter " + _filterHolder); |
| 1465 | Filter filter= _filterHolder.getFilter(); |
| 1466 | if (_filterHolder.isAsyncSupported()) |
| 1467 | filter.doFilter(request, response, _next); |
| 1468 | else |
| 1469 | { |
| 1470 | final boolean suspendable=baseRequest.isAsyncSupported(); |
| 1471 | if (suspendable) |
| 1472 | { |
| 1473 | try |
| 1474 | { |
| 1475 | baseRequest.setAsyncSupported(false); |
| 1476 | filter.doFilter(request, response, _next); |
| 1477 | } |
| 1478 | finally |
| 1479 | { |
| 1480 | baseRequest.setAsyncSupported(true); |
| 1481 | } |
| 1482 | } |
| 1483 | else |
| 1484 | filter.doFilter(request, response, _next); |
| 1485 | } |
| 1486 | return; |
| 1487 | } |
| 1488 | |
| 1489 | // Call servlet |
| 1490 | |
| 1491 | HttpServletRequest srequest = (HttpServletRequest)request; |
| 1492 | if (_servletHolder != null) |
| 1493 | { |
| 1494 | if (LOG.isDebugEnabled()) |
| 1495 | LOG.debug("call servlet " + _servletHolder); |
| 1496 | _servletHolder.handle(baseRequest,request, response); |
| 1497 | } |
| 1498 | else if (getHandler()==null) |
| 1499 | notFound(srequest, (HttpServletResponse)response); |
| 1500 | else |
| 1501 | nextHandle(URIUtil.addPaths(srequest.getServletPath(),srequest.getPathInfo()), |
| 1502 | baseRequest,srequest,(HttpServletResponse)response); |
| 1503 | |
| 1504 | } |
| 1505 | |
| 1506 | public String toString() |
| 1507 | { |
| 1508 | if (_filterHolder!=null) |
| 1509 | return _filterHolder+"->"+_next.toString(); |
| 1510 | if (_servletHolder!=null) |
| 1511 | return _servletHolder.toString(); |
| 1512 | return "null"; |
| 1513 | } |
| 1514 | } |
| 1515 | |
| 1516 | /* ------------------------------------------------------------ */ |
| 1517 | /* ------------------------------------------------------------ */ |
| 1518 | private class Chain implements FilterChain |
| 1519 | { |
| 1520 | final Request _baseRequest; |
| 1521 | final Object _chain; |
| 1522 | final ServletHolder _servletHolder; |
| 1523 | int _filter= 0; |
| 1524 | |
| 1525 | /* ------------------------------------------------------------ */ |
| 1526 | Chain(Request baseRequest, Object filters, ServletHolder servletHolder) |
| 1527 | { |
| 1528 | _baseRequest=baseRequest; |
| 1529 | _chain= filters; |
| 1530 | _servletHolder= servletHolder; |
| 1531 | } |
| 1532 | |
| 1533 | /* ------------------------------------------------------------ */ |
| 1534 | public void doFilter(ServletRequest request, ServletResponse response) |
| 1535 | throws IOException, ServletException |
| 1536 | { |
| 1537 | if (LOG.isDebugEnabled()) |
| 1538 | LOG.debug("doFilter " + _filter); |
| 1539 | |
| 1540 | // pass to next filter |
| 1541 | if (_filter < LazyList.size(_chain)) |
| 1542 | { |
| 1543 | FilterHolder holder= (FilterHolder)LazyList.get(_chain, _filter++); |
| 1544 | if (LOG.isDebugEnabled()) |
| 1545 | LOG.debug("call filter " + holder); |
| 1546 | Filter filter= holder.getFilter(); |
| 1547 | |
| 1548 | if (holder.isAsyncSupported() || !_baseRequest.isAsyncSupported()) |
| 1549 | { |
| 1550 | filter.doFilter(request, response, this); |
| 1551 | } |
| 1552 | else |
| 1553 | { |
| 1554 | try |
| 1555 | { |
| 1556 | _baseRequest.setAsyncSupported(false); |
| 1557 | filter.doFilter(request, response, this); |
| 1558 | } |
| 1559 | finally |
| 1560 | { |
| 1561 | _baseRequest.setAsyncSupported(true); |
| 1562 | } |
| 1563 | } |
| 1564 | |
| 1565 | return; |
| 1566 | } |
| 1567 | |
| 1568 | // Call servlet |
| 1569 | HttpServletRequest srequest = (HttpServletRequest)request; |
| 1570 | if (_servletHolder != null) |
| 1571 | { |
| 1572 | if (LOG.isDebugEnabled()) |
| 1573 | LOG.debug("call servlet " + _servletHolder); |
| 1574 | _servletHolder.handle(_baseRequest,request, response); |
| 1575 | } |
| 1576 | else if (getHandler()==null) |
| 1577 | notFound(srequest, (HttpServletResponse)response); |
| 1578 | else |
| 1579 | { |
| 1580 | Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest(); |
| 1581 | nextHandle(URIUtil.addPaths(srequest.getServletPath(),srequest.getPathInfo()), |
| 1582 | baseRequest,srequest,(HttpServletResponse)response); |
| 1583 | } |
| 1584 | } |
| 1585 | |
| 1586 | /* ------------------------------------------------------------ */ |
| 1587 | public String toString() |
| 1588 | { |
| 1589 | StringBuilder b = new StringBuilder(); |
| 1590 | for (int i=0; i<LazyList.size(_chain);i++) |
| 1591 | { |
| 1592 | Object o=LazyList.get(_chain, i); |
| 1593 | b.append(o.toString()); |
| 1594 | b.append("->"); |
| 1595 | } |
| 1596 | b.append(_servletHolder); |
| 1597 | return b.toString(); |
| 1598 | } |
| 1599 | } |
| 1600 | |
| 1601 | /* ------------------------------------------------------------ */ |
| 1602 | /** |
| 1603 | * @return The maximum entries in a filter chain cache. |
| 1604 | */ |
| 1605 | public int getMaxFilterChainsCacheSize() |
| 1606 | { |
| 1607 | return _maxFilterChainsCacheSize; |
| 1608 | } |
| 1609 | |
| 1610 | /* ------------------------------------------------------------ */ |
| 1611 | /** Set the maximum filter chain cache size. |
| 1612 | * Filter chains are cached if {@link #isFilterChainsCached()} is true. If the max cache size |
| 1613 | * is greater than zero, then the cache is flushed whenever it grows to be this size. |
| 1614 | * |
| 1615 | * @param maxFilterChainsCacheSize the maximum number of entries in a filter chain cache. |
| 1616 | */ |
| 1617 | public void setMaxFilterChainsCacheSize(int maxFilterChainsCacheSize) |
| 1618 | { |
| 1619 | _maxFilterChainsCacheSize = maxFilterChainsCacheSize; |
| 1620 | } |
| 1621 | |
| 1622 | /* ------------------------------------------------------------ */ |
| 1623 | void destroyServlet(Servlet servlet) |
| 1624 | { |
| 1625 | if (_contextHandler!=null) |
| 1626 | _contextHandler.destroyServlet(servlet); |
| 1627 | } |
| 1628 | |
| 1629 | /* ------------------------------------------------------------ */ |
| 1630 | void destroyFilter(Filter filter) |
| 1631 | { |
| 1632 | if (_contextHandler!=null) |
| 1633 | _contextHandler.destroyFilter(filter); |
| 1634 | } |
| 1635 | |
| 1636 | /* ------------------------------------------------------------ */ |
| 1637 | @Override |
| 1638 | public void dump(Appendable out,String indent) throws IOException |
| 1639 | { |
| 1640 | super.dumpThis(out); |
| 1641 | dump(out,indent, |
| 1642 | TypeUtil.asList(getHandlers()), |
| 1643 | getBeans(), |
| 1644 | TypeUtil.asList(getFilterMappings()), |
| 1645 | TypeUtil.asList(getFilters()), |
| 1646 | TypeUtil.asList(getServletMappings()), |
| 1647 | TypeUtil.asList(getServlets())); |
| 1648 | } |
| 1649 | } |