]> WPIA git - gigi.git/commitdiff
Importing upstream Jetty jetty-9.2.1.v20140609
authorFelix Dörre <felix@dogcraft.de>
Thu, 19 Jun 2014 17:22:50 +0000 (19:22 +0200)
committerBenny Baumann <BenBE@geshi.org>
Fri, 20 Jun 2014 22:00:46 +0000 (00:00 +0200)
Imported packages:
* http
* io
* security
* server
* servlet
* util

Removed for compilance:
* Packages:
  - org.eclipse.jetty.server.session.jmx
  - org.eclipse.jetty.server.handler.jmx
  - org.eclipse.jetty.server.jmx
  - org.eclipse.jetty.servlet.jmx

* Classes:
  - org.eclipse.jetty.util.log.JettyAwareLogger
  - org.eclipse.jetty.util.log.Slf4jLog
  - org.eclipse.jetty.server.Slf4jRequestLog

328 files changed:
.classpath
lib/jetty/org/eclipse/jetty/http/DateGenerator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/DateParser.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpContent.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpCookie.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpField.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpFields.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpGenerator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpHeader.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpHeaderValue.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpMethod.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpParser.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpScheme.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpStatus.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpTester.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpTokens.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpURI.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpVersion.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/MimeTypes.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/PathMap.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/encoding.properties [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/mime.properties [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/useragents [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/AbstractConnection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/AbstractEndPoint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ArrayByteBufferPool.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ByteArrayEndPoint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ByteBufferPool.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ChannelEndPoint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ClientConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/Connection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/EndPoint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/EofException.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/FillInterest.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/IdleTimeout.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/MappedByteBufferPool.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/NetworkTrafficListener.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/RuntimeIOException.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/SelectChannelEndPoint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/SelectorManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/UncheckedPrintWriter.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/WriteFlusher.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/WriterOutputStream.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ssl/SslConnection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ssl/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/AbstractUserAuthentication.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/Authenticator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/ConstraintAware.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/ConstraintMapping.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/ConstraintSecurityHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/CrossContextPsuedoSession.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/DefaultIdentityService.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/DefaultUserIdentity.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/HashLoginService.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/IdentityService.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/JDBCLoginService.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/LoginService.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/MappedLoginService.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/PropertyUserStore.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/RoleInfo.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/RoleRunAsToken.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/RunAsToken.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/SecurityHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/ServerAuthException.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/SpnegoLoginService.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/SpnegoUserIdentity.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/SpnegoUserPrincipal.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/UserAuthentication.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/UserDataConstraint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/BasicAuthenticator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/DeferredAuthentication.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/DigestAuthenticator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/FormAuthenticator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/LoginAuthenticator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/LoginCallback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/SessionAuthentication.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/AbstractConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/AbstractConnector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/AbstractNCSARequestLog.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/AbstractNetworkConnector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/AsyncContextEvent.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/AsyncContextState.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/AsyncNCSARequestLog.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Authentication.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ClassLoaderDump.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Connector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ConnectorStatistics.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/CookieCutter.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Dispatcher.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/EncodingHttpWriter.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ForwardedRequestCustomizer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Handler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HandlerContainer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HostHeaderCustomizer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpChannel.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpChannelState.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpConfiguration.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpConnection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpInput.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpInputOverHTTP.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpOutput.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpTransport.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpWriter.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/InclusiveByteRange.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Iso88591HttpWriter.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/LocalConnector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/LowResourceMonitor.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/NCSARequestLog.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/NetworkConnector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/NetworkTrafficServerConnector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/QueuedHttpInput.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/QuietServletException.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Request.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/RequestLog.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ResourceCache.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Response.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/SecureRequestCustomizer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Server.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ServerConnector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ServletRequestHttpWrapper.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ServletResponseHttpWrapper.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/SessionIdManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/SessionManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ShutdownMonitor.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/SslConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/UserIdentity.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Utf8HttpWriter.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/AbstractHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/ContextHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/ContextHandlerCollection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/DebugHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/DefaultHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/ErrorHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/HandlerCollection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/HandlerList.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/HandlerWrapper.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/HotSwapHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/IPAccessHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/MovedContextHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/RequestLogHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/ResourceHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/ScopedHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/ShutdownHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/StatisticsHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/nio/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/AbstractSession.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/AbstractSessionIdManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/AbstractSessionManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/HashSessionIdManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/HashSessionManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/HashedSession.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/JDBCSessionIdManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/JDBCSessionManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/MemSession.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/SessionHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/BaseHolder.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/DefaultServlet.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/FilterHolder.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/FilterMapping.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/Holder.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/Invoker.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/ListenerHolder.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/NoJspServlet.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/ServletContextHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/ServletHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/ServletHolder.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/ServletMapping.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/ServletTester.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/StatisticsServlet.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/listener/ELContextCleaner.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/listener/IntrospectorCleaner.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/listener/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/AbstractTrie.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ArrayQueue.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ArrayTernaryTrie.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ArrayTrie.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ArrayUtil.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Atomics.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Attributes.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/AttributesMap.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/B64Code.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/BlockingArrayQueue.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/BlockingCallback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/BufferUtil.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ByteArrayISO8859Writer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ByteArrayOutputStream2.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Callback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/CompletableCallback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ConcurrentArrayQueue.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ConcurrentHashSet.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/DateCache.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Fields.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ForkInvoker.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/FutureCallback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/FuturePromise.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/HostMap.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/HttpCookieStore.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/IO.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/IPAddressMap.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/IntrospectionUtil.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/IteratingCallback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/IteratingNestedCallback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Jetty.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/LazyList.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/LeakDetector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Loader.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/MemoryUtils.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/MultiException.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/MultiMap.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/MultiPartInputStreamParser.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/MultiPartOutputStream.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/MultiPartWriter.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/PatternMatcher.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Promise.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/QuotedStringTokenizer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ReadLineInputStream.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/RolloverFileOutputStream.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Scanner.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/SharedBlockingCallback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/SocketAddressResolver.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/StringUtil.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/TreeTrie.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Trie.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/TypeUtil.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/URIUtil.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/UrlEncoded.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Utf8Appendable.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Utf8LineParser.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Utf8StringBuffer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Utf8StringBuilder.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/annotation/ManagedAttribute.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/annotation/ManagedObject.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/annotation/ManagedOperation.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/annotation/Name.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/annotation/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/AbstractLifeCycle.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/Container.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/ContainerLifeCycle.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/Destroyable.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/Dumpable.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/FileDestroyable.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/Graceful.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/LifeCycle.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/AbstractLogger.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/JavaUtilLog.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/Log.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/Logger.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/LoggerLog.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/StacklessLogging.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/StdErrLog.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/AbstractLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/DOMLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/Java2DLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/LDAPLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/LoginConfigurationLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/SecurityProviderLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/BadResource.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/EmptyResource.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/FileResource.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/JarFileResource.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/JarResource.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/Resource.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/ResourceCollection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/ResourceFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/URLResource.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/security/CertificateUtils.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/security/CertificateValidator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/security/Constraint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/security/Credential.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/security/Password.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/security/UnixCrypt.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/security/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ssl/SslContextFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ssl/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/statistic/CounterStatistic.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/statistic/SampleStatistic.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/statistic/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/ExecutorThreadPool.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/NonBlockingThread.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/QueuedThreadPool.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/Scheduler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/ShutdownThread.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/ThreadPool.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/TimerScheduler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/package-info.java [new file with mode: 0644]

index 342147deed9492586ed3c076b286d2fe31402d94..0fba86b0775db39f7ce4a9047b50948e78c78c21 100644 (file)
@@ -2,6 +2,7 @@
 <classpath>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="src" path="lib/servlet-api"/>
+       <classpathentry kind="src" path="lib/jetty"/>
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
        <classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/lib/jetty/org/eclipse/jetty/http/DateGenerator.java b/lib/jetty/org/eclipse/jetty/http/DateGenerator.java
new file mode 100644 (file)
index 0000000..3ecaad8
--- /dev/null
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.eclipse.jetty.util.StringUtil;
+
+/**
+ * ThreadLocal Date formatters for HTTP style dates.
+ *
+ */
+public class DateGenerator
+{
+    private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
+    static
+    {
+        __GMT.setID("GMT");
+    }
+    
+    static final String[] DAYS =
+        { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+    static final String[] MONTHS =
+        { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
+
+
+    private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>()
+    {
+        @Override
+        protected DateGenerator initialValue()
+        {
+            return new DateGenerator();
+        }
+    };
+
+
+    public final static String __01Jan1970=DateGenerator.formatDate(0);
+    
+    /**
+     * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
+     */
+    public static String formatDate(long date)
+    {
+        return __dateGenerator.get().doFormatDate(date);
+    }
+
+    /**
+     * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
+     */
+    public static void formatCookieDate(StringBuilder buf, long date)
+    {
+        __dateGenerator.get().doFormatCookieDate(buf,date);
+    }
+
+    /**
+     * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
+     */
+    public static String formatCookieDate(long date)
+    {
+        StringBuilder buf = new StringBuilder(28);
+        formatCookieDate(buf, date);
+        return buf.toString();
+    }
+    
+    private final StringBuilder buf = new StringBuilder(32);
+    private final GregorianCalendar gc = new GregorianCalendar(__GMT);
+
+    /**
+     * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
+     */
+    public String doFormatDate(long date)
+    {
+        buf.setLength(0);
+        gc.setTimeInMillis(date);
+
+        int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
+        int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
+        int month = gc.get(Calendar.MONTH);
+        int year = gc.get(Calendar.YEAR);
+        int century = year / 100;
+        year = year % 100;
+
+        int hours = gc.get(Calendar.HOUR_OF_DAY);
+        int minutes = gc.get(Calendar.MINUTE);
+        int seconds = gc.get(Calendar.SECOND);
+
+        buf.append(DAYS[day_of_week]);
+        buf.append(',');
+        buf.append(' ');
+        StringUtil.append2digits(buf, day_of_month);
+
+        buf.append(' ');
+        buf.append(MONTHS[month]);
+        buf.append(' ');
+        StringUtil.append2digits(buf, century);
+        StringUtil.append2digits(buf, year);
+
+        buf.append(' ');
+        StringUtil.append2digits(buf, hours);
+        buf.append(':');
+        StringUtil.append2digits(buf, minutes);
+        buf.append(':');
+        StringUtil.append2digits(buf, seconds);
+        buf.append(" GMT");
+        return buf.toString();
+    }
+
+    /**
+     * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
+     */
+    public void doFormatCookieDate(StringBuilder buf, long date)
+    {
+        gc.setTimeInMillis(date);
+
+        int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
+        int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
+        int month = gc.get(Calendar.MONTH);
+        int year = gc.get(Calendar.YEAR);
+        year = year % 10000;
+
+        int epoch = (int) ((date / 1000) % (60 * 60 * 24));
+        int seconds = epoch % 60;
+        epoch = epoch / 60;
+        int minutes = epoch % 60;
+        int hours = epoch / 60;
+
+        buf.append(DAYS[day_of_week]);
+        buf.append(',');
+        buf.append(' ');
+        StringUtil.append2digits(buf, day_of_month);
+
+        buf.append('-');
+        buf.append(MONTHS[month]);
+        buf.append('-');
+        StringUtil.append2digits(buf, year/100);
+        StringUtil.append2digits(buf, year%100);
+
+        buf.append(' ');
+        StringUtil.append2digits(buf, hours);
+        buf.append(':');
+        StringUtil.append2digits(buf, minutes);
+        buf.append(':');
+        StringUtil.append2digits(buf, seconds);
+        buf.append(" GMT");
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/DateParser.java b/lib/jetty/org/eclipse/jetty/http/DateParser.java
new file mode 100644 (file)
index 0000000..1ede4ce
--- /dev/null
@@ -0,0 +1,109 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * ThreadLocal data parsers for HTTP style dates
+ *
+ */
+class DateParser
+{
+    private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
+    static
+    {
+        __GMT.setID("GMT");
+    }
+    
+    final static String __dateReceiveFmt[] =
+    {
+        "EEE, dd MMM yyyy HH:mm:ss zzz",
+        "EEE, dd-MMM-yy HH:mm:ss",
+        "EEE MMM dd HH:mm:ss yyyy",
+
+        "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz",
+        "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss",
+        "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz",
+        "dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz",
+        "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",
+        "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz",
+        "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
+    };
+
+    public static long parseDate(String date)
+    {
+        return __dateParser.get().parse(date);
+    }
+
+    private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>()
+    {
+        @Override
+        protected DateParser initialValue()
+        {
+            return new DateParser();
+        }
+    };
+    
+    final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length];
+
+    private long parse(final String dateVal)
+    {
+        for (int i = 0; i < _dateReceive.length; i++)
+        {
+            if (_dateReceive[i] == null)
+            {
+                _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
+                _dateReceive[i].setTimeZone(__GMT);
+            }
+
+            try
+            {
+                Date date = (Date) _dateReceive[i].parseObject(dateVal);
+                return date.getTime();
+            }
+            catch (java.lang.Exception e)
+            {
+                // LOG.ignore(e);
+            }
+        }
+
+        if (dateVal.endsWith(" GMT"))
+        {
+            final String val = dateVal.substring(0, dateVal.length() - 4);
+
+            for (SimpleDateFormat element : _dateReceive)
+            {
+                try
+                {
+                    Date date = (Date) element.parseObject(val);
+                    return date.getTime();
+                }
+                catch (java.lang.Exception e)
+                {
+                    // LOG.ignore(e);
+                }
+            }
+        }
+        return -1;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpContent.java b/lib/jetty/org/eclipse/jetty/http/HttpContent.java
new file mode 100644 (file)
index 0000000..ebae56d
--- /dev/null
@@ -0,0 +1,172 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/** HttpContent.
+ * 
+ *
+ */
+public interface HttpContent
+{
+    String getContentType();
+    String getLastModified();
+    ByteBuffer getIndirectBuffer();
+    ByteBuffer getDirectBuffer();
+    String getETag();
+    Resource getResource();
+    long getContentLength();
+    InputStream getInputStream() throws IOException;
+    ReadableByteChannel getReadableByteChannel() throws IOException;
+    void release();
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public class ResourceAsHttpContent implements HttpContent
+    {
+        final Resource _resource;
+        final String _mimeType;
+        final int _maxBuffer;
+        final String _etag;
+
+        /* ------------------------------------------------------------ */
+        public ResourceAsHttpContent(final Resource resource, final String mimeType)
+        {
+            this(resource,mimeType,-1,false);
+        }
+
+        /* ------------------------------------------------------------ */
+        public ResourceAsHttpContent(final Resource resource, final String mimeType, int maxBuffer)
+        {
+            this(resource,mimeType,maxBuffer,false);
+        }
+
+        /* ------------------------------------------------------------ */
+        public ResourceAsHttpContent(final Resource resource, final String mimeType, boolean etag)
+        {
+            this(resource,mimeType,-1,etag);
+        }
+
+        /* ------------------------------------------------------------ */
+        public ResourceAsHttpContent(final Resource resource, final String mimeType, int maxBuffer, boolean etag)
+        {
+            _resource=resource;
+            _mimeType=mimeType;
+            _maxBuffer=maxBuffer;
+            _etag=etag?resource.getWeakETag():null;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String getContentType()
+        {
+            return _mimeType;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String getLastModified()
+        {
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public ByteBuffer getDirectBuffer()
+        {
+            if (_resource.length()<=0 || _maxBuffer<_resource.length())
+                return null;
+            try
+            {
+                return BufferUtil.toBuffer(_resource,true);
+            }
+            catch(IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+        
+        /* ------------------------------------------------------------ */
+        @Override
+        public String getETag()
+        {
+            return _etag;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public ByteBuffer getIndirectBuffer()
+        {
+            if (_resource.length()<=0 || _maxBuffer<_resource.length())
+                return null;
+            try
+            {
+                return BufferUtil.toBuffer(_resource,false);
+            }
+            catch(IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public long getContentLength()
+        {
+            return _resource.length();
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public InputStream getInputStream() throws IOException
+        {
+            return _resource.getInputStream();
+        }
+        
+        /* ------------------------------------------------------------ */
+        @Override
+        public ReadableByteChannel getReadableByteChannel() throws IOException
+        {
+            return _resource.getReadableByteChannel();
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public Resource getResource()
+        {
+            return _resource;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public void release()
+        {
+            _resource.close();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpCookie.java b/lib/jetty/org/eclipse/jetty/http/HttpCookie.java
new file mode 100644 (file)
index 0000000..1a2426b
--- /dev/null
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.concurrent.TimeUnit;
+
+public class HttpCookie
+{
+    private final String _name;
+    private final String _value;
+    private final String _comment;
+    private final String _domain;
+    private final long _maxAge;
+    private final String _path;
+    private final boolean _secure;
+    private final int _version;
+    private final boolean _httpOnly;
+    private final long _expiration;
+
+    public HttpCookie(String name, String value)
+    {
+        this(name, value, -1);
+    }
+
+    public HttpCookie(String name, String value, String domain, String path)
+    {
+        this(name, value, domain, path, -1, false, false);
+    }
+
+    public HttpCookie(String name, String value, long maxAge)
+    {
+        this(name, value, null, null, maxAge, false, false);
+    }
+
+    public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure)
+    {
+        this(name, value, domain, path, maxAge, httpOnly, secure, null, 0);
+    }
+
+    public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version)
+    {
+        _name = name;
+        _value = value;
+        _domain = domain;
+        _path = path;
+        _maxAge = maxAge;
+        _httpOnly = httpOnly;
+        _secure = secure;
+        _comment = comment;
+        _version = version;
+        _expiration = maxAge < 0 ? -1 : System.nanoTime() + TimeUnit.SECONDS.toNanos(maxAge);
+    }
+
+    /**
+     * @return the cookie name
+     */
+    public String getName()
+    {
+        return _name;
+    }
+
+    /**
+     * @return the cookie value
+     */
+    public String getValue()
+    {
+        return _value;
+    }
+
+    /**
+     * @return the cookie comment
+     */
+    public String getComment()
+    {
+        return _comment;
+    }
+
+    /**
+     * @return the cookie domain
+     */
+    public String getDomain()
+    {
+        return _domain;
+    }
+
+    /**
+     * @return the cookie max age in seconds
+     */
+    public long getMaxAge()
+    {
+        return _maxAge;
+    }
+
+    /**
+     * @return the cookie path
+     */
+    public String getPath()
+    {
+        return _path;
+    }
+
+    /**
+     * @return whether the cookie is valid for secure domains
+     */
+    public boolean isSecure()
+    {
+        return _secure;
+    }
+
+    /**
+     * @return the cookie version
+     */
+    public int getVersion()
+    {
+        return _version;
+    }
+
+    /**
+     * @return whether the cookie is valid for the http protocol only
+     */
+    public boolean isHttpOnly()
+    {
+        return _httpOnly;
+    }
+
+    /**
+     * @param timeNanos the time to check for cookie expiration, in nanoseconds
+     * @return whether the cookie is expired by the given time
+     */
+    public boolean isExpired(long timeNanos)
+    {
+        return _expiration >= 0 && timeNanos >= _expiration;
+    }
+
+    /**
+     * @return a string representation of this cookie
+     */
+    public String asString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append(getName()).append("=").append(getValue());
+        if (getDomain() != null)
+            builder.append(";$Domain=").append(getDomain());
+        if (getPath() != null)
+            builder.append(";$Path=").append(getPath());
+        return builder.toString();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpField.java b/lib/jetty/org/eclipse/jetty/http/HttpField.java
new file mode 100644 (file)
index 0000000..50f29b1
--- /dev/null
@@ -0,0 +1,89 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+
+/* ------------------------------------------------------------ */
+/** A HTTP Field
+ */
+public class HttpField
+{
+    private final HttpHeader _header;
+    private final String _name;
+    private final String _value;
+        
+    public HttpField(HttpHeader header, String name, String value)
+    {
+        _header = header;
+        _name = name;
+        _value = value;
+    }  
+    
+    public HttpField(HttpHeader header, String value)
+    {
+        this(header,header.asString(),value);
+    }
+    
+    public HttpField(HttpHeader header, HttpHeaderValue value)
+    {
+        this(header,header.asString(),value.asString());
+    }
+    
+    public HttpField(String name, String value)
+    {
+        this(HttpHeader.CACHE.get(name),name,value);
+    }
+
+    public HttpHeader getHeader()
+    {
+        return _header;
+    }
+
+    public String getName()
+    {
+        return _name;
+    }
+
+    public String getValue()
+    {
+        return _value;
+    }
+    
+    @Override
+    public String toString()
+    {
+        String v=getValue();
+        return getName() + ": " + (v==null?"":v);
+    }
+
+    public boolean isSame(HttpField field)
+    {
+        if (field==null)
+            return false;
+        if (field==this)
+            return true;
+        if (_header!=null && _header==field.getHeader())
+            return true;
+        if (_name.equalsIgnoreCase(field.getName()))
+            return true;
+        return false;
+    }
+    
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpFields.java b/lib/jetty/org/eclipse/jetty/http/HttpFields.java
new file mode 100644 (file)
index 0000000..d474227
--- /dev/null
@@ -0,0 +1,784 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * HTTP Fields. A collection of HTTP header and or Trailer fields.
+ *
+ * <p>This class is not synchronized as it is expected that modifications will only be performed by a
+ * single thread.
+ * 
+ * <p>The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
+ *
+ */
+public class HttpFields implements Iterable<HttpField>
+{
+    private static final Logger LOG = Log.getLogger(HttpFields.class);
+    private final static Pattern __splitter = Pattern.compile("\\s*,\\s*");     
+    public final static String __separators = ", \t";
+
+    private final ArrayList<HttpField> _fields = new ArrayList<>(20);
+
+    /**
+     * Constructor.
+     */
+    public HttpFields()
+    {
+    }
+
+    /**
+     * Get Collection of header names.
+     */
+    public Collection<String> getFieldNamesCollection()
+    {
+        final Set<String> list = new HashSet<>(_fields.size());
+        for (HttpField f : _fields)
+        {
+            if (f!=null)
+                list.add(f.getName());
+        }
+        return list;
+    }
+
+    /**
+     * Get enumeration of header _names. Returns an enumeration of strings representing the header
+     * _names for this request.
+     */
+    public Enumeration<String> getFieldNames()
+    {
+        return Collections.enumeration(getFieldNamesCollection());
+    }
+
+    public int size()
+    {
+        return _fields.size();
+    }
+
+    /**
+     * Get a Field by index.
+     * @return A Field value or null if the Field value has not been set
+     *
+     */
+    public HttpField getField(int i)
+    {
+        return _fields.get(i);
+    }
+
+    @Override
+    public Iterator<HttpField> iterator()
+    {
+        return _fields.iterator();
+    }
+
+    public HttpField getField(HttpHeader header)
+    {
+        for (int i=0;i<_fields.size();i++)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getHeader()==header)
+                return f;
+        }
+        return null;
+    }
+
+    public HttpField getField(String name)
+    {
+        for (int i=0;i<_fields.size();i++)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getName().equalsIgnoreCase(name))
+                return f;
+        }
+        return null;
+    }
+    
+    public boolean contains(HttpHeader header, String value)
+    {
+        for (int i=0;i<_fields.size();i++)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getHeader()==header && contains(f,value))
+                return true;
+        }
+        return false;
+    }
+    
+    public boolean contains(String name, String value)
+    {
+        for (int i=0;i<_fields.size();i++)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getName().equalsIgnoreCase(name) && contains(f,value))
+                return true;
+        }
+        return false;
+    }
+    
+    private boolean contains(HttpField field,String value)
+    {
+        String v = field.getValue();
+        if (v==null)
+            return false;
+
+        if (value.equalsIgnoreCase(v))
+            return true;
+
+        String[] split = __splitter.split(v);
+        for (int i = 0; split!=null && i < split.length; i++) 
+        {
+            if (value.equals(split[i]))
+                return true;
+        }
+
+        return false;
+    }
+    
+    public boolean containsKey(String name)
+    {
+        for (int i=0;i<_fields.size();i++)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getName().equalsIgnoreCase(name))
+                return true;
+        }
+        return false;
+    }
+
+    public String getStringField(HttpHeader header)
+    {
+        return getStringField(header.asString());
+    }
+
+    public String get(HttpHeader header)
+    {
+        return getStringField(header.asString());
+    }
+
+    public String get(String header)
+    {
+        return getStringField(header);
+    }
+
+    /**
+     * @return the value of a field, or null if not found. For multiple fields of the same name,
+     *         only the first is returned.
+     * @param name the case-insensitive field name
+     */
+    public String getStringField(String name)
+    {
+        HttpField field = getField(name);
+        return field==null?null:field.getValue();
+    }
+
+    /**
+     * Get multi headers
+     *
+     * @return List the values
+     * @param name the case-insensitive field name
+     */
+    public List<String> getValuesList(String name)
+    {
+        final List<String> list = new ArrayList<>();
+        for (HttpField f : _fields)
+            if (f.getName().equalsIgnoreCase(name))
+                list.add(f.getValue());
+        return list;
+    }
+
+    /**
+     * Get multi headers
+     *
+     * @return Enumeration of the values
+     * @param name the case-insensitive field name
+     */
+    public Enumeration<String> getValues(final String name)
+    {
+        for (int i=0;i<_fields.size();i++)
+        {
+            final HttpField f = _fields.get(i);
+            
+            if (f.getName().equalsIgnoreCase(name) && f.getValue()!=null)
+            {
+                final int first=i;
+                return new Enumeration<String>()
+                {
+                    HttpField field=f;
+                    int i = first+1;
+
+                    @Override
+                    public boolean hasMoreElements()
+                    {
+                        if (field==null)
+                        {
+                            while (i<_fields.size()) 
+                            {
+                                field=_fields.get(i++);
+                                if (field.getName().equalsIgnoreCase(name) && field.getValue()!=null)
+                                    return true;
+                            }
+                            field=null;
+                            return false;
+                        }
+                        return true;
+                    }
+
+                    @Override
+                    public String nextElement() throws NoSuchElementException
+                    {
+                        if (hasMoreElements())
+                        {
+                            String value=field.getValue();
+                            field=null;
+                            return value;
+                        }
+                        throw new NoSuchElementException();
+                    }
+
+                };
+            }
+        }
+
+        List<String> empty=Collections.emptyList();
+        return Collections.enumeration(empty);
+    }
+
+    /**
+     * Get multi field values with separator. The multiple values can be represented as separate
+     * headers of the same name, or by a single header using the separator(s), or a combination of
+     * both. Separators may be quoted.
+     *
+     * @param name the case-insensitive field name
+     * @param separators String of separators.
+     * @return Enumeration of the values, or null if no such header.
+     */
+    public Enumeration<String> getValues(String name, final String separators)
+    {
+        final Enumeration<String> e = getValues(name);
+        if (e == null)
+            return null;
+        return new Enumeration<String>()
+        {
+            QuotedStringTokenizer tok = null;
+
+            @Override
+            public boolean hasMoreElements()
+            {
+                if (tok != null && tok.hasMoreElements()) return true;
+                while (e.hasMoreElements())
+                {
+                    String value = e.nextElement();
+                    if (value!=null)
+                    {
+                        tok = new QuotedStringTokenizer(value, separators, false, false);
+                        if (tok.hasMoreElements()) return true;
+                    }
+                }
+                tok = null;
+                return false;
+            }
+
+            @Override
+            public String nextElement() throws NoSuchElementException
+            {
+                if (!hasMoreElements()) throw new NoSuchElementException();
+                String next = (String) tok.nextElement();
+                if (next != null) next = next.trim();
+                return next;
+            }
+        };
+    }
+
+    public void put(HttpField field)
+    {
+        boolean put=false;
+        for (int i=_fields.size();i-->0;)
+        {
+            HttpField f=_fields.get(i);
+            if (f.isSame(field))
+            {
+                if (put)
+                    _fields.remove(i);
+                else
+                {
+                    _fields.set(i,field);
+                    put=true;
+                }
+            }
+        }
+        if (!put)
+            _fields.add(field);
+    }
+    
+    /**
+     * Set a field.
+     *
+     * @param name the name of the field
+     * @param value the value of the field. If null the field is cleared.
+     */
+    public void put(String name, String value)
+    {
+        if (value == null)
+            remove(name);
+        else
+            put(new HttpField(name, value));
+    }
+
+    public void put(HttpHeader header, HttpHeaderValue value)
+    {
+        put(header,value.toString());
+    }
+
+    /**
+     * Set a field.
+     *
+     * @param header the header name of the field
+     * @param value the value of the field. If null the field is cleared.
+     */
+    public void put(HttpHeader header, String value)
+    {
+        if (value == null)
+            remove(header);
+        else
+            put(new HttpField(header, value));
+    }
+
+    /**
+     * Set a field.
+     *
+     * @param name the name of the field
+     * @param list the List value of the field. If null the field is cleared.
+     */
+    public void put(String name, List<String> list)
+    {
+        remove(name);
+        for (String v : list)
+            if (v!=null)
+                add(name,v);
+    }
+
+    /**
+     * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
+     * headers of the same name.
+     *
+     * @param name the name of the field
+     * @param value the value of the field.
+     * @exception IllegalArgumentException If the name is a single valued field and already has a
+     *                value.
+     */
+    public void add(String name, String value) throws IllegalArgumentException
+    {
+        if (value == null)
+            return;
+
+        HttpField field = new HttpField(name, value);
+        _fields.add(field);
+    }
+
+    public void add(HttpHeader header, HttpHeaderValue value) throws IllegalArgumentException
+    {
+        add(header,value.toString());
+    }
+
+    /**
+     * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
+     * headers of the same name.
+     *
+     * @param header the header
+     * @param value the value of the field.
+     * @exception IllegalArgumentException 
+     */
+    public void add(HttpHeader header, String value) throws IllegalArgumentException
+    {
+        if (value == null) throw new IllegalArgumentException("null value");
+
+        HttpField field = new HttpField(header, value);
+        _fields.add(field);
+    }
+
+    /**
+     * Remove a field.
+     *
+     * @param name the field to remove
+     */
+    public HttpField remove(HttpHeader name)
+    {
+        for (int i=_fields.size();i-->0;)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getHeader()==name)
+                return _fields.remove(i);
+        }
+        return null;
+    }
+
+    /**
+     * Remove a field.
+     *
+     * @param name the field to remove
+     */
+    public HttpField remove(String name)
+    {
+        for (int i=_fields.size();i-->0;)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getName().equalsIgnoreCase(name))
+                return _fields.remove(i);
+        }
+        return null;
+    }
+
+    /**
+     * Get a header as an long value. Returns the value of an integer field or -1 if not found. The
+     * case of the field name is ignored.
+     *
+     * @param name the case-insensitive field name
+     * @exception NumberFormatException If bad long found
+     */
+    public long getLongField(String name) throws NumberFormatException
+    {
+        HttpField field = getField(name);
+        return field==null?-1L:StringUtil.toLong(field.getValue());
+    }
+
+    /**
+     * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case
+     * of the field name is ignored.
+     *
+     * @param name the case-insensitive field name
+     */
+    public long getDateField(String name)
+    {
+        HttpField field = getField(name);
+        if (field == null)
+            return -1;
+
+        String val = valueParameters(field.getValue(), null);
+        if (val == null)
+            return -1;
+
+        final long date = DateParser.parseDate(val);
+        if (date==-1)
+            throw new IllegalArgumentException("Cannot convert date: " + val);
+        return date;
+    }
+
+
+    /**
+     * Sets the value of an long field.
+     *
+     * @param name the field name
+     * @param value the field long value
+     */
+    public void putLongField(HttpHeader name, long value)
+    {
+        String v = Long.toString(value);
+        put(name, v);
+    }
+
+    /**
+     * Sets the value of an long field.
+     *
+     * @param name the field name
+     * @param value the field long value
+     */
+    public void putLongField(String name, long value)
+    {
+        String v = Long.toString(value);
+        put(name, v);
+    }
+
+
+    /**
+     * Sets the value of a date field.
+     *
+     * @param name the field name
+     * @param date the field date value
+     */
+    public void putDateField(HttpHeader name, long date)
+    {
+        String d=DateGenerator.formatDate(date);
+        put(name, d);
+    }
+
+    /**
+     * Sets the value of a date field.
+     *
+     * @param name the field name
+     * @param date the field date value
+     */
+    public void putDateField(String name, long date)
+    {
+        String d=DateGenerator.formatDate(date);
+        put(name, d);
+    }
+
+    /**
+     * Sets the value of a date field.
+     *
+     * @param name the field name
+     * @param date the field date value
+     */
+    public void addDateField(String name, long date)
+    {
+        String d=DateGenerator.formatDate(date);
+        add(name,d);
+    }
+
+    @Override
+    public String
+    toString()
+    {
+        try
+        {
+            StringBuilder buffer = new StringBuilder();
+            for (HttpField field : _fields)
+            {
+                if (field != null)
+                {
+                    String tmp = field.getName();
+                    if (tmp != null) buffer.append(tmp);
+                    buffer.append(": ");
+                    tmp = field.getValue();
+                    if (tmp != null) buffer.append(tmp);
+                    buffer.append("\r\n");
+                }
+            }
+            buffer.append("\r\n");
+            return buffer.toString();
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+            return e.toString();
+        }
+    }
+
+    /**
+     * Clear the header.
+     */
+    public void clear()
+    {
+        _fields.clear();
+    }
+
+    public void add(HttpField field)
+    {
+        _fields.add(field);
+    }
+
+    
+    
+    /**
+     * Add fields from another HttpFields instance. Single valued fields are replaced, while all
+     * others are added.
+     *
+     * @param fields the fields to add
+     */
+    public void add(HttpFields fields)
+    {
+        if (fields == null) return;
+
+        Enumeration<String> e = fields.getFieldNames();
+        while (e.hasMoreElements())
+        {
+            String name = e.nextElement();
+            Enumeration<String> values = fields.getValues(name);
+            while (values.hasMoreElements())
+                add(name, values.nextElement());
+        }
+    }
+
+    /**
+     * Get field value parameters. Some field values can have parameters. This method separates the
+     * value from the parameters and optionally populates a map with the parameters. For example:
+     *
+     * <PRE>
+     *
+     * FieldName : Value ; param1=val1 ; param2=val2
+     *
+     * </PRE>
+     *
+     * @param value The Field value, possibly with parameteres.
+     * @param parameters A map to populate with the parameters, or null
+     * @return The value.
+     */
+    public static String valueParameters(String value, Map<String,String> parameters)
+    {
+        if (value == null) return null;
+
+        int i = value.indexOf(';');
+        if (i < 0) return value;
+        if (parameters == null) return value.substring(0, i).trim();
+
+        StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true);
+        while (tok1.hasMoreTokens())
+        {
+            String token = tok1.nextToken();
+            StringTokenizer tok2 = new QuotedStringTokenizer(token, "= ");
+            if (tok2.hasMoreTokens())
+            {
+                String paramName = tok2.nextToken();
+                String paramVal = null;
+                if (tok2.hasMoreTokens()) paramVal = tok2.nextToken();
+                parameters.put(paramName, paramVal);
+            }
+        }
+
+        return value.substring(0, i).trim();
+    }
+
+    private static final Float __one = new Float("1.0");
+    private static final Float __zero = new Float("0.0");
+    private static final Trie<Float> __qualities = new ArrayTernaryTrie<>();
+    static
+    {
+        __qualities.put("*", __one);
+        __qualities.put("1.0", __one);
+        __qualities.put("1", __one);
+        __qualities.put("0.9", new Float("0.9"));
+        __qualities.put("0.8", new Float("0.8"));
+        __qualities.put("0.7", new Float("0.7"));
+        __qualities.put("0.66", new Float("0.66"));
+        __qualities.put("0.6", new Float("0.6"));
+        __qualities.put("0.5", new Float("0.5"));
+        __qualities.put("0.4", new Float("0.4"));
+        __qualities.put("0.33", new Float("0.33"));
+        __qualities.put("0.3", new Float("0.3"));
+        __qualities.put("0.2", new Float("0.2"));
+        __qualities.put("0.1", new Float("0.1"));
+        __qualities.put("0", __zero);
+        __qualities.put("0.0", __zero);
+    }
+
+    public static Float getQuality(String value)
+    {
+        if (value == null) return __zero;
+
+        int qe = value.indexOf(";");
+        if (qe++ < 0 || qe == value.length()) return __one;
+
+        if (value.charAt(qe++) == 'q')
+        {
+            qe++;
+            Float q = __qualities.get(value, qe, value.length() - qe);
+            if (q != null)
+                return q;
+        }
+
+        Map<String,String> params = new HashMap<>(4);
+        valueParameters(value, params);
+        String qs = params.get("q");
+        if (qs==null)
+            qs="*";
+        Float q = __qualities.get(qs);
+        if (q == null)
+        {
+            try
+            {
+                q = new Float(qs);
+            }
+            catch (Exception e)
+            {
+                q = __one;
+            }
+        }
+        return q;
+    }
+
+    /**
+     * List values in quality order.
+     *
+     * @param e Enumeration of values with quality parameters
+     * @return values in quality order.
+     */
+    public static List<String> qualityList(Enumeration<String> e)
+    {
+        if (e == null || !e.hasMoreElements())
+            return Collections.emptyList();
+
+        Object list = null;
+        Object qual = null;
+
+        // Assume list will be well ordered and just add nonzero
+        while (e.hasMoreElements())
+        {
+            String v = e.nextElement();
+            Float q = getQuality(v);
+
+            if (q >= 0.001)
+            {
+                list = LazyList.add(list, v);
+                qual = LazyList.add(qual, q);
+            }
+        }
+
+        List<String> vl = LazyList.getList(list, false);
+        if (vl.size() < 2) 
+            return vl;
+
+        List<Float> ql = LazyList.getList(qual, false);
+
+        // sort list with swaps
+        Float last = __zero;
+        for (int i = vl.size(); i-- > 0;)
+        {
+            Float q = ql.get(i);
+            if (last.compareTo(q) > 0)
+            {
+                String tmp = vl.get(i);
+                vl.set(i, vl.get(i + 1));
+                vl.set(i + 1, tmp);
+                ql.set(i, ql.get(i + 1));
+                ql.set(i + 1, q);
+                last = __zero;
+                i = vl.size();
+                continue;
+            }
+            last = q;
+        }
+        ql.clear();
+        return vl;
+    }
+
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpGenerator.java b/lib/jetty/org/eclipse/jetty/http/HttpGenerator.java
new file mode 100644 (file)
index 0000000..a51e4ba
--- /dev/null
@@ -0,0 +1,1104 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.http.HttpTokens.EndOfContent;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * HttpGenerator. Builds HTTP Messages.
+ *
+ * If the system property "org.eclipse.jetty.http.HttpGenerator.STRICT" is set to true,
+ * then the generator will strictly pass on the exact strings received from methods and header
+ * fields.  Otherwise a fast case insensitive string lookup is used that may alter the
+ * case and white space of some methods/headers
+ * </p>
+ */
+public class HttpGenerator
+{
+    private final static Logger LOG = Log.getLogger(HttpGenerator.class);
+
+    public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT"); 
+
+    private final static byte[] __colon_space = new byte[] {':',' '};
+    private final static HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE};
+    public static final ResponseInfo CONTINUE_100_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,100,null,false);
+    public static final ResponseInfo PROGRESS_102_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,102,null,false);
+    public final static ResponseInfo RESPONSE_500_INFO =
+        new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0,HttpStatus.INTERNAL_SERVER_ERROR_500,null,false);
+
+    // states
+    public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END }
+    public enum Result { NEED_CHUNK,NEED_INFO,NEED_HEADER,FLUSH,CONTINUE,SHUTDOWN_OUT,DONE}
+
+    // other statics
+    public static final int CHUNK_SIZE = 12;
+
+    private State _state = State.START;
+    private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT;
+
+    private long _contentPrepared = 0;
+    private boolean _noContent = false;
+    private Boolean _persistent = null;
+
+    private final int _send;
+    private final static int SEND_SERVER = 0x01;
+    private final static int SEND_XPOWEREDBY = 0x02;
+
+
+    /* ------------------------------------------------------------------------------- */
+    public static void setJettyVersion(String serverVersion)
+    {
+        SEND[SEND_SERVER] = StringUtil.getBytes("Server: " + serverVersion + "\015\012");
+        SEND[SEND_XPOWEREDBY] = StringUtil.getBytes("X-Powered-By: " + serverVersion + "\015\012");
+        SEND[SEND_SERVER | SEND_XPOWEREDBY] = StringUtil.getBytes("Server: " + serverVersion + "\015\012X-Powered-By: " +
+                serverVersion + "\015\012");
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    // data
+    private boolean _needCRLF = false;
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpGenerator()
+    {
+        this(false,false);
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    public HttpGenerator(boolean sendServerVersion,boolean sendXPoweredBy)
+    {
+        _send=(sendServerVersion?SEND_SERVER:0) | (sendXPoweredBy?SEND_XPOWEREDBY:0);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public void reset()
+    {
+        _state = State.START;
+        _endOfContent = EndOfContent.UNKNOWN_CONTENT;
+        _noContent=false;
+        _persistent = null;
+        _contentPrepared = 0;
+        _needCRLF = false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public boolean getSendServerVersion ()
+    {
+        return (_send&SEND_SERVER)!=0;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setSendServerVersion (boolean sendServerVersion)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /* ------------------------------------------------------------ */
+    public State getState()
+    {
+        return _state;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isState(State state)
+    {
+        return _state == state;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isIdle()
+    {
+        return _state == State.START;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isEnd()
+    {
+        return _state == State.END;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isCommitted()
+    {
+        return _state.ordinal() >= State.COMMITTED.ordinal();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isChunking()
+    {
+        return _endOfContent==EndOfContent.CHUNKED_CONTENT;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setPersistent(boolean persistent)
+    {
+        _persistent=persistent;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if known to be persistent
+     */
+    public boolean isPersistent()
+    {
+        return Boolean.TRUE.equals(_persistent);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isWritten()
+    {
+        return _contentPrepared>0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getContentPrepared()
+    {
+        return _contentPrepared;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void abort()
+    {
+        _persistent=false;
+        _state=State.END;
+        _endOfContent=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Result generateRequest(RequestInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
+    {
+        switch(_state)
+        {
+            case START:
+            {
+                if (info==null)
+                    return Result.NEED_INFO;
+
+                // Do we need a request header
+                if (header==null)
+                    return Result.NEED_HEADER;
+
+                // If we have not been told our persistence, set the default
+                if (_persistent==null)
+                    _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
+
+                // prepare the header
+                int pos=BufferUtil.flipToFill(header);
+                try
+                {
+                    // generate ResponseLine
+                    generateRequestLine(info,header);
+
+                    if (info.getHttpVersion()==HttpVersion.HTTP_0_9)
+                        _noContent=true;
+                    else
+                        generateHeaders(info,header,content,last);
+
+                    boolean expect100 = info.getHttpFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
+
+                    if (expect100)
+                    {
+                        _state = State.COMMITTED;
+                    }
+                    else
+                    {
+                        // handle the content.
+                        int len = BufferUtil.length(content);
+                        if (len>0)
+                        {
+                            _contentPrepared+=len;
+                            if (isChunking())
+                                prepareChunk(header,len);
+                        }
+                        _state = last?State.COMPLETING:State.COMMITTED;
+                    }
+
+                    return Result.FLUSH;
+                }
+                catch(Exception e)
+                {
+                    String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
+                    throw new IOException(message,e);
+                }
+                finally
+                {
+                    BufferUtil.flipToFlush(header,pos);
+                }
+            }
+
+            case COMMITTED:
+            {
+                int len = BufferUtil.length(content);
+
+                if (len>0)
+                {
+                    // Do we need a chunk buffer?
+                    if (isChunking())
+                    {
+                        // Do we need a chunk buffer?
+                        if (chunk==null)
+                            return Result.NEED_CHUNK;
+                        BufferUtil.clearToFill(chunk);
+                        prepareChunk(chunk,len);
+                        BufferUtil.flipToFlush(chunk,0);
+                    }
+                    _contentPrepared+=len;
+                }
+
+                if (last)
+                {
+                    _state=State.COMPLETING;
+                    return len>0?Result.FLUSH:Result.CONTINUE;
+                }
+
+                return Result.FLUSH;
+            }
+
+            case COMPLETING:
+            {
+                if (BufferUtil.hasContent(content))
+                {
+                    LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
+                }
+
+                if (isChunking())
+                {
+                    // Do we need a chunk buffer?
+                    if (chunk==null)
+                        return Result.NEED_CHUNK;
+                    BufferUtil.clearToFill(chunk);
+                    prepareChunk(chunk,0);
+                    BufferUtil.flipToFlush(chunk,0);
+                    _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+                    return Result.FLUSH;
+                }
+
+                _state=State.END;
+               return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
+            }
+
+            case END:
+                if (BufferUtil.hasContent(content))
+                {
+                    LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
+                }
+                return Result.DONE;
+
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public Result generateResponse(ResponseInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
+    {
+        switch(_state)
+        {
+            case START:
+            {
+                if (info==null)
+                    return Result.NEED_INFO;
+
+                // Handle 0.9
+                if (info.getHttpVersion() == HttpVersion.HTTP_0_9)
+                {
+                    _persistent = false;
+                    _endOfContent=EndOfContent.EOF_CONTENT;
+                    if (BufferUtil.hasContent(content))
+                        _contentPrepared+=content.remaining();
+                    _state = last?State.COMPLETING:State.COMMITTED;
+                    return Result.FLUSH;
+                }
+
+                // Do we need a response header
+                if (header==null)
+                    return Result.NEED_HEADER;
+
+                // If we have not been told our persistence, set the default
+                if (_persistent==null)
+                    _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
+
+                // prepare the header
+                int pos=BufferUtil.flipToFill(header);
+                try
+                {
+                    // generate ResponseLine
+                    generateResponseLine(info,header);
+
+                    // Handle 1xx and no content responses
+                    int status=info.getStatus();
+                    if (status>=100 && status<200 )
+                    {
+                        _noContent=true;
+
+                        if (status!=HttpStatus.SWITCHING_PROTOCOLS_101 )
+                        {
+                            header.put(HttpTokens.CRLF);
+                            _state=State.COMPLETING_1XX;
+                            return Result.FLUSH;
+                        }
+                    }
+                    else if (status==HttpStatus.NO_CONTENT_204 || status==HttpStatus.NOT_MODIFIED_304)
+                    {
+                        _noContent=true;
+                    }
+
+                    generateHeaders(info,header,content,last);
+
+                    // handle the content.
+                    int len = BufferUtil.length(content);
+                    if (len>0)
+                    {
+                        _contentPrepared+=len;
+                        if (isChunking() && !info.isHead())
+                            prepareChunk(header,len);
+                    }
+                    _state = last?State.COMPLETING:State.COMMITTED;
+                }
+                catch(Exception e)
+                {
+                    String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
+                    throw new IOException(message,e);
+                }
+                finally
+                {
+                    BufferUtil.flipToFlush(header,pos);
+                }
+
+                return Result.FLUSH;
+            }
+
+            case COMMITTED:
+            {
+                int len = BufferUtil.length(content);
+
+                // handle the content.
+                if (len>0)
+                {
+                    if (isChunking())
+                    {
+                        if (chunk==null)
+                            return Result.NEED_CHUNK;
+                        BufferUtil.clearToFill(chunk);
+                        prepareChunk(chunk,len);
+                        BufferUtil.flipToFlush(chunk,0);
+                    }
+                    _contentPrepared+=len;
+                }
+
+                if (last)
+                {
+                    _state=State.COMPLETING;
+                    return len>0?Result.FLUSH:Result.CONTINUE;
+                }
+                return len>0?Result.FLUSH:Result.DONE;
+
+            }
+
+            case COMPLETING_1XX:
+            {
+                reset();
+                return Result.DONE;
+            }
+
+            case COMPLETING:
+            {
+                if (BufferUtil.hasContent(content))
+                {
+                    LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
+                }
+
+                if (isChunking())
+                {
+                    // Do we need a chunk buffer?
+                    if (chunk==null)
+                        return Result.NEED_CHUNK;
+
+                    // Write the last chunk
+                    BufferUtil.clearToFill(chunk);
+                    prepareChunk(chunk,0);
+                    BufferUtil.flipToFlush(chunk,0);
+                    _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+                    return Result.FLUSH;
+                }
+
+                _state=State.END;
+
+               return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
+            }
+
+            case END:
+                if (BufferUtil.hasContent(content))
+                {
+                    LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
+                }
+                return Result.DONE;
+
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void prepareChunk(ByteBuffer chunk, int remaining)
+    {
+        // if we need CRLF add this to header
+        if (_needCRLF)
+            BufferUtil.putCRLF(chunk);
+
+        // Add the chunk size to the header
+        if (remaining>0)
+        {
+            BufferUtil.putHexInt(chunk, remaining);
+            BufferUtil.putCRLF(chunk);
+            _needCRLF=true;
+        }
+        else
+        {
+            chunk.put(LAST_CHUNK);
+            _needCRLF=false;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void generateRequestLine(RequestInfo request,ByteBuffer header)
+    {
+        header.put(StringUtil.getBytes(request.getMethod()));
+        header.put((byte)' ');
+        header.put(StringUtil.getBytes(request.getUri()));
+        switch(request.getHttpVersion())
+        {
+            case HTTP_1_0:
+            case HTTP_1_1:
+                header.put((byte)' ');
+                header.put(request.getHttpVersion().toBytes());
+                break;
+            default:
+                throw new IllegalStateException();
+        }
+        header.put(HttpTokens.CRLF);
+    }
+
+    /* ------------------------------------------------------------ */
+    private void generateResponseLine(ResponseInfo response, ByteBuffer header)
+    {
+        // Look for prepared response line
+        int status=response.getStatus();
+        PreparedResponse preprepared = status<__preprepared.length?__preprepared[status]:null;
+        String reason=response.getReason();
+        if (preprepared!=null)
+        {
+            if (reason==null)
+                header.put(preprepared._responseLine);
+            else
+            {
+                header.put(preprepared._schemeCode);
+                header.put(getReasonBytes(reason));
+                header.put(HttpTokens.CRLF);
+            }
+        }
+        else // generate response line
+        {
+            header.put(HTTP_1_1_SPACE);
+            header.put((byte) ('0' + status / 100));
+            header.put((byte) ('0' + (status % 100) / 10));
+            header.put((byte) ('0' + (status % 10)));
+            header.put((byte) ' ');
+            if (reason==null)
+            {
+                header.put((byte) ('0' + status / 100));
+                header.put((byte) ('0' + (status % 100) / 10));
+                header.put((byte) ('0' + (status % 10)));
+            }
+            else
+                header.put(getReasonBytes(reason));
+            header.put(HttpTokens.CRLF);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private byte[] getReasonBytes(String reason)
+    {
+        if (reason.length()>1024)
+            reason=reason.substring(0,1024);
+        byte[] _bytes = StringUtil.getBytes(reason);
+
+        for (int i=_bytes.length;i-->0;)
+            if (_bytes[i]=='\r' || _bytes[i]=='\n')
+                _bytes[i]='?';
+        return _bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    private void generateHeaders(Info _info,ByteBuffer header,ByteBuffer content,boolean last)
+    {
+        final RequestInfo request=(_info instanceof RequestInfo)?(RequestInfo)_info:null;
+        final ResponseInfo response=(_info instanceof ResponseInfo)?(ResponseInfo)_info:null;
+
+        // default field values
+        int send=_send;
+        HttpField transfer_encoding=null;
+        boolean keep_alive=false;
+        boolean close=false;
+        boolean content_type=false;
+        StringBuilder connection = null;
+
+        // Generate fields
+        if (_info.getHttpFields() != null)
+        {
+            for (HttpField field : _info.getHttpFields())
+            {
+                HttpHeader h = field.getHeader();
+
+                switch (h==null?HttpHeader.UNKNOWN:h)
+                {
+                    case CONTENT_LENGTH:
+                        // handle specially below
+                        if (_info.getContentLength()>=0)
+                            _endOfContent=EndOfContent.CONTENT_LENGTH;
+                        break;
+
+                    case CONTENT_TYPE:
+                    {
+                        if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString()))
+                            _endOfContent=EndOfContent.SELF_DEFINING_CONTENT;
+
+                        // write the field to the header
+                        content_type=true;
+                        putTo(field,header);
+                        break;
+                    }
+
+                    case TRANSFER_ENCODING:
+                    {
+                        if (_info.getHttpVersion() == HttpVersion.HTTP_1_1)
+                            transfer_encoding = field;
+                        // Do NOT add yet!
+                        break;
+                    }
+
+                    case CONNECTION:
+                    {
+                        if (request!=null)
+                            putTo(field,header);
+
+                        // Lookup and/or split connection value field
+                        HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue())?CLOSE:new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
+                        String[] split = null;
+
+                        if (values[0]==null)
+                        {
+                            split = field.getValue().split("\\s*,\\s*");
+                            if (split.length>0)
+                            {
+                                values=new HttpHeaderValue[split.length];
+                                for (int i=0;i<split.length;i++)
+                                    values[i]=HttpHeaderValue.CACHE.get(split[i]);
+                            }
+                        }
+
+                        // Handle connection values
+                        for (int i=0;i<values.length;i++)
+                        {
+                            HttpHeaderValue value=values[i];
+                            switch (value==null?HttpHeaderValue.UNKNOWN:value)
+                            {
+                                case UPGRADE:
+                                {
+                                    // special case for websocket connection ordering
+                                    header.put(HttpHeader.CONNECTION.getBytesColonSpace()).put(HttpHeader.UPGRADE.getBytes());
+                                    header.put(CRLF);
+                                    break;
+                                }
+
+                                case CLOSE:
+                                {
+                                    close=true;
+                                    if (response!=null)
+                                    {
+                                        _persistent=false;
+                                        if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
+                                            _endOfContent=EndOfContent.EOF_CONTENT;
+                                    }
+                                    break;
+                                }
+
+                                case KEEP_ALIVE:
+                                {
+                                    if (_info.getHttpVersion() == HttpVersion.HTTP_1_0)
+                                    {
+                                        keep_alive = true;
+                                        if (response!=null)
+                                            _persistent=true;
+                                    }
+                                    break;
+                                }
+
+                                default:
+                                {
+                                    if (connection==null)
+                                        connection=new StringBuilder();
+                                    else
+                                        connection.append(',');
+                                    connection.append(split==null?field.getValue():split[i]);
+                                }
+                            }
+                        }
+
+                        // Do NOT add yet!
+                        break;
+                    }
+
+                    case SERVER:
+                    {
+                        send=send&~SEND_SERVER;
+                        putTo(field,header);
+                        break;
+                    }
+
+                    default:
+                        putTo(field,header);
+                }
+            }
+        }
+
+
+        // Calculate how to end _content and connection, _content length and transfer encoding
+        // settings.
+        // From RFC 2616 4.4:
+        // 1. No body for 1xx, 204, 304 & HEAD response
+        // 2. Force _content-length?
+        // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
+        // 4. Content-Length
+        // 5. multipart/byteranges
+        // 6. close
+        int status=response!=null?response.getStatus():-1;
+        switch (_endOfContent)
+        {
+            case UNKNOWN_CONTENT:
+                // It may be that we have no _content, or perhaps _content just has not been
+                // written yet?
+
+                // Response known not to have a body
+                if (_contentPrepared == 0 && response!=null && (status < 200 || status == 204 || status == 304))
+                    _endOfContent=EndOfContent.NO_CONTENT;
+                else if (_info.getContentLength()>0)
+                {
+                    // we have been given a content length
+                    _endOfContent=EndOfContent.CONTENT_LENGTH;
+                    long content_length = _info.getContentLength();
+                    if ((response!=null || content_length>0 || content_type ) && !_noContent)
+                    {
+                        // known length but not actually set.
+                        header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+                        BufferUtil.putDecLong(header, content_length);
+                        header.put(HttpTokens.CRLF);
+                    }
+                }
+                else if (last)
+                {
+                    // we have seen all the _content there is, so we can be content-length limited.
+                    _endOfContent=EndOfContent.CONTENT_LENGTH;
+                    long content_length = _contentPrepared+BufferUtil.length(content);
+
+                    // Do we need to tell the headers about it
+                    if ((response!=null || content_length>0 || content_type ) && !_noContent)
+                    {
+                        header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+                        BufferUtil.putDecLong(header, content_length);
+                        header.put(HttpTokens.CRLF);
+                    }
+                }
+                else
+                {
+                    // No idea, so we must assume that a body is coming.
+                    _endOfContent = EndOfContent.CHUNKED_CONTENT;
+                    // HTTP 1.0 does not understand chunked content, so we must use EOF content.
+                    // For a request with HTTP 1.0 & Connection: keep-alive
+                    // we *must* close the connection, otherwise the client
+                    // has no way to detect the end of the content.
+                    if (!isPersistent() || _info.getHttpVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
+                        _endOfContent = EndOfContent.EOF_CONTENT;
+                }
+                break;
+
+            case CONTENT_LENGTH:
+                long content_length = _info.getContentLength();
+                if ((response!=null || content_length>0 || content_type ) && !_noContent)
+                {
+                    // known length but not actually set.
+                    header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+                    BufferUtil.putDecLong(header, content_length);
+                    header.put(HttpTokens.CRLF);
+                }
+                break;
+
+            case NO_CONTENT:
+                if (response!=null && status >= 200 && status != 204 && status != 304)
+                    header.put(CONTENT_LENGTH_0);
+                break;
+
+            case EOF_CONTENT:
+                _persistent = request!=null;
+                break;
+
+            case CHUNKED_CONTENT:
+                break;
+
+            default:
+                break;
+        }
+
+        // Add transfer_encoding if needed
+        if (isChunking())
+        {
+            // try to use user supplied encoding as it may have other values.
+            if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue()))
+            {
+                String c = transfer_encoding.getValue();
+                if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
+                    putTo(transfer_encoding,header);
+                else
+                    throw new IllegalArgumentException("BAD TE");
+            }
+            else
+                header.put(TRANSFER_ENCODING_CHUNKED);
+        }
+
+        // Handle connection if need be
+        if (_endOfContent==EndOfContent.EOF_CONTENT)
+        {
+            keep_alive=false;
+            _persistent=false;
+        }
+
+        // If this is a response, work out persistence
+        if (response!=null)
+        {
+            if (!isPersistent() && (close || _info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()))
+            {
+                if (connection==null)
+                    header.put(CONNECTION_CLOSE);
+                else
+                {
+                    header.put(CONNECTION_CLOSE,0,CONNECTION_CLOSE.length-2);
+                    header.put((byte)',');
+                    header.put(StringUtil.getBytes(connection.toString()));
+                    header.put(CRLF);
+                }
+            }
+            else if (keep_alive)
+            {
+                if (connection==null)
+                    header.put(CONNECTION_KEEP_ALIVE);
+                else
+                {
+                    header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_KEEP_ALIVE.length-2);
+                    header.put((byte)',');
+                    header.put(StringUtil.getBytes(connection.toString()));
+                    header.put(CRLF);
+                }
+            }
+            else if (connection!=null)
+            {
+                header.put(HttpHeader.CONNECTION.getBytesColonSpace());
+                header.put(StringUtil.getBytes(connection.toString()));
+                header.put(CRLF);
+            }
+        }
+
+        if (status>199)
+            header.put(SEND[send]);
+
+        // end the header.
+        header.put(HttpTokens.CRLF);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public static byte[] getReasonBuffer(int code)
+    {
+        PreparedResponse status = code<__preprepared.length?__preprepared[code]:null;
+        if (status!=null)
+            return status._reason;
+        return null;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    public String toString()
+    {
+        return String.format("%s{s=%s}",
+                getClass().getSimpleName(),
+                _state);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    // common _content
+    private static final byte[] LAST_CHUNK =    { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
+    private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
+    private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
+    private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
+    private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
+    private static final byte[] CRLF = StringUtil.getBytes("\015\012");
+    private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
+    private static final byte[][] SEND = new byte[][]{
+            new byte[0],
+            StringUtil.getBytes("Server: Jetty(9.x.x)\015\012"),
+        StringUtil.getBytes("X-Powered-By: Jetty(9.x.x)\015\012"),
+        StringUtil.getBytes("Server: Jetty(9.x.x)\015\012X-Powered-By: Jetty(9.x.x)\015\012")
+    };
+
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    // Build cache of response lines for status
+    private static class PreparedResponse
+    {
+        byte[] _reason;
+        byte[] _schemeCode;
+        byte[] _responseLine;
+    }
+    private static final PreparedResponse[] __preprepared = new PreparedResponse[HttpStatus.MAX_CODE+1];
+    static
+    {
+        int versionLength=HttpVersion.HTTP_1_1.toString().length();
+
+        for (int i=0;i<__preprepared.length;i++)
+        {
+            HttpStatus.Code code = HttpStatus.getCode(i);
+            if (code==null)
+                continue;
+            String reason=code.getMessage();
+            byte[] line=new byte[versionLength+5+reason.length()+2];
+            HttpVersion.HTTP_1_1.toBuffer().get(line,0,versionLength);
+            line[versionLength+0]=' ';
+            line[versionLength+1]=(byte)('0'+i/100);
+            line[versionLength+2]=(byte)('0'+(i%100)/10);
+            line[versionLength+3]=(byte)('0'+(i%10));
+            line[versionLength+4]=' ';
+            for (int j=0;j<reason.length();j++)
+                line[versionLength+5+j]=(byte)reason.charAt(j);
+            line[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN;
+            line[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
+
+            __preprepared[i] = new PreparedResponse();
+            __preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0,versionLength+5);
+            __preprepared[i]._reason = Arrays.copyOfRange(line, versionLength+5, line.length-2);
+            __preprepared[i]._responseLine=line;
+        }
+    }
+
+    public static class Info
+    {
+        final HttpVersion _httpVersion;
+        final HttpFields _httpFields;
+        final long _contentLength;
+
+        private Info(HttpVersion httpVersion, HttpFields httpFields, long contentLength)
+        {
+            _httpVersion = httpVersion;
+            _httpFields = httpFields;
+            _contentLength = contentLength;
+        }
+
+        public HttpVersion getHttpVersion()
+        {
+            return _httpVersion;
+        }
+        public HttpFields getHttpFields()
+        {
+            return _httpFields;
+        }
+        public long getContentLength()
+        {
+            return _contentLength;
+        }
+    }
+
+    public static class RequestInfo extends Info
+    {
+        private final String _method;
+        private final String _uri;
+
+        public RequestInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, String method, String uri)
+        {
+            super(httpVersion,httpFields,contentLength);
+            _method = method;
+            _uri = uri;
+        }
+
+        public String getMethod()
+        {
+            return _method;
+        }
+
+        public String getUri()
+        {
+            return _uri;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("RequestInfo{%s %s %s,%d}",_method,_uri,_httpVersion,_contentLength);
+        }
+    }
+
+    public static class ResponseInfo extends Info
+    {
+        private final int _status;
+        private final String _reason;
+        private final boolean _head;
+
+        public ResponseInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, int status, String reason, boolean head)
+        {
+            super(httpVersion,httpFields,contentLength);
+            _status = status;
+            _reason = reason;
+            _head = head;
+        }
+
+        public boolean isInformational()
+        {
+            return _status>=100 && _status<200;
+        }
+
+        public int getStatus()
+        {
+            return _status;
+        }
+
+        public String getReason()
+        {
+            return _reason;
+        }
+
+        public boolean isHead()
+        {
+            return _head;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("ResponseInfo{%s %s %s,%d,%b}",_httpVersion,_status,_reason,_contentLength,_head);
+        }
+    } 
+
+    private static void putSanitisedName(String s,ByteBuffer buffer)
+    {
+        int l=s.length();
+        for (int i=0;i<l;i++)
+        {
+            char c=s.charAt(i);
+            
+            if (c<0 || c>0xff || c=='\r' || c=='\n'|| c==':')
+                buffer.put((byte)'?');
+            else
+                buffer.put((byte)(0xff&c));
+        }
+    }
+
+    private static void putSanitisedValue(String s,ByteBuffer buffer)
+    {
+        int l=s.length();
+        for (int i=0;i<l;i++)
+        {
+            char c=s.charAt(i);
+            
+            if (c<0 || c>0xff || c=='\r' || c=='\n')
+                buffer.put((byte)'?');
+            else
+                buffer.put((byte)(0xff&c));
+        }
+    }
+
+    public static void putTo(HttpField field, ByteBuffer bufferInFillMode)
+    {
+        if (field instanceof CachedHttpField)
+        {
+            ((CachedHttpField)field).putTo(bufferInFillMode);
+        }
+        else
+        {
+            HttpHeader header=field.getHeader();
+            if (header!=null)
+            {
+                bufferInFillMode.put(header.getBytesColonSpace());
+                putSanitisedValue(field.getValue(),bufferInFillMode);
+            }
+            else
+            {
+                putSanitisedName(field.getName(),bufferInFillMode);
+                bufferInFillMode.put(__colon_space);
+                putSanitisedValue(field.getValue(),bufferInFillMode);
+            }
+
+            BufferUtil.putCRLF(bufferInFillMode);
+        }
+    }
+
+    public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode) 
+    {
+        for (HttpField field : fields)
+        {
+            if (field != null)
+                putTo(field,bufferInFillMode);
+        }
+        BufferUtil.putCRLF(bufferInFillMode);
+    }
+    
+    public static class CachedHttpField extends HttpField
+    {
+        private final byte[] _bytes;
+        public CachedHttpField(HttpHeader header,String value)
+        {
+            super(header,value);
+            int cbl=header.getBytesColonSpace().length;
+            _bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2);
+            System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length());
+            _bytes[_bytes.length-2]=(byte)'\r';
+            _bytes[_bytes.length-1]=(byte)'\n';
+        }
+        
+        public void putTo(ByteBuffer bufferInFillMode)
+        {
+            bufferInFillMode.put(_bytes);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpHeader.java b/lib/jetty/org/eclipse/jetty/http/HttpHeader.java
new file mode 100644 (file)
index 0000000..ab0ddcf
--- /dev/null
@@ -0,0 +1,178 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+public enum HttpHeader
+{
+    /* ------------------------------------------------------------ */
+    /** General Fields.
+     */
+    CONNECTION("Connection"),
+    CACHE_CONTROL("Cache-Control"),
+    DATE("Date"),
+    PRAGMA("Pragma"),
+    PROXY_CONNECTION ("Proxy-Connection"),
+    TRAILER("Trailer"),
+    TRANSFER_ENCODING("Transfer-Encoding"),
+    UPGRADE("Upgrade"),
+    VIA("Via"),
+    WARNING("Warning"),
+    NEGOTIATE("Negotiate"),
+
+    /* ------------------------------------------------------------ */
+    /** Entity Fields.
+     */
+    ALLOW("Allow"),
+    CONTENT_ENCODING("Content-Encoding"),
+    CONTENT_LANGUAGE("Content-Language"),
+    CONTENT_LENGTH("Content-Length"),
+    CONTENT_LOCATION("Content-Location"),
+    CONTENT_MD5("Content-MD5"),
+    CONTENT_RANGE("Content-Range"),
+    CONTENT_TYPE("Content-Type"),
+    EXPIRES("Expires"),
+    LAST_MODIFIED("Last-Modified"),
+
+    /* ------------------------------------------------------------ */
+    /** Request Fields.
+     */
+    ACCEPT("Accept"),
+    ACCEPT_CHARSET("Accept-Charset"),
+    ACCEPT_ENCODING("Accept-Encoding"),
+    ACCEPT_LANGUAGE("Accept-Language"),
+    AUTHORIZATION("Authorization"),
+    EXPECT("Expect"),
+    FORWARDED("Forwarded"),
+    FROM("From"),
+    HOST("Host"),
+    IF_MATCH("If-Match"),
+    IF_MODIFIED_SINCE("If-Modified-Since"),
+    IF_NONE_MATCH("If-None-Match"),
+    IF_RANGE("If-Range"),
+    IF_UNMODIFIED_SINCE("If-Unmodified-Since"),
+    KEEP_ALIVE("Keep-Alive"),
+    MAX_FORWARDS("Max-Forwards"),
+    PROXY_AUTHORIZATION("Proxy-Authorization"),
+    RANGE("Range"),
+    REQUEST_RANGE("Request-Range"),
+    REFERER("Referer"),
+    TE("TE"),
+    USER_AGENT("User-Agent"),
+    X_FORWARDED_FOR("X-Forwarded-For"),
+    X_FORWARDED_PROTO("X-Forwarded-Proto"),
+    X_FORWARDED_SERVER("X-Forwarded-Server"),
+    X_FORWARDED_HOST("X-Forwarded-Host"),
+
+    /* ------------------------------------------------------------ */
+    /** Response Fields.
+     */
+    ACCEPT_RANGES("Accept-Ranges"),
+    AGE("Age"),
+    ETAG("ETag"),
+    LOCATION("Location"),
+    PROXY_AUTHENTICATE("Proxy-Authenticate"),
+    RETRY_AFTER("Retry-After"),
+    SERVER("Server"),
+    SERVLET_ENGINE("Servlet-Engine"),
+    VARY("Vary"),
+    WWW_AUTHENTICATE("WWW-Authenticate"),
+
+    /* ------------------------------------------------------------ */
+    /** Other Fields.
+     */
+    COOKIE("Cookie"),
+    SET_COOKIE("Set-Cookie"),
+    SET_COOKIE2("Set-Cookie2"),
+    MIME_VERSION("MIME-Version"),
+    IDENTITY("identity"),
+    
+    X_POWERED_BY("X-Powered-By"),
+
+    UNKNOWN("::UNKNOWN::");
+
+
+    /* ------------------------------------------------------------ */
+    public final static Trie<HttpHeader> CACHE= new ArrayTrie<>(512);
+    static
+    {
+        for (HttpHeader header : HttpHeader.values())
+            if (header!=UNKNOWN)
+                CACHE.put(header.toString(),header);
+    }
+    
+    private final String _string;
+    private final byte[] _bytes;
+    private final byte[] _bytesColonSpace;
+    private final ByteBuffer _buffer;
+
+    /* ------------------------------------------------------------ */
+    HttpHeader(String s)
+    {
+        _string=s;
+        _bytes=StringUtil.getBytes(s);
+        _bytesColonSpace=StringUtil.getBytes(s+": ");
+        _buffer=ByteBuffer.wrap(_bytes);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteBuffer toBuffer()
+    {
+        return _buffer.asReadOnlyBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] getBytes()
+    {
+        return _bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] getBytesColonSpace()
+    {
+        return _bytesColonSpace;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean is(String s)
+    {
+        return _string.equalsIgnoreCase(s);    
+    }
+
+    /* ------------------------------------------------------------ */
+    public String asString()
+    {
+        return _string;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _string;
+    }
+    
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpHeaderValue.java b/lib/jetty/org/eclipse/jetty/http/HttpHeaderValue.java
new file mode 100644 (file)
index 0000000..8338a32
--- /dev/null
@@ -0,0 +1,104 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/**
+ * 
+ */
+public enum HttpHeaderValue
+{
+    CLOSE("close"),
+    CHUNKED("chunked"),
+    GZIP("gzip"),
+    IDENTITY("identity"),
+    KEEP_ALIVE("keep-alive"),
+    CONTINUE("100-continue"),
+    PROCESSING("102-processing"),
+    TE("TE"),
+    BYTES("bytes"),
+    NO_CACHE("no-cache"),
+    UPGRADE("Upgrade"),
+    UNKNOWN("::UNKNOWN::");
+
+    /* ------------------------------------------------------------ */
+    public final static Trie<HttpHeaderValue> CACHE= new ArrayTrie<HttpHeaderValue>();
+    static
+    {
+        for (HttpHeaderValue value : HttpHeaderValue.values())
+            if (value!=UNKNOWN)
+                CACHE.put(value.toString(),value);
+    }
+
+    private final String _string;
+    private final ByteBuffer _buffer;
+
+    /* ------------------------------------------------------------ */
+    HttpHeaderValue(String s)
+    {
+        _string=s;
+        _buffer=BufferUtil.toBuffer(s);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteBuffer toBuffer()
+    {
+        return _buffer.asReadOnlyBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean is(String s)
+    {
+        return _string.equalsIgnoreCase(s);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String asString()
+    {
+        return _string;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _string;
+    }
+
+    /* ------------------------------------------------------------ */
+    private static EnumSet<HttpHeader> __known =
+            EnumSet.of(HttpHeader.CONNECTION,
+                    HttpHeader.TRANSFER_ENCODING,
+                    HttpHeader.CONTENT_ENCODING);
+
+    /* ------------------------------------------------------------ */
+    public static boolean hasKnownValues(HttpHeader header)
+    {
+        if (header==null)
+            return false;
+        return __known.contains(header);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpMethod.java b/lib/jetty/org/eclipse/jetty/http/HttpMethod.java
new file mode 100644 (file)
index 0000000..8a26268
--- /dev/null
@@ -0,0 +1,174 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/* ------------------------------------------------------------------------------- */
+/**
+ */
+public enum HttpMethod
+{
+    GET,
+    POST,
+    HEAD,
+    PUT,
+    OPTIONS,
+    DELETE,
+    TRACE,
+    CONNECT,
+    MOVE,
+    PROXY;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Optimised lookup to find a method name and trailing space in a byte array.
+     * @param bytes Array containing ISO-8859-1 characters
+     * @param position The first valid index
+     * @param limit The first non valid index
+     * @return A HttpMethod if a match or null if no easy match.
+     */
+    public static HttpMethod lookAheadGet(byte[] bytes, final int position, int limit)
+    {
+        int length=limit-position;
+        if (length<4)
+            return null;
+        switch(bytes[position])
+        {
+            case 'G':
+                if (bytes[position+1]=='E' && bytes[position+2]=='T' && bytes[position+3]==' ')
+                    return GET;
+                break;
+            case 'P':
+                if (bytes[position+1]=='O' && bytes[position+2]=='S' && bytes[position+3]=='T' && length>=5 && bytes[position+4]==' ')
+                    return POST;
+                if (bytes[position+1]=='R' && bytes[position+2]=='O' && bytes[position+3]=='X' && length>=6 && bytes[position+4]=='Y' && bytes[position+5]==' ')
+                    return PROXY;
+                if (bytes[position+1]=='U' && bytes[position+2]=='T' && bytes[position+3]==' ')
+                    return PUT;
+                break;
+            case 'H':
+                if (bytes[position+1]=='E' && bytes[position+2]=='A' && bytes[position+3]=='D' && length>=5 && bytes[position+4]==' ')
+                    return HEAD;
+                break;
+            case 'O':
+                if (bytes[position+1]=='O' && bytes[position+2]=='T' && bytes[position+3]=='I' && length>=8 &&
+                bytes[position+4]=='O' && bytes[position+5]=='N' && bytes[position+6]=='S' && bytes[position+7]==' ' )
+                    return OPTIONS;
+                break;
+            case 'D':
+                if (bytes[position+1]=='E' && bytes[position+2]=='L' && bytes[position+3]=='E' && length>=7 &&
+                bytes[position+4]=='T' && bytes[position+5]=='E' && bytes[position+6]==' ' )
+                    return DELETE;
+                break;
+            case 'T':
+                if (bytes[position+1]=='R' && bytes[position+2]=='A' && bytes[position+3]=='C' && length>=6 &&
+                bytes[position+4]=='E' && bytes[position+5]==' ' )
+                    return TRACE;
+                break;
+            case 'C':
+                if (bytes[position+1]=='O' && bytes[position+2]=='N' && bytes[position+3]=='N' && length>=8 &&
+                bytes[position+4]=='E' && bytes[position+5]=='C' && bytes[position+6]=='T' && bytes[position+7]==' ' )
+                    return CONNECT;
+                break;
+            case 'M':
+                if (bytes[position+1]=='O' && bytes[position+2]=='V' && bytes[position+3]=='E' &&  bytes[position+4]==' ')
+                    return MOVE;
+                break;
+
+            default:
+                break;
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Optimised lookup to find a method name and trailing space in a byte array.
+     * @param buffer buffer containing ISO-8859-1 characters
+     * @return A HttpMethod if a match or null if no easy match.
+     */
+    public static HttpMethod lookAheadGet(ByteBuffer buffer)
+    {
+        if (buffer.hasArray())
+            return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
+
+        // TODO use cache and check for space
+        // return CACHE.getBest(buffer,0,buffer.remaining());
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public final static Trie<HttpMethod> CACHE= new ArrayTrie<>();
+    static
+    {
+        for (HttpMethod method : HttpMethod.values())
+            CACHE.put(method.toString(),method);
+    }
+
+    /* ------------------------------------------------------------ */
+    private final ByteBuffer _buffer;
+    private final byte[] _bytes;
+
+    /* ------------------------------------------------------------ */
+    HttpMethod()
+    {
+        _bytes=StringUtil.getBytes(toString());
+        _buffer=ByteBuffer.wrap(_bytes);
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] getBytes()
+    {
+        return _bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean is(String s)
+    {
+        return toString().equalsIgnoreCase(s);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteBuffer asBuffer()
+    {
+        return _buffer.asReadOnlyBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public String asString()
+    {
+        return toString();
+    }
+
+    /**
+     * Converts the given String parameter to an HttpMethod
+     * @param method the String to get the equivalent HttpMethod from
+     * @return the HttpMethod or null if the parameter method is unknown
+     */
+    public static HttpMethod fromString(String method)
+    {
+        return CACHE.get(method);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpParser.java b/lib/jetty/org/eclipse/jetty/http/HttpParser.java
new file mode 100644 (file)
index 0000000..79f1c28
--- /dev/null
@@ -0,0 +1,1688 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.http.HttpTokens.EndOfContent;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** A Parser for HTTP 0.9, 1.0 and 1.1
+ * <p>
+ * The is parser parses HTTP client and server messages from buffers
+ * passed in the {@link #parseNext(ByteBuffer)} method.  The parsed
+ * elements of the HTTP message are passed as event calls to the 
+ * {@link HttpHandler} instance the parser is constructed with.
+ * If the passed handler is a {@link RequestHandler} then server side
+ * parsing is performed and if it is a {@link ResponseHandler}, then 
+ * client side parsing is done.
+ * </p>
+ * <p>
+ * The contract of the {@link HttpHandler} API is that if a call returns 
+ * true then the call to {@link #parseNext(ByteBuffer)} will return as 
+ * soon as possible also with a true response.  Typically this indicates
+ * that the parsing has reached a stage where the caller should process 
+ * the events accumulated by the handler.    It is the preferred calling
+ * style that handling such as calling a servlet to process a request, 
+ * should be done after a true return from {@link #parseNext(ByteBuffer)}
+ * rather than from within the scope of a call like 
+ * {@link RequestHandler#messageComplete()}
+ * </p>
+ * <p>
+ * For performance, the parse is heavily dependent on the 
+ * {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a
+ * single pass for both the structure ( : and CRLF ) and semantic (which
+ * header and value) of a header.  Specifically the static {@link HttpHeader#CACHE}
+ * is used to lookup common combinations of headers and values 
+ * (eg. "Connection: close"), or just header names (eg. "Connection:" ).
+ * For headers who's value is not known statically (eg. Host, COOKIE) then a
+ * per parser dynamic Trie of {@link HttpFields} from previous parsed messages
+ * is used to help the parsing of subsequent messages.
+ * </p>
+ * <p>
+ * If the system property "org.eclipse.jetty.http.HttpParser.STRICT" is set to true,
+ * then the parser will strictly pass on the exact strings received for methods and header
+ * fields.  Otherwise a fast case insensitive string lookup is used that may alter the
+ * case of the method and/or headers
+ * </p>
+ */
+public class HttpParser
+{
+    public static final Logger LOG = Log.getLogger(HttpParser.class);
+    public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpParser.STRICT"); 
+    public final static int INITIAL_URI_LENGTH=256;
+
+    /**
+     * Cache of common {@link HttpField}s including: <UL>
+     * <LI>Common static combinations such as:<UL>
+     *   <li>Connection: close
+     *   <li>Accept-Encoding: gzip
+     *   <li>Content-Length: 0
+     * </ul>
+     * <li>Combinations of Content-Type header for common mime types by common charsets
+     * <li>Most common headers with null values so that a lookup will at least
+     * determine the header name even if the name:value combination is not cached
+     * </ul>
+     */
+    public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
+    
+    // States
+    public enum State
+    {
+        START,
+        METHOD,
+        RESPONSE_VERSION,
+        SPACE1,
+        STATUS,
+        URI,
+        SPACE2,
+        REQUEST_VERSION,
+        REASON,
+        PROXY,
+        HEADER,
+        HEADER_IN_NAME,
+        HEADER_VALUE,
+        HEADER_IN_VALUE,
+        CONTENT,
+        EOF_CONTENT,
+        CHUNKED_CONTENT,
+        CHUNK_SIZE,
+        CHUNK_PARAMS,
+        CHUNK,
+        END,
+        CLOSED
+    }
+
+    private final boolean DEBUG=LOG.isDebugEnabled(); // Cache debug to help branch prediction
+    private final HttpHandler<ByteBuffer> _handler;
+    private final RequestHandler<ByteBuffer> _requestHandler;
+    private final ResponseHandler<ByteBuffer> _responseHandler;
+    private final int _maxHeaderBytes;
+    private final boolean _strict;
+    private HttpField _field;
+    private HttpHeader _header;
+    private String _headerString;
+    private HttpHeaderValue _value;
+    private String _valueString;
+    private int _responseStatus;
+    private int _headerBytes;
+    private boolean _host;
+
+    /* ------------------------------------------------------------------------------- */
+    private volatile State _state=State.START;
+    private volatile boolean _eof;
+    private volatile boolean _closed;
+    private HttpMethod _method;
+    private String _methodString;
+    private HttpVersion _version;
+    private ByteBuffer _uri=ByteBuffer.allocate(INITIAL_URI_LENGTH); // Tune?
+    private EndOfContent _endOfContent;
+    private long _contentLength;
+    private long _contentPosition;
+    private int _chunkLength;
+    private int _chunkPosition;
+    private boolean _headResponse;
+    private boolean _cr;
+    private ByteBuffer _contentChunk;
+    private Trie<HttpField> _connectionFields;
+
+    private int _length;
+    private final StringBuilder _string=new StringBuilder();
+
+    static
+    {
+        CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE));
+        CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE));
+        CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.UPGRADE));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip,deflate,sdch"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-GB,en-US;q=0.8,en;q=0.6"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT,"*/*"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT,"image/png,image/*;q=0.8,*/*;q=0.5"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
+        CACHE.put(new HttpField(HttpHeader.PRAGMA,"no-cache"));
+        CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate"));
+        CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"no-cache"));
+        CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH,"0"));
+        CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
+        CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
+        CACHE.put(new HttpField(HttpHeader.TRANSFER_ENCODING,"chunked"));
+        CACHE.put(new HttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT"));
+        
+        // Add common Content types as fields
+        for (String type : new String[]{"text/plain","text/html","text/xml","text/json","application/json","application/x-www-form-urlencoded"})
+        {
+            HttpField field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type);
+            CACHE.put(field);
+            
+            for (String charset : new String[]{"UTF-8","ISO-8859-1"})
+            {
+                CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset));
+                CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset));
+            }
+        }
+    
+        // Add headers with null values so HttpParser can avoid looking up name again for unknown values
+        for (HttpHeader h:HttpHeader.values())
+            if (!CACHE.put(new HttpField(h,(String)null)))
+                throw new IllegalStateException("CACHE FULL");
+        // Add some more common headers
+        CACHE.put(new HttpField(HttpHeader.REFERER,(String)null));
+        CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE,(String)null));
+        CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH,(String)null));
+        CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null));
+        CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(RequestHandler<ByteBuffer> handler)
+    {
+        this(handler,-1,__STRICT);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(ResponseHandler<ByteBuffer> handler)
+    {
+        this(handler,-1,__STRICT);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(RequestHandler<ByteBuffer> handler,int maxHeaderBytes)
+    {
+        this(handler,maxHeaderBytes,__STRICT);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(ResponseHandler<ByteBuffer> handler,int maxHeaderBytes)
+    {
+        this(handler,maxHeaderBytes,__STRICT);
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(RequestHandler<ByteBuffer> handler,int maxHeaderBytes,boolean strict)
+    {
+        _handler=handler;
+        _requestHandler=handler;
+        _responseHandler=null;
+        _maxHeaderBytes=maxHeaderBytes;
+        _strict=strict;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(ResponseHandler<ByteBuffer> handler,int maxHeaderBytes,boolean strict)
+    {
+        _handler=handler;
+        _requestHandler=null;
+        _responseHandler=handler;
+        _maxHeaderBytes=maxHeaderBytes;
+        _strict=strict;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public long getContentLength()
+    {
+        return _contentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getContentRead()
+    {
+        return _contentPosition;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set if a HEAD response is expected
+     * @param head
+     */
+    public void setHeadResponse(boolean head)
+    {
+        _headResponse=head;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    protected void setResponseStatus(int status)
+    {
+        _responseStatus=status;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public State getState()
+    {
+        return _state;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean inContentState()
+    {
+        return _state.ordinal()>=State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal();
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean inHeaderState()
+    {
+        return _state.ordinal() < State.CONTENT.ordinal();
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean isChunking()
+    {
+        return _endOfContent==EndOfContent.CHUNKED_CONTENT;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isStart()
+    {
+        return isState(State.START);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isClosed()
+    {
+        return isState(State.CLOSED);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isIdle()
+    {
+        return isState(State.START)||isState(State.END)||isState(State.CLOSED);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isComplete()
+    {
+        return isState(State.END)||isState(State.CLOSED);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean isState(State state)
+    {
+        return _state == state;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    private static class BadMessage extends Error
+    {
+        private static final long serialVersionUID = 1L;
+        private final int _code;
+        private final String _message;
+
+        BadMessage()
+        {
+            this(400,null);
+        }
+        
+        BadMessage(int code)
+        {
+            this(code,null);
+        }
+        
+        BadMessage(String message)
+        {
+            this(400,message);
+        }
+        
+        BadMessage(int code,String message)
+        {
+            _code=code;
+            _message=message;
+        }
+        
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    private byte next(ByteBuffer buffer)
+    {
+        byte ch = buffer.get();
+        
+        if (_cr)
+        {
+            if (ch!=HttpTokens.LINE_FEED)
+                throw new BadMessage("Bad EOL");
+            _cr=false;
+            return ch;
+        }
+
+        if (ch>=0 && ch<HttpTokens.SPACE)
+        {
+            if (ch==HttpTokens.CARRIAGE_RETURN)
+            {
+                if (buffer.hasRemaining())
+                {
+                    if(_maxHeaderBytes>0 && _state.ordinal()<State.END.ordinal())
+                        _headerBytes++;
+                    ch=buffer.get();
+                    if (ch!=HttpTokens.LINE_FEED)
+                        throw new BadMessage("Bad EOL");
+                }
+                else
+                {
+                    _cr=true;
+                    // Can return 0 here to indicate the need for more characters, 
+                    // because a real 0 in the buffer would cause a BadMessage below 
+                    return 0;
+                }
+            }
+            // Only LF or TAB acceptable special characters
+            else if (!(ch==HttpTokens.LINE_FEED || ch==HttpTokens.TAB))
+                throw new BadMessage("Illegal character");
+        }
+        
+        return ch;
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    /* Quick lookahead for the start state looking for a request method or a HTTP version,
+     * otherwise skip white space until something else to parse.
+     */
+    private boolean quickStart(ByteBuffer buffer)
+    {          
+        if (_requestHandler!=null)
+        {
+            _method = HttpMethod.lookAheadGet(buffer);
+            if (_method!=null)
+            {
+                _methodString = _method.asString();
+                buffer.position(buffer.position()+_methodString.length()+1);
+                
+                setState(State.SPACE1);
+                return false;
+            }
+        }
+        else if (_responseHandler!=null)
+        {
+            _version = HttpVersion.lookAheadGet(buffer);
+            if (_version!=null)
+            {
+                buffer.position(buffer.position()+_version.asString().length()+1);
+                setState(State.SPACE1);
+                return false;
+            }
+        }
+        
+        // Quick start look
+        while (_state==State.START && buffer.hasRemaining())
+        {
+            int ch=next(buffer);
+
+            if (ch > HttpTokens.SPACE)
+            {
+                _string.setLength(0);
+                _string.append((char)ch);
+                setState(_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION);
+                return false;
+            }
+            else if (ch==0)
+                break;
+            else if (ch<0)
+                throw new BadMessage();
+            
+            // count this white space as a header byte to avoid DOS
+            if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+            {
+                LOG.warn("padding is too large >"+_maxHeaderBytes);
+                throw new BadMessage(HttpStatus.BAD_REQUEST_400);
+            }
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    private void setString(String s)
+    {
+        _string.setLength(0);
+        _string.append(s);
+        _length=s.length();
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    private String takeString()
+    {
+        _string.setLength(_length);
+        String s =_string.toString();
+        _string.setLength(0);
+        _length=-1;
+        return s;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /* Parse a request or response line
+     */
+    private boolean parseLine(ByteBuffer buffer)
+    {
+        boolean handle=false;
+
+        // Process headers
+        while (_state.ordinal()<State.HEADER.ordinal() && buffer.hasRemaining() && !handle)
+        {
+            // process each character
+            byte ch=next(buffer);
+            if (ch==0)
+                break;
+
+            if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+            {
+                if (_state==State.URI)
+                {
+                    LOG.warn("URI is too large >"+_maxHeaderBytes);
+                    throw new BadMessage(HttpStatus.REQUEST_URI_TOO_LONG_414);
+                }
+                else
+                {
+                    if (_requestHandler!=null)
+                        LOG.warn("request is too large >"+_maxHeaderBytes);
+                    else
+                        LOG.warn("response is too large >"+_maxHeaderBytes);
+                    throw new BadMessage(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
+                }
+            }
+
+            switch (_state)
+            {
+                case METHOD:
+                    if (ch == HttpTokens.SPACE)
+                    {
+                        _length=_string.length();
+                        _methodString=takeString();
+                        HttpMethod method=HttpMethod.CACHE.get(_methodString);
+                        if (method!=null && !_strict)
+                            _methodString=method.asString();
+                        setState(State.SPACE1);
+                    }
+                    else if (ch < HttpTokens.SPACE)
+                        throw new BadMessage(ch<0?"Illegal character":"No URI");
+                    else
+                        _string.append((char)ch);
+                    break;
+
+                case RESPONSE_VERSION:
+                    if (ch == HttpTokens.SPACE)
+                    {
+                        _length=_string.length();
+                        String version=takeString();
+                        _version=HttpVersion.CACHE.get(version);
+                        if (_version==null)
+                            throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Unknown Version");
+                        setState(State.SPACE1);
+                    }
+                    else if (ch < HttpTokens.SPACE)
+                        throw new BadMessage(ch<0?"Illegal character":"No Status");
+                    else
+                        _string.append((char)ch);
+                    break;
+
+                case SPACE1:
+                    if (ch > HttpTokens.SPACE || ch<0)
+                    {
+                        if (_responseHandler!=null)
+                        {
+                            setState(State.STATUS);
+                            setResponseStatus(ch-'0');
+                        }
+                        else
+                        {
+                            _uri.clear();
+                            setState(State.URI);
+                            // quick scan for space or EoBuffer
+                            if (buffer.hasArray())
+                            {
+                                byte[] array=buffer.array();
+                                int p=buffer.arrayOffset()+buffer.position();
+                                int l=buffer.arrayOffset()+buffer.limit();
+                                int i=p;
+                                while (i<l && array[i]>HttpTokens.SPACE)
+                                    i++;
+
+                                int len=i-p;
+                                _headerBytes+=len;
+                                
+                                if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+                                {
+                                    LOG.warn("URI is too large >"+_maxHeaderBytes);
+                                    throw new BadMessage(HttpStatus.REQUEST_URI_TOO_LONG_414);
+                                }
+                                if (_uri.remaining()<=len)
+                                {
+                                    ByteBuffer uri = ByteBuffer.allocate(_uri.capacity()+2*len);
+                                    _uri.flip();
+                                    uri.put(_uri);
+                                    _uri=uri;
+                                }
+                                _uri.put(array,p-1,len+1);
+                                buffer.position(i-buffer.arrayOffset());
+                            }
+                            else
+                                _uri.put(ch);
+                        }
+                    }
+                    else if (ch < HttpTokens.SPACE)
+                    {
+                        throw new BadMessage(HttpStatus.BAD_REQUEST_400,_requestHandler!=null?"No URI":"No Status");
+                    }
+                    break;
+
+                case STATUS:
+                    if (ch == HttpTokens.SPACE)
+                    {
+                        setState(State.SPACE2);
+                    }
+                    else if (ch>='0' && ch<='9')
+                    {
+                        _responseStatus=_responseStatus*10+(ch-'0');
+                    }
+                    else if (ch < HttpTokens.SPACE && ch>=0)
+                    {
+                        handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
+                        setState(State.HEADER);
+                    }
+                    else
+                    {
+                        throw new BadMessage();
+                    }
+                    break;
+
+                case URI:
+                    if (ch == HttpTokens.SPACE)
+                    {
+                        setState(State.SPACE2);
+                    }
+                    else if (ch < HttpTokens.SPACE && ch>=0)
+                    {
+                        // HTTP/0.9
+                        _uri.flip();
+                        handle=_requestHandler.startRequest(_method,_methodString,_uri,null)||handle;
+                        setState(State.END);
+                        BufferUtil.clear(buffer);
+                        handle=_handler.headerComplete()||handle;
+                        handle=_handler.messageComplete()||handle;
+                    }
+                    else
+                    {
+                        if (!_uri.hasRemaining())
+                        {
+                            ByteBuffer uri = ByteBuffer.allocate(_uri.capacity()*2);
+                            _uri.flip();
+                            uri.put(_uri);
+                            _uri=uri;
+                        }
+                        _uri.put(ch);
+                    }
+                    break;
+
+                case SPACE2:
+                    if (ch > HttpTokens.SPACE)
+                    {
+                        _string.setLength(0);
+                        _string.append((char)ch);
+                        if (_responseHandler!=null)
+                        {
+                            _length=1;
+                            setState(State.REASON);
+                        }
+                        else
+                        {
+                            setState(State.REQUEST_VERSION);
+
+                            // try quick look ahead for HTTP Version
+                            HttpVersion version;
+                            if (buffer.position()>0 && buffer.hasArray())
+                                version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
+                            else
+                                version=HttpVersion.CACHE.getBest(buffer,0,buffer.remaining());
+                            if (version==null)
+                            {
+                                if (_method==HttpMethod.PROXY)
+                                {
+                                    if (!(_requestHandler instanceof ProxyHandler))
+                                        throw new BadMessage();
+                                    
+                                    _uri.flip();
+                                    String protocol=BufferUtil.toString(_uri);
+                                    // This is the proxy protocol, so we can assume entire first line is in buffer else 400
+                                    buffer.position(buffer.position()-1);
+                                    String sAddr = getProxyField(buffer);
+                                    String dAddr = getProxyField(buffer);
+                                    int sPort = BufferUtil.takeInt(buffer);
+                                    next(buffer);
+                                    int dPort = BufferUtil.takeInt(buffer);
+                                    next(buffer);
+                                    _state=State.START;
+                                    ((ProxyHandler)_requestHandler).proxied(protocol,sAddr,dAddr,sPort,dPort);
+                                    return false;
+                                }
+                            }
+                            else
+                            {
+                                int pos = buffer.position()+version.asString().length()-1;
+                                if (pos<buffer.limit())
+                                {
+                                    byte n=buffer.get(pos);
+                                    if (n==HttpTokens.CARRIAGE_RETURN)
+                                    {
+                                        _cr=true;
+                                        _version=version;
+                                        _string.setLength(0);
+                                        buffer.position(pos+1);
+                                    }
+                                    else if (n==HttpTokens.LINE_FEED)
+                                    {
+                                        _version=version;
+                                        _string.setLength(0);
+                                        buffer.position(pos);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    else if (ch == HttpTokens.LINE_FEED)
+                    {
+                        if (_responseHandler!=null)
+                        {
+                            handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
+                            setState(State.HEADER);
+                        }
+                        else
+                        {
+                            // HTTP/0.9
+                            _uri.flip();
+                            handle=_requestHandler.startRequest(_method,_methodString,_uri, null)||handle;
+                            setState(State.END);
+                            BufferUtil.clear(buffer);
+                            handle=_handler.headerComplete()||handle;
+                            handle=_handler.messageComplete()||handle;
+                        }
+                    }
+                    else if (ch<0)
+                        throw new BadMessage();
+                    break;
+
+                case REQUEST_VERSION:
+                    if (ch == HttpTokens.LINE_FEED)
+                    {
+                        if (_version==null)
+                        {
+                            _length=_string.length();
+                            _version=HttpVersion.CACHE.get(takeString());
+                        }
+                        if (_version==null)
+                            throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Unknown Version");
+                        
+                        // Should we try to cache header fields?
+                        if (_connectionFields==null && _version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
+                        {
+                            int header_cache = _handler.getHeaderCacheSize();
+                            _connectionFields=new ArrayTernaryTrie<>(header_cache);                            
+                        }
+
+                        setState(State.HEADER);
+                        _uri.flip();
+                        handle=_requestHandler.startRequest(_method,_methodString,_uri, _version)||handle;
+                        continue;
+                    }
+                    else if (ch>=HttpTokens.SPACE)
+                        _string.append((char)ch);
+                    else
+                        throw new BadMessage();
+
+                    break;
+
+                case REASON:
+                    if (ch == HttpTokens.LINE_FEED)
+                    {
+                        String reason=takeString();
+
+                        setState(State.HEADER);
+                        handle=_responseHandler.startResponse(_version, _responseStatus, reason)||handle;
+                        continue;
+                    }
+                    else if (ch>=HttpTokens.SPACE)
+                    {
+                        _string.append((char)ch);
+                        if (ch!=' '&&ch!='\t')
+                            _length=_string.length();
+                    } 
+                    else
+                        throw new BadMessage();
+                    break;
+
+                default:
+                    throw new IllegalStateException(_state.toString());
+
+            }
+        }
+
+        return handle;
+    }
+
+    private boolean handleKnownHeaders(ByteBuffer buffer)
+    {
+        boolean add_to_connection_trie=false;
+        switch (_header)
+        {
+            case CONTENT_LENGTH:
+                if (_endOfContent != EndOfContent.CHUNKED_CONTENT)
+                {
+                    try
+                    {
+                        _contentLength=Long.parseLong(_valueString);
+                    }
+                    catch(NumberFormatException e)
+                    {
+                        LOG.ignore(e);
+                        throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Content-Length");
+                    }
+                    if (_contentLength <= 0)
+                        _endOfContent=EndOfContent.NO_CONTENT;
+                    else
+                        _endOfContent=EndOfContent.CONTENT_LENGTH;
+                }
+                break;
+
+            case TRANSFER_ENCODING:
+                if (_value==HttpHeaderValue.CHUNKED)
+                    _endOfContent=EndOfContent.CHUNKED_CONTENT;
+                else
+                {
+                    if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString()))
+                        _endOfContent=EndOfContent.CHUNKED_CONTENT;
+                    else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString()))
+                    {
+                        throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad chunking");
+                    }
+                }
+                break;
+
+            case HOST:
+                add_to_connection_trie=_connectionFields!=null && _field==null;
+                _host=true;
+                String host=_valueString;
+                int port=0;
+                if (host==null || host.length()==0)
+                {
+                    throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Host header");
+                }
+
+                int len=host.length();
+                loop: for (int i = len; i-- > 0;)
+                {
+                    char c2 = (char)(0xff & host.charAt(i));
+                    switch (c2)
+                    {
+                        case ']':
+                            break loop;
+
+                        case ':':
+                            try
+                            {
+                                len=i;
+                                port = StringUtil.toInt(host.substring(i+1));
+                            }
+                            catch (NumberFormatException e)
+                            {
+                                if (DEBUG)
+                                    LOG.debug(e);
+                                throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Host header");
+                            }
+                            break loop;
+                    }
+                }
+                if (host.charAt(0)=='[')
+                {
+                    if (host.charAt(len-1)!=']') 
+                    {
+                        throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad IPv6 Host header");
+                    }
+                    host = host.substring(1,len-1);
+                }
+                else if (len!=host.length())
+                    host = host.substring(0,len);
+                
+                if (_requestHandler!=null)
+                    _requestHandler.parsedHostHeader(host,port);
+                
+              break;
+              
+            case CONNECTION:
+                // Don't cache if not persistent
+                if (_valueString!=null && _valueString.contains("close"))
+                {
+                    _closed=true;
+                    _connectionFields=null;
+                }
+                break;
+
+            case AUTHORIZATION:
+            case ACCEPT:
+            case ACCEPT_CHARSET:
+            case ACCEPT_ENCODING:
+            case ACCEPT_LANGUAGE:
+            case COOKIE:
+            case CACHE_CONTROL:
+            case USER_AGENT:
+                add_to_connection_trie=_connectionFields!=null && _field==null;
+                break;
+                
+            default: break;
+        }
+    
+        if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null)
+        {
+            _field=new HttpField(_header,_valueString);
+            _connectionFields.put(_field);
+        }
+        
+        return false;
+    }
+    
+    
+    /* ------------------------------------------------------------------------------- */
+    /*
+     * Parse the message headers and return true if the handler has signaled for a return
+     */
+    protected boolean parseHeaders(ByteBuffer buffer)
+    {
+        boolean handle=false;
+
+        // Process headers
+        while (_state.ordinal()<State.CONTENT.ordinal() && buffer.hasRemaining() && !handle)
+        {
+            // process each character
+            byte ch=next(buffer);
+            if (ch==0)
+                break;
+            
+            if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+            {
+                LOG.warn("Header is too large >"+_maxHeaderBytes);
+                throw new BadMessage(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
+            }
+
+            switch (_state)
+            {
+                case HEADER:
+                    switch(ch)
+                    {
+                        case HttpTokens.COLON:
+                        case HttpTokens.SPACE:
+                        case HttpTokens.TAB:
+                        {
+                            // header value without name - continuation?
+                            if (_valueString==null)
+                            {
+                                _string.setLength(0);
+                                _length=0;
+                            }
+                            else
+                            {
+                                setString(_valueString);
+                                _string.append(' ');
+                                _length++;
+                                _valueString=null;
+                            }
+                            setState(State.HEADER_VALUE);
+                            break;
+                        }
+
+                        default:
+                        {
+                            // handler last header if any.  Delayed to here just in case there was a continuation line (above)
+                            if (_headerString!=null || _valueString!=null)
+                            {
+                                // Handle known headers
+                                if (_header!=null && handleKnownHeaders(buffer))
+                                {
+                                    _headerString=_valueString=null;
+                                    _header=null;
+                                    _value=null;
+                                    _field=null;
+                                    return true;
+                                }
+                                handle=_handler.parsedHeader(_field!=null?_field:new HttpField(_header,_headerString,_valueString))||handle;
+                            }
+                            _headerString=_valueString=null;
+                            _header=null;
+                            _value=null;
+                            _field=null;
+
+                            // now handle the ch
+                            if (ch == HttpTokens.LINE_FEED)
+                            {
+                                _contentPosition=0;
+
+                                // End of headers!
+
+                                // Was there a required host header?
+                                if (!_host && _version!=HttpVersion.HTTP_1_0 && _requestHandler!=null)
+                                {
+                                    throw new BadMessage(HttpStatus.BAD_REQUEST_400,"No Host");
+                                }
+
+                                // is it a response that cannot have a body?
+                                if (_responseHandler !=null  && // response  
+                                    (_responseStatus == 304  || // not-modified response
+                                    _responseStatus == 204 || // no-content response
+                                    _responseStatus < 200)) // 1xx response
+                                    _endOfContent=EndOfContent.NO_CONTENT; // ignore any other headers set
+                                
+                                // else if we don't know framing
+                                else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
+                                {
+                                    if (_responseStatus == 0  // request
+                                            || _responseStatus == 304 // not-modified response
+                                            || _responseStatus == 204 // no-content response
+                                            || _responseStatus < 200) // 1xx response
+                                        _endOfContent=EndOfContent.NO_CONTENT;
+                                    else
+                                        _endOfContent=EndOfContent.EOF_CONTENT;
+                                }
+
+                                // How is the message ended?
+                                switch (_endOfContent)
+                                {
+                                    case EOF_CONTENT:
+                                        setState(State.EOF_CONTENT);
+                                        handle=_handler.headerComplete()||handle;
+                                        break;
+
+                                    case CHUNKED_CONTENT:
+                                        setState(State.CHUNKED_CONTENT);
+                                        handle=_handler.headerComplete()||handle;
+                                        break;
+
+                                    case NO_CONTENT:
+                                        handle=_handler.headerComplete()||handle;
+                                        setState(State.END);
+                                        handle=_handler.messageComplete()||handle;
+                                        break;
+
+                                    default:
+                                        setState(State.CONTENT);
+                                        handle=_handler.headerComplete()||handle;
+                                        break;
+                                }
+                            }
+                            else if (ch<=HttpTokens.SPACE)
+                                throw new BadMessage();
+                            else
+                            {
+                                if (buffer.hasRemaining())
+                                {
+                                    // Try a look ahead for the known header name and value.
+                                    HttpField field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining());
+                                    if (field==null)
+                                        field=CACHE.getBest(buffer,-1,buffer.remaining());
+                                        
+                                    if (field!=null)
+                                    {
+                                        final String n;
+                                        final String v;
+
+                                        if (_strict)
+                                        {
+                                            // Have to get the fields exactly from the buffer to match case
+                                            String fn=field.getName();
+                                            String fv=field.getValue();
+                                            n=BufferUtil.toString(buffer,buffer.position()-1,fn.length(),StandardCharsets.US_ASCII);
+                                            if (fv==null)
+                                                v=null;
+                                            else
+                                            {
+                                                v=BufferUtil.toString(buffer,buffer.position()+fn.length()+1,fv.length(),StandardCharsets.ISO_8859_1);
+                                                field=new HttpField(field.getHeader(),n,v);
+                                            }
+                                        }
+                                        else
+                                        {
+                                            n=field.getName();
+                                            v=field.getValue(); 
+                                        }
+                                        
+                                        _header=field.getHeader();
+                                        _headerString=n;
+         
+                                        if (v==null)
+                                        {
+                                            // Header only
+                                            setState(State.HEADER_VALUE);
+                                            _string.setLength(0);
+                                            _length=0;
+                                            buffer.position(buffer.position()+n.length()+1);
+                                            break;
+                                        }
+                                        else
+                                        {
+                                            // Header and value
+                                            int pos=buffer.position()+n.length()+v.length()+1;
+                                            byte b=buffer.get(pos);
+
+                                            if (b==HttpTokens.CARRIAGE_RETURN || b==HttpTokens.LINE_FEED)
+                                            {                     
+                                                _field=field;
+                                                _valueString=v;
+                                                setState(State.HEADER_IN_VALUE);
+
+                                                if (b==HttpTokens.CARRIAGE_RETURN)
+                                                {
+                                                    _cr=true;
+                                                    buffer.position(pos+1);
+                                                }
+                                                else
+                                                    buffer.position(pos);
+                                                break;
+                                            }
+                                            else
+                                            {
+                                                setState(State.HEADER_IN_VALUE);
+                                                setString(v);
+                                                buffer.position(pos);
+                                                break;
+                                            }
+                                        }
+                                    }
+                                }
+
+                                // New header
+                                setState(State.HEADER_IN_NAME);
+                                _string.setLength(0);
+                                _string.append((char)ch);
+                                _length=1;
+                            }
+                        }
+                    }
+                    break;
+
+                case HEADER_IN_NAME:
+                    if (ch==HttpTokens.COLON || ch==HttpTokens.LINE_FEED)
+                    {
+                        if (_headerString==null)
+                        {
+                            _headerString=takeString();
+                            _header=HttpHeader.CACHE.get(_headerString);
+                        }
+                        _length=-1;
+
+                        setState(ch==HttpTokens.LINE_FEED?State.HEADER:State.HEADER_VALUE);
+                        break;
+                    }
+                    
+                    if (ch>=HttpTokens.SPACE || ch==HttpTokens.TAB)
+                    {
+                        if (_header!=null)
+                        {
+                            setString(_header.asString());
+                            _header=null;
+                            _headerString=null;
+                        }
+
+                        _string.append((char)ch);
+                        if (ch>HttpTokens.SPACE)
+                            _length=_string.length();
+                        break;
+                    }
+                     
+                    throw new BadMessage("Illegal character");
+
+                case HEADER_VALUE:
+                    if (ch>HttpTokens.SPACE || ch<0)
+                    {
+                        _string.append((char)(0xff&ch));
+                        _length=_string.length();
+                        setState(State.HEADER_IN_VALUE);
+                        break;
+                    }
+                    
+                    if (ch==HttpTokens.SPACE || ch==HttpTokens.TAB)
+                        break;
+                    
+                    if (ch==HttpTokens.LINE_FEED)
+                    {
+                        if (_length > 0)
+                        {
+                            _value=null;
+                            _valueString=(_valueString==null)?takeString():(_valueString+" "+takeString());
+                        }
+                        setState(State.HEADER);
+                        break; 
+                    }
+
+                    throw new BadMessage("Illegal character");
+
+                case HEADER_IN_VALUE:
+                    if (ch>=HttpTokens.SPACE || ch<0 || ch==HttpTokens.TAB)
+                    {
+                        if (_valueString!=null)
+                        {
+                            setString(_valueString);
+                            _valueString=null;
+                            _field=null;
+                        }
+                        _string.append((char)(0xff&ch));
+                        if (ch>HttpTokens.SPACE || ch<0)
+                            _length=_string.length();
+                        break;
+                    }
+                    
+                    if (ch==HttpTokens.LINE_FEED)
+                    {
+                        if (_length > 0)
+                        {
+                            _value=null;
+                            _valueString=takeString();
+                            _length=-1;
+                        }
+                        setState(State.HEADER);
+                        break;
+                    }
+                    throw new BadMessage("Illegal character");
+                    
+                default:
+                    throw new IllegalStateException(_state.toString());
+
+            }
+        }
+
+        return handle;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * Parse until next Event.
+     * @return True if an {@link RequestHandler} method was called and it returned true;
+     */
+    public boolean parseNext(ByteBuffer buffer)
+    {
+        if (DEBUG)
+            LOG.debug("parseNext s={} {}",_state,BufferUtil.toDetailString(buffer));
+        try
+        {
+            // Start a request/response
+            if (_state==State.START)
+            {
+                _version=null;
+                _method=null;
+                _methodString=null;
+                _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+                _header=null;
+                if (quickStart(buffer))
+                    return true;
+            }
+            
+            // Request/response line
+            if (_state.ordinal()>= State.START.ordinal() && _state.ordinal()<State.HEADER.ordinal())
+            {
+                if (parseLine(buffer))
+                    return true;
+            }
+
+            // parse headers
+            if (_state.ordinal()>= State.HEADER.ordinal() && _state.ordinal()<State.CONTENT.ordinal())
+            {
+                if (parseHeaders(buffer))
+                    return true;
+            }
+            
+            // parse content
+            if (_state.ordinal()>= State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal())
+            {
+                // Handle HEAD response
+                if (_responseStatus>0 && _headResponse)
+                {
+                    setState(State.END);
+                    if (_handler.messageComplete())
+                        return true;
+                }
+                else
+                {
+                    if (parseContent(buffer))
+                        return true;
+                }
+            }
+            
+            // handle end states
+            if (_state==State.END)
+            {
+                // eat white space
+                while (buffer.remaining()>0 && buffer.get(buffer.position())<=HttpTokens.SPACE)
+                    buffer.get();
+            }
+            else if (_state==State.CLOSED)
+            {
+                if (BufferUtil.hasContent(buffer))
+                {
+                    // Just ignore data when closed
+                    _headerBytes+=buffer.remaining();
+                    BufferUtil.clear(buffer);
+                    if (_headerBytes>_maxHeaderBytes)
+                    {
+                        // Don't want to waste time reading data of a closed request
+                        throw new IllegalStateException("too much data after closed");
+                    }
+                }
+            }
+            
+            // Handle EOF
+            if (_eof && !buffer.hasRemaining())
+            {
+                switch(_state)
+                {
+                    case CLOSED:
+                        break;
+                        
+                    case START:
+                        setState(State.CLOSED);
+                        _handler.earlyEOF();
+                        break;
+                        
+                    case END:
+                        setState(State.CLOSED);
+                        break;
+                        
+                    case EOF_CONTENT:
+                        setState(State.CLOSED);
+                        return _handler.messageComplete();
+
+                    case  CONTENT:
+                    case  CHUNKED_CONTENT:
+                    case  CHUNK_SIZE:
+                    case  CHUNK_PARAMS:
+                    case  CHUNK:
+                        setState(State.CLOSED);
+                        _handler.earlyEOF();
+                        break;
+
+                    default:
+                        if (DEBUG)
+                            LOG.debug("{} EOF in {}",this,_state);
+                        setState(State.CLOSED);
+                        _handler.badMessage(400,null);
+                        break;
+                }
+            }
+            
+            return false;
+        }
+        catch(BadMessage e)
+        {
+            BufferUtil.clear(buffer);
+
+            LOG.warn("badMessage: "+e._code+(e._message!=null?" "+e._message:"")+" for "+_handler);
+            if (DEBUG)
+                LOG.debug(e);
+            setState(State.CLOSED);
+            _handler.badMessage(e._code, e._message);
+            return false;
+        }
+        catch(Exception e)
+        {
+            BufferUtil.clear(buffer);
+
+            LOG.warn("badMessage: "+e.toString()+" for "+_handler);
+            if (DEBUG)
+                LOG.debug(e);
+            
+            if (_state.ordinal()<=State.END.ordinal())
+            {
+                setState(State.CLOSED);
+                _handler.badMessage(400,null);
+            }
+            else
+            {
+                _handler.earlyEOF();
+                setState(State.CLOSED);
+            }
+
+            return false;
+        }
+    }
+
+    protected boolean parseContent(ByteBuffer buffer)
+    {
+        int remaining=buffer.remaining();
+        if (remaining==0 && _state==State.CONTENT)
+        {
+            long content=_contentLength - _contentPosition;
+            if (content == 0)
+            {
+                setState(State.END);
+                if (_handler.messageComplete())
+                    return true;
+            }
+        }
+        
+        // Handle _content
+        byte ch;
+        while (_state.ordinal() < State.END.ordinal() && remaining>0)
+        {
+            switch (_state)
+            {
+                case EOF_CONTENT:
+                    _contentChunk=buffer.asReadOnlyBuffer();
+                    _contentPosition += remaining;
+                    buffer.position(buffer.position()+remaining);
+                    if (_handler.content(_contentChunk))
+                        return true;
+                    break;
+
+                case CONTENT:
+                {
+                    long content=_contentLength - _contentPosition;
+                    if (content == 0)
+                    {
+                        setState(State.END);
+                        if (_handler.messageComplete())
+                            return true;
+                    }
+                    else
+                    {
+                        _contentChunk=buffer.asReadOnlyBuffer();
+
+                        // limit content by expected size
+                        if (remaining > content)
+                        {
+                            // We can cast remaining to an int as we know that it is smaller than
+                            // or equal to length which is already an int.
+                            _contentChunk.limit(_contentChunk.position()+(int)content);
+                        }
+
+                        _contentPosition += _contentChunk.remaining();
+                        buffer.position(buffer.position()+_contentChunk.remaining());
+
+                        if (_handler.content(_contentChunk))
+                            return true;
+
+                        if(_contentPosition == _contentLength)
+                        {
+                            setState(State.END);
+                            if (_handler.messageComplete())
+                                return true;
+                        }
+                    }
+                    break;
+                }
+
+                case CHUNKED_CONTENT:
+                {
+                    ch=next(buffer);
+                    if (ch>HttpTokens.SPACE)
+                    {
+                        _chunkLength=TypeUtil.convertHexDigit(ch);
+                        _chunkPosition=0;
+                        setState(State.CHUNK_SIZE);
+                    }
+
+                    break;
+                }
+
+                case CHUNK_SIZE:
+                {
+                    ch=next(buffer);
+                    if (ch==0)
+                        break;
+                    if (ch == HttpTokens.LINE_FEED)
+                    {
+                        if (_chunkLength == 0)
+                        {
+                            setState(State.END);
+                            if (_handler.messageComplete())
+                                return true;
+                        }
+                        else
+                            setState(State.CHUNK);
+                    }
+                    else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
+                        setState(State.CHUNK_PARAMS);
+                    else
+                        _chunkLength=_chunkLength * 16 + TypeUtil.convertHexDigit(ch);
+                    break;
+                }
+
+                case CHUNK_PARAMS:
+                {
+                    ch=next(buffer);
+                    if (ch == HttpTokens.LINE_FEED)
+                    {
+                        if (_chunkLength == 0)
+                        {
+                            setState(State.END);
+                            if (_handler.messageComplete())
+                                return true;
+                        }
+                        else
+                            setState(State.CHUNK);
+                    }
+                    break;
+                }
+
+                case CHUNK:
+                {
+                    int chunk=_chunkLength - _chunkPosition;
+                    if (chunk == 0)
+                    {
+                        setState(State.CHUNKED_CONTENT);
+                    }
+                    else
+                    {
+                        _contentChunk=buffer.asReadOnlyBuffer();
+
+                        if (remaining > chunk)
+                            _contentChunk.limit(_contentChunk.position()+chunk);
+                        chunk=_contentChunk.remaining();
+
+                        _contentPosition += chunk;
+                        _chunkPosition += chunk;
+                        buffer.position(buffer.position()+chunk);
+                        if (_handler.content(_contentChunk))
+                            return true;
+                    }
+                    break;
+                }
+                
+                case CLOSED:
+                {
+                    BufferUtil.clear(buffer);
+                    return false;
+                }
+
+                default: 
+                    break;
+                    
+            }
+            
+            remaining=buffer.remaining();
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean isAtEOF()
+    {
+        return _eof;
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    public void atEOF()
+
+    {        
+        if (DEBUG)
+            LOG.debug("atEOF {}", this);
+        _eof=true;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public void close()
+    {
+        if (DEBUG)
+            LOG.debug("close {}", this);
+        setState(State.CLOSED);
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    public void reset()
+    {
+        if (DEBUG)
+            LOG.debug("reset {}", this);
+        // reset state
+        if (_state==State.CLOSED)
+            return;
+        if (_closed)
+        {
+            setState(State.CLOSED);
+            return;
+        }
+        
+        setState(State.START);
+        _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+        _contentLength=-1;
+        _contentPosition=0;
+        _responseStatus=0;
+        _contentChunk=null;
+        _headerBytes=0;
+        _host=false;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    protected void setState(State state)
+    {
+        if (DEBUG)
+            LOG.debug("{} --> {}",_state,state);
+        _state=state;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    public String toString()
+    {
+        return String.format("%s{s=%s,%d of %d}",
+                getClass().getSimpleName(),
+                _state,
+                _contentPosition,
+                _contentLength);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* Event Handler interface
+     * These methods return true if the caller should process the events
+     * so far received (eg return from parseNext and call HttpChannel.handle).
+     * If multiple callbacks are called in sequence (eg 
+     * headerComplete then messageComplete) from the same point in the parsing
+     * then it is sufficient for the caller to process the events only once.
+     */
+    public interface HttpHandler<T>
+    {
+        public boolean content(T item);
+
+        public boolean headerComplete();
+
+        public boolean messageComplete();
+
+        /**
+         * This is the method called by parser when a HTTP Header name and value is found
+         * @param field The field parsed
+         * @return True if the parser should return to its caller
+         */
+        public boolean parsedHeader(HttpField field);
+
+        /* ------------------------------------------------------------ */
+        /** Called to signal that an EOF was received unexpectedly
+         * during the parsing of a HTTP message
+         */
+        public void earlyEOF();
+
+        /* ------------------------------------------------------------ */
+        /** Called to signal that a bad HTTP message has been received.
+         * @param status The bad status to send
+         * @param reason The textual reason for badness
+         */
+        public void badMessage(int status, String reason);
+        
+        /* ------------------------------------------------------------ */
+        /** @return the size in bytes of the per parser header cache
+         */
+        public int getHeaderCacheSize();
+    }
+
+    public interface ProxyHandler 
+    {
+        void proxied(String protocol, String sAddr, String dAddr, int sPort, int dPort);
+    }
+    
+    public interface RequestHandler<T> extends HttpHandler<T>
+    {
+        /**
+         * This is the method called by parser when the HTTP request line is parsed
+         * @param method The method as enum if of a known type
+         * @param methodString The method as a string
+         * @param uri The raw bytes of the URI.  These are copied into a ByteBuffer that will not be changed until this parser is reset and reused.
+         * @param version
+         * @return true if handling parsing should return.
+         */
+        public abstract boolean startRequest(HttpMethod method, String methodString, ByteBuffer uri, HttpVersion version);
+
+        /**
+         * This is the method called by the parser after it has parsed the host header (and checked it's format). This is
+         * called after the {@link HttpHandler#parsedHeader(HttpField)} methods and before
+         * HttpHandler#headerComplete();
+         */
+        public abstract boolean parsedHostHeader(String host,int port);
+    }
+
+    public interface ResponseHandler<T> extends HttpHandler<T>
+    {
+        /**
+         * This is the method called by parser when the HTTP request line is parsed
+         */
+        public abstract boolean startResponse(HttpVersion version, int status, String reason);
+    }
+
+    public Trie<HttpField> getFieldCache()
+    {
+        return _connectionFields;
+    }
+
+    private String getProxyField(ByteBuffer buffer)
+    {
+        _string.setLength(0);
+        _length=0;
+        
+        while (buffer.hasRemaining())
+        {
+            // process each character
+            byte ch=next(buffer);
+            if (ch<=' ')
+                return _string.toString();
+            _string.append((char)ch);    
+        }
+        throw new BadMessage();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpScheme.java b/lib/jetty/org/eclipse/jetty/http/HttpScheme.java
new file mode 100644 (file)
index 0000000..13f2a8d
--- /dev/null
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Trie;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ */
+public enum HttpScheme
+{
+    HTTP("http"),
+    HTTPS("https"),
+    WS("ws"),
+    WSS("wss");
+
+    /* ------------------------------------------------------------ */
+    public final static Trie<HttpScheme> CACHE= new ArrayTrie<HttpScheme>();
+    static
+    {
+        for (HttpScheme version : HttpScheme.values())
+            CACHE.put(version.asString(),version);
+    }
+
+    private final String _string;
+    private final ByteBuffer _buffer;
+
+    /* ------------------------------------------------------------ */
+    HttpScheme(String s)
+    {
+        _string=s;
+        _buffer=BufferUtil.toBuffer(s);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteBuffer asByteBuffer()
+    {
+        return _buffer.asReadOnlyBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean is(String s)
+    {
+        return _string.equalsIgnoreCase(s);
+    }
+
+    public String asString()
+    {
+        return _string;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _string;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpStatus.java b/lib/jetty/org/eclipse/jetty/http/HttpStatus.java
new file mode 100644 (file)
index 0000000..e6ea1f7
--- /dev/null
@@ -0,0 +1,1037 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+/**
+ * <p>
+ * HttpStatusCode enum class, for status codes based on various HTTP RFCs. (see
+ * table below)
+ * </p>
+ *
+ * <table border="1" cellpadding="5">
+ * <tr>
+ * <th>Enum</th>
+ * <th>Code</th>
+ * <th>Message</th>
+ * <th>
+ * <a href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a></th>
+ * <th>
+ * <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a></th>
+ * <th>
+ * <a href="http://tools.ietf.org/html/rfc2518">RFC 2518 - WEBDAV</a></th>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Informational - 1xx</code></strong></td>
+ * <td colspan="5">{@link #isInformational(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #CONTINUE_100}</td>
+ * <td>100</td>
+ * <td>Continue</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.1.1">Sec. 10.1.1</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #SWITCHING_PROTOCOLS_101}</td>
+ * <td>101</td>
+ * <td>Switching Protocols</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.1.2">Sec. 10.1.2</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PROCESSING_102}</td>
+ * <td>102</td>
+ * <td>Processing</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.1">Sec. 10.1</a></td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Success - 2xx</code></strong></td>
+ * <td colspan="5">{@link #isSuccess(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #OK_200}</td>
+ * <td>200</td>
+ * <td>OK</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.1">Sec. 10.2.1</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #CREATED_201}</td>
+ * <td>201</td>
+ * <td>Created</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.2">Sec. 10.2.2</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #ACCEPTED_202}</td>
+ * <td>202</td>
+ * <td>Accepted</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.3">Sec. 10.2.3</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NON_AUTHORITATIVE_INFORMATION_203}</td>
+ * <td>203</td>
+ * <td>Non Authoritative Information</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.4">Sec. 10.2.4</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NO_CONTENT_204}</td>
+ * <td>204</td>
+ * <td>No Content</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.5">Sec. 10.2.5</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #RESET_CONTENT_205}</td>
+ * <td>205</td>
+ * <td>Reset Content</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.6">Sec. 10.2.6</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PARTIAL_CONTENT_206}</td>
+ * <td>206</td>
+ * <td>Partial Content</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.7">Sec. 10.2.7</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #MULTI_STATUS_207}</td>
+ * <td>207</td>
+ * <td>Multi-Status</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.2">Sec. 10.2</a></td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>207</strike></td>
+ * <td><strike>Partial Update OK</strike></td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-rev-01.txt"
+ * >draft/01</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Redirection - 3xx</code></strong></td>
+ * <td colspan="5">{@link #isRedirection(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #MULTIPLE_CHOICES_300}</td>
+ * <td>300</td>
+ * <td>Multiple Choices</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.1">Sec. 10.3.1</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #MOVED_PERMANENTLY_301}</td>
+ * <td>301</td>
+ * <td>Moved Permanently</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.2">Sec. 10.3.2</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #MOVED_TEMPORARILY_302}</td>
+ * <td>302</td>
+ * <td>Moved Temporarily</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>(now "<code>302 Found</code>")</td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #FOUND_302}</td>
+ * <td>302</td>
+ * <td>Found</td>
+ * <td>(was "<code>302 Moved Temporarily</code>")</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.3">Sec. 10.3.3</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #SEE_OTHER_303}</td>
+ * <td>303</td>
+ * <td>See Other</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.4">Sec. 10.3.4</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_MODIFIED_304}</td>
+ * <td>304</td>
+ * <td>Not Modified</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.5">Sec. 10.3.5</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #USE_PROXY_305}</td>
+ * <td>305</td>
+ * <td>Use Proxy</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.6">Sec. 10.3.6</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td>306</td>
+ * <td><em>(Unused)</em></td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.7">Sec. 10.3.7</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #TEMPORARY_REDIRECT_307}</td>
+ * <td>307</td>
+ * <td>Temporary Redirect</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.8">Sec. 10.3.8</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Client Error - 4xx</code></strong></td>
+ * <td colspan="5">{@link #isClientError(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #BAD_REQUEST_400}</td>
+ * <td>400</td>
+ * <td>Bad Request</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.1">Sec. 10.4.1</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #UNAUTHORIZED_401}</td>
+ * <td>401</td>
+ * <td>Unauthorized</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.2">Sec. 10.4.2</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PAYMENT_REQUIRED_402}</td>
+ * <td>402</td>
+ * <td>Payment Required</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.3">Sec. 10.4.3</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #FORBIDDEN_403}</td>
+ * <td>403</td>
+ * <td>Forbidden</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.4">Sec. 10.4.4</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_FOUND_404}</td>
+ * <td>404</td>
+ * <td>Not Found</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.5">Sec. 10.4.5</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #METHOD_NOT_ALLOWED_405}</td>
+ * <td>405</td>
+ * <td>Method Not Allowed</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.6">Sec. 10.4.6</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_ACCEPTABLE_406}</td>
+ * <td>406</td>
+ * <td>Not Acceptable</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.7">Sec. 10.4.7</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PROXY_AUTHENTICATION_REQUIRED_407}</td>
+ * <td>407</td>
+ * <td>Proxy Authentication Required</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.8">Sec. 10.4.8</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUEST_TIMEOUT_408}</td>
+ * <td>408</td>
+ * <td>Request Timeout</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.9">Sec. 10.4.9</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #CONFLICT_409}</td>
+ * <td>409</td>
+ * <td>Conflict</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.10">Sec. 10.4.10</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #GONE_410}</td>
+ * <td>410</td>
+ * <td>Gone</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.11">Sec. 10.4.11</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #LENGTH_REQUIRED_411}</td>
+ * <td>411</td>
+ * <td>Length Required</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.12">Sec. 10.4.12</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PRECONDITION_FAILED_412}</td>
+ * <td>412</td>
+ * <td>Precondition Failed</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.13">Sec. 10.4.13</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUEST_ENTITY_TOO_LARGE_413}</td>
+ * <td>413</td>
+ * <td>Request Entity Too Large</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.14">Sec. 10.4.14</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUEST_URI_TOO_LONG_414}</td>
+ * <td>414</td>
+ * <td>Request-URI Too Long</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.15">Sec. 10.4.15</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #UNSUPPORTED_MEDIA_TYPE_415}</td>
+ * <td>415</td>
+ * <td>Unsupported Media Type</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.16">Sec. 10.4.16</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUESTED_RANGE_NOT_SATISFIABLE_416}</td>
+ * <td>416</td>
+ * <td>Requested Range Not Satisfiable</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.17">Sec. 10.4.17</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXPECTATION_FAILED_417}</td>
+ * <td>417</td>
+ * <td>Expectation Failed</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.18">Sec. 10.4.18</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>418</strike></td>
+ * <td><strike>Reauthentication Required</strike></td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-http-v11-spec-rev-01#section-10.4.19"
+ * >draft/01</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>418</strike></td>
+ * <td><strike>Unprocessable Entity</strike></td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.3"
+ * >draft/05</a></td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>419</strike></td>
+ * <td><strike>Proxy Reauthentication Required</stike></td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-http-v11-spec-rev-01#section-10.4.20"
+ * >draft/01</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>419</strike></td>
+ * <td><strike>Insufficient Space on Resource</stike></td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.4"
+ * >draft/05</a></td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>420</strike></td>
+ * <td><strike>Method Failure</strike></td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.5"
+ * >draft/05</a></td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td>421</td>
+ * <td><em>(Unused)</em></td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #UNPROCESSABLE_ENTITY_422}</td>
+ * <td>422</td>
+ * <td>Unprocessable Entity</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.3">Sec. 10.3</a></td>
+ * </tr>
+ * <tr>
+ * <td>{@link #LOCKED_423}</td>
+ * <td>423</td>
+ * <td>Locked</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.4">Sec. 10.4</a></td>
+ * </tr>
+ * <tr>
+ * <td>{@link #FAILED_DEPENDENCY_424}</td>
+ * <td>424</td>
+ * <td>Failed Dependency</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.5">Sec. 10.5</a></td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Server Error - 5xx</code></strong></td>
+ * <td colspan="5">{@link #isServerError(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #INTERNAL_SERVER_ERROR_500}</td>
+ * <td>500</td>
+ * <td>Internal Server Error</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.1">Sec. 10.5.1</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_IMPLEMENTED_501}</td>
+ * <td>501</td>
+ * <td>Not Implemented</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.2">Sec. 10.5.2</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #BAD_GATEWAY_502}</td>
+ * <td>502</td>
+ * <td>Bad Gateway</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.3">Sec. 10.5.3</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #SERVICE_UNAVAILABLE_503}</td>
+ * <td>503</td>
+ * <td>Service Unavailable</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.4">Sec. 10.5.4</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #GATEWAY_TIMEOUT_504}</td>
+ * <td>504</td>
+ * <td>Gateway Timeout</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.5">Sec. 10.5.5</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #HTTP_VERSION_NOT_SUPPORTED_505}</td>
+ * <td>505</td>
+ * <td>HTTP Version Not Supported</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.6">Sec. 10.5.6</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td>506</td>
+ * <td><em>(Unused)</em></td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #INSUFFICIENT_STORAGE_507}</td>
+ * <td>507</td>
+ * <td>Insufficient Storage</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.6">Sec. 10.6</a></td>
+ * </tr>
+ *
+ * </table>
+ *
+ * @version $Id$
+ */
+public class HttpStatus
+{
+    public final static int NOT_SET_000 = 0;
+    public final static int CONTINUE_100 = 100;
+    public final static int SWITCHING_PROTOCOLS_101 = 101;
+    public final static int PROCESSING_102 = 102;
+
+    public final static int OK_200 = 200;
+    public final static int CREATED_201 = 201;
+    public final static int ACCEPTED_202 = 202;
+    public final static int NON_AUTHORITATIVE_INFORMATION_203 = 203;
+    public final static int NO_CONTENT_204 = 204;
+    public final static int RESET_CONTENT_205 = 205;
+    public final static int PARTIAL_CONTENT_206 = 206;
+    public final static int MULTI_STATUS_207 = 207;
+
+    public final static int MULTIPLE_CHOICES_300 = 300;
+    public final static int MOVED_PERMANENTLY_301 = 301;
+    public final static int MOVED_TEMPORARILY_302 = 302;
+    public final static int FOUND_302 = 302;
+    public final static int SEE_OTHER_303 = 303;
+    public final static int NOT_MODIFIED_304 = 304;
+    public final static int USE_PROXY_305 = 305;
+    public final static int TEMPORARY_REDIRECT_307 = 307;
+
+    public final static int BAD_REQUEST_400 = 400;
+    public final static int UNAUTHORIZED_401 = 401;
+    public final static int PAYMENT_REQUIRED_402 = 402;
+    public final static int FORBIDDEN_403 = 403;
+    public final static int NOT_FOUND_404 = 404;
+    public final static int METHOD_NOT_ALLOWED_405 = 405;
+    public final static int NOT_ACCEPTABLE_406 = 406;
+    public final static int PROXY_AUTHENTICATION_REQUIRED_407 = 407;
+    public final static int REQUEST_TIMEOUT_408 = 408;
+    public final static int CONFLICT_409 = 409;
+    public final static int GONE_410 = 410;
+    public final static int LENGTH_REQUIRED_411 = 411;
+    public final static int PRECONDITION_FAILED_412 = 412;
+    public final static int REQUEST_ENTITY_TOO_LARGE_413 = 413;
+    public final static int REQUEST_URI_TOO_LONG_414 = 414;
+    public final static int UNSUPPORTED_MEDIA_TYPE_415 = 415;
+    public final static int REQUESTED_RANGE_NOT_SATISFIABLE_416 = 416;
+    public final static int EXPECTATION_FAILED_417 = 417;
+    public final static int UNPROCESSABLE_ENTITY_422 = 422;
+    public final static int LOCKED_423 = 423;
+    public final static int FAILED_DEPENDENCY_424 = 424;
+
+    public final static int INTERNAL_SERVER_ERROR_500 = 500;
+    public final static int NOT_IMPLEMENTED_501 = 501;
+    public final static int BAD_GATEWAY_502 = 502;
+    public final static int SERVICE_UNAVAILABLE_503 = 503;
+    public final static int GATEWAY_TIMEOUT_504 = 504;
+    public final static int HTTP_VERSION_NOT_SUPPORTED_505 = 505;
+    public final static int INSUFFICIENT_STORAGE_507 = 507;
+
+    public static final int MAX_CODE = 507;
+
+
+    private static final Code[] codeMap = new Code[MAX_CODE+1];
+
+    static
+    {
+        for (Code code : Code.values())
+        {
+            codeMap[code._code] = code;
+        }
+    }
+
+
+    public enum Code
+    {
+        /*
+         * --------------------------------------------------------------------
+         * Informational messages in 1xx series. As defined by ... RFC 1945 -
+         * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+         */
+
+        /** <code>100 Continue</code> */
+        CONTINUE(CONTINUE_100, "Continue"),
+        /** <code>101 Switching Protocols</code> */
+        SWITCHING_PROTOCOLS(SWITCHING_PROTOCOLS_101, "Switching Protocols"),
+        /** <code>102 Processing</code> */
+        PROCESSING(PROCESSING_102, "Processing"),
+
+        /*
+         * --------------------------------------------------------------------
+         * Success messages in 2xx series. As defined by ... RFC 1945 - HTTP/1.0
+         * RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+         */
+
+        /** <code>200 OK</code> */
+        OK(OK_200, "OK"),
+        /** <code>201 Created</code> */
+        CREATED(CREATED_201, "Created"),
+        /** <code>202 Accepted</code> */
+        ACCEPTED(ACCEPTED_202, "Accepted"),
+        /** <code>203 Non Authoritative Information</code> */
+        NON_AUTHORITATIVE_INFORMATION(NON_AUTHORITATIVE_INFORMATION_203, "Non Authoritative Information"),
+        /** <code>204 No Content</code> */
+        NO_CONTENT(NO_CONTENT_204, "No Content"),
+        /** <code>205 Reset Content</code> */
+        RESET_CONTENT(RESET_CONTENT_205, "Reset Content"),
+        /** <code>206 Partial Content</code> */
+        PARTIAL_CONTENT(PARTIAL_CONTENT_206, "Partial Content"),
+        /** <code>207 Multi-Status</code> */
+        MULTI_STATUS(MULTI_STATUS_207, "Multi-Status"),
+
+        /*
+         * --------------------------------------------------------------------
+         * Redirection messages in 3xx series. As defined by ... RFC 1945 -
+         * HTTP/1.0 RFC 2616 - HTTP/1.1
+         */
+
+        /** <code>300 Mutliple Choices</code> */
+        MULTIPLE_CHOICES(MULTIPLE_CHOICES_300, "Multiple Choices"),
+        /** <code>301 Moved Permanently</code> */
+        MOVED_PERMANENTLY(MOVED_PERMANENTLY_301, "Moved Permanently"),
+        /** <code>302 Moved Temporarily</code> */
+        MOVED_TEMPORARILY(MOVED_TEMPORARILY_302, "Moved Temporarily"),
+        /** <code>302 Found</code> */
+        FOUND(FOUND_302, "Found"),
+        /** <code>303 See Other</code> */
+        SEE_OTHER(SEE_OTHER_303, "See Other"),
+        /** <code>304 Not Modified</code> */
+        NOT_MODIFIED(NOT_MODIFIED_304, "Not Modified"),
+        /** <code>305 Use Proxy</code> */
+        USE_PROXY(USE_PROXY_305, "Use Proxy"),
+        /** <code>307 Temporary Redirect</code> */
+        TEMPORARY_REDIRECT(TEMPORARY_REDIRECT_307, "Temporary Redirect"),
+
+        /*
+         * --------------------------------------------------------------------
+         * Client Error messages in 4xx series. As defined by ... RFC 1945 -
+         * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+         */
+
+        /** <code>400 Bad Request</code> */
+        BAD_REQUEST(BAD_REQUEST_400, "Bad Request"),
+        /** <code>401 Unauthorized</code> */
+        UNAUTHORIZED(UNAUTHORIZED_401, "Unauthorized"),
+        /** <code>402 Payment Required</code> */
+        PAYMENT_REQUIRED(PAYMENT_REQUIRED_402, "Payment Required"),
+        /** <code>403 Forbidden</code> */
+        FORBIDDEN(FORBIDDEN_403, "Forbidden"),
+        /** <code>404 Not Found</code> */
+        NOT_FOUND(NOT_FOUND_404, "Not Found"),
+        /** <code>405 Method Not Allowed</code> */
+        METHOD_NOT_ALLOWED(METHOD_NOT_ALLOWED_405, "Method Not Allowed"),
+        /** <code>406 Not Acceptable</code> */
+        NOT_ACCEPTABLE(NOT_ACCEPTABLE_406, "Not Acceptable"),
+        /** <code>407 Proxy Authentication Required</code> */
+        PROXY_AUTHENTICATION_REQUIRED(PROXY_AUTHENTICATION_REQUIRED_407, "Proxy Authentication Required"),
+        /** <code>408 Request Timeout</code> */
+        REQUEST_TIMEOUT(REQUEST_TIMEOUT_408, "Request Timeout"),
+        /** <code>409 Conflict</code> */
+        CONFLICT(CONFLICT_409, "Conflict"),
+        /** <code>410 Gone</code> */
+        GONE(GONE_410, "Gone"),
+        /** <code>411 Length Required</code> */
+        LENGTH_REQUIRED(LENGTH_REQUIRED_411, "Length Required"),
+        /** <code>412 Precondition Failed</code> */
+        PRECONDITION_FAILED(PRECONDITION_FAILED_412, "Precondition Failed"),
+        /** <code>413 Request Entity Too Large</code> */
+        REQUEST_ENTITY_TOO_LARGE(REQUEST_ENTITY_TOO_LARGE_413, "Request Entity Too Large"),
+        /** <code>414 Request-URI Too Long</code> */
+        REQUEST_URI_TOO_LONG(REQUEST_URI_TOO_LONG_414, "Request-URI Too Long"),
+        /** <code>415 Unsupported Media Type</code> */
+        UNSUPPORTED_MEDIA_TYPE(UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Media Type"),
+        /** <code>416 Requested Range Not Satisfiable</code> */
+        REQUESTED_RANGE_NOT_SATISFIABLE(REQUESTED_RANGE_NOT_SATISFIABLE_416, "Requested Range Not Satisfiable"),
+        /** <code>417 Expectation Failed</code> */
+        EXPECTATION_FAILED(EXPECTATION_FAILED_417, "Expectation Failed"),
+        /** <code>422 Unprocessable Entity</code> */
+        UNPROCESSABLE_ENTITY(UNPROCESSABLE_ENTITY_422, "Unprocessable Entity"),
+        /** <code>423 Locked</code> */
+        LOCKED(LOCKED_423, "Locked"),
+        /** <code>424 Failed Dependency</code> */
+        FAILED_DEPENDENCY(FAILED_DEPENDENCY_424, "Failed Dependency"),
+
+        /*
+         * --------------------------------------------------------------------
+         * Server Error messages in 5xx series. As defined by ... RFC 1945 -
+         * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+         */
+
+        /** <code>500 Server Error</code> */
+        INTERNAL_SERVER_ERROR(INTERNAL_SERVER_ERROR_500, "Server Error"),
+        /** <code>501 Not Implemented</code> */
+        NOT_IMPLEMENTED(NOT_IMPLEMENTED_501, "Not Implemented"),
+        /** <code>502 Bad Gateway</code> */
+        BAD_GATEWAY(BAD_GATEWAY_502, "Bad Gateway"),
+        /** <code>503 Service Unavailable</code> */
+        SERVICE_UNAVAILABLE(SERVICE_UNAVAILABLE_503, "Service Unavailable"),
+        /** <code>504 Gateway Timeout</code> */
+        GATEWAY_TIMEOUT(GATEWAY_TIMEOUT_504, "Gateway Timeout"),
+        /** <code>505 HTTP Version Not Supported</code> */
+        HTTP_VERSION_NOT_SUPPORTED(HTTP_VERSION_NOT_SUPPORTED_505, "HTTP Version Not Supported"),
+        /** <code>507 Insufficient Storage</code> */
+        INSUFFICIENT_STORAGE(INSUFFICIENT_STORAGE_507, "Insufficient Storage");
+
+        private final int _code;
+        private final String _message;
+
+        private Code(int code, String message)
+        {
+            this._code = code;
+            _message=message;
+        }
+
+        public int getCode()
+        {
+            return _code;
+        }
+
+        public String getMessage()
+        {
+            return _message;
+        }
+
+
+        public boolean equals(int code)
+        {
+            return (this._code == code);
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("[%03d %s]",this._code,this.getMessage());
+        }
+
+        /**
+         * Simple test against an code to determine if it falls into the
+         * <code>Informational</code> message category as defined in the <a
+         * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+         * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+         * HTTP/1.1</a>.
+         *
+         * @return true if within range of codes that belongs to
+         *         <code>Informational</code> messages.
+         */
+        public boolean isInformational()
+        {
+            return HttpStatus.isInformational(this._code);
+        }
+
+        /**
+         * Simple test against an code to determine if it falls into the
+         * <code>Success</code> message category as defined in the <a
+         * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+         * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+         * HTTP/1.1</a>.
+         *
+         * @return true if within range of codes that belongs to
+         *         <code>Success</code> messages.
+         */
+        public boolean isSuccess()
+        {
+            return HttpStatus.isSuccess(this._code);
+        }
+
+        /**
+         * Simple test against an code to determine if it falls into the
+         * <code>Redirection</code> message category as defined in the <a
+         * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+         * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+         * HTTP/1.1</a>.
+         *
+         * @return true if within range of codes that belongs to
+         *         <code>Redirection</code> messages.
+         */
+        public boolean isRedirection()
+        {
+            return HttpStatus.isRedirection(this._code);
+        }
+
+        /**
+         * Simple test against an code to determine if it falls into the
+         * <code>Client Error</code> message category as defined in the <a
+         * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+         * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+         * HTTP/1.1</a>.
+         *
+         * @return true if within range of codes that belongs to
+         *         <code>Client Error</code> messages.
+         */
+        public boolean isClientError()
+        {
+            return HttpStatus.isClientError(this._code);
+        }
+
+        /**
+         * Simple test against an code to determine if it falls into the
+         * <code>Server Error</code> message category as defined in the <a
+         * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+         * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+         * HTTP/1.1</a>.
+         *
+         * @return true if within range of codes that belongs to
+         *         <code>Server Error</code> messages.
+         */
+        public boolean isServerError()
+        {
+            return HttpStatus.isServerError(this._code);
+        }
+    }
+
+
+    /**
+     * Get the HttpStatusCode for a specific code
+     *
+     * @param code
+     *            the code to lookup.
+     * @return the {@link HttpStatus} if found, or null if not found.
+     */
+    public static Code getCode(int code)
+    {
+        if (code <= MAX_CODE)
+        {
+            return codeMap[code];
+        }
+        return null;
+    }
+
+    /**
+     * Get the status message for a specific code.
+     *
+     * @param code
+     *            the code to look up
+     * @return the specific message, or the code number itself if code
+     *         does not match known list.
+     */
+    public static String getMessage(int code)
+    {
+        Code codeEnum = getCode(code);
+        if (codeEnum != null)
+        {
+            return codeEnum.getMessage();
+        }
+        else
+        {
+            return Integer.toString(code);
+        }
+    }
+
+    /**
+     * Simple test against an code to determine if it falls into the
+     * <code>Informational</code> message category as defined in the <a
+     * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+     * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+     *
+     * @param code
+     *            the code to test.
+     * @return true if within range of codes that belongs to
+     *         <code>Informational</code> messages.
+     */
+    public static boolean isInformational(int code)
+    {
+        return ((100 <= code) && (code <= 199));
+    }
+
+    /**
+     * Simple test against an code to determine if it falls into the
+     * <code>Success</code> message category as defined in the <a
+     * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+     * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+     *
+     * @param code
+     *            the code to test.
+     * @return true if within range of codes that belongs to
+     *         <code>Success</code> messages.
+     */
+    public static boolean isSuccess(int code)
+    {
+        return ((200 <= code) && (code <= 299));
+    }
+
+    /**
+     * Simple test against an code to determine if it falls into the
+     * <code>Redirection</code> message category as defined in the <a
+     * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+     * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+     *
+     * @param code
+     *            the code to test.
+     * @return true if within range of codes that belongs to
+     *         <code>Redirection</code> messages.
+     */
+    public static boolean isRedirection(int code)
+    {
+        return ((300 <= code) && (code <= 399));
+    }
+
+    /**
+     * Simple test against an code to determine if it falls into the
+     * <code>Client Error</code> message category as defined in the <a
+     * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+     * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+     *
+     * @param code
+     *            the code to test.
+     * @return true if within range of codes that belongs to
+     *         <code>Client Error</code> messages.
+     */
+    public static boolean isClientError(int code)
+    {
+        return ((400 <= code) && (code <= 499));
+    }
+
+    /**
+     * Simple test against an code to determine if it falls into the
+     * <code>Server Error</code> message category as defined in the <a
+     * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+     * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+     *
+     * @param code
+     *            the code to test.
+     * @return true if within range of codes that belongs to
+     *         <code>Server Error</code> messages.
+     */
+    public static boolean isServerError(int code)
+    {
+        return ((500 <= code) && (code <= 599));
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpTester.java b/lib/jetty/org/eclipse/jetty/http/HttpTester.java
new file mode 100644 (file)
index 0000000..537c457
--- /dev/null
@@ -0,0 +1,367 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.http.HttpGenerator.RequestInfo;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+
+public class HttpTester
+{
+    private HttpTester()
+    {
+    }
+
+    public static Request newRequest()
+    {
+        return new Request();
+    }
+
+    public static Request parseRequest(String request)
+    {
+        Request r=new Request();
+        HttpParser parser =new HttpParser(r);
+        parser.parseNext(BufferUtil.toBuffer(request));
+        return r;
+    }
+
+    public static Request parseRequest(ByteBuffer request)
+    {
+        Request r=new Request();
+        HttpParser parser =new HttpParser(r);
+        parser.parseNext(request);
+        return r;
+    }
+
+    public static Response parseResponse(String response)
+    {
+        Response r=new Response();
+        HttpParser parser =new HttpParser(r);
+        parser.parseNext(BufferUtil.toBuffer(response));
+        return r;
+    }
+
+    public static Response parseResponse(ByteBuffer response)
+    {
+        Response r=new Response();
+        HttpParser parser =new HttpParser(r);
+        parser.parseNext(response);
+        return r;
+    }
+
+
+    public abstract static class Message extends HttpFields implements HttpParser.HttpHandler<ByteBuffer>
+    {
+        ByteArrayOutputStream _content;
+        HttpVersion _version=HttpVersion.HTTP_1_0;
+
+        public HttpVersion getVersion()
+        {
+            return _version;
+        }
+
+        public void setVersion(String version)
+        {
+            setVersion(HttpVersion.CACHE.get(version));
+        }
+
+        public void setVersion(HttpVersion version)
+        {
+            _version=version;
+        }
+
+        public void setContent(byte[] bytes)
+        {
+            try
+            {
+                _content=new ByteArrayOutputStream();
+                _content.write(bytes);
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void setContent(String content)
+        {
+            try
+            {
+                _content=new ByteArrayOutputStream();
+                _content.write(StringUtil.getBytes(content));
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void setContent(ByteBuffer content)
+        {
+            try
+            {
+                _content=new ByteArrayOutputStream();
+                _content.write(BufferUtil.toArray(content));
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+        @Override
+        public boolean parsedHeader(HttpField field)
+        {
+            put(field.getName(),field.getValue());
+            return false;
+        }
+
+        @Override
+        public boolean messageComplete()
+        {
+            return true;
+        }
+
+        @Override
+        public boolean headerComplete()
+        {
+            _content=new ByteArrayOutputStream();
+            return false;
+        }
+
+        @Override
+        public void earlyEOF()
+        {
+        }
+
+        @Override
+        public boolean content(ByteBuffer ref)
+        {
+            try
+            {
+                _content.write(BufferUtil.toArray(ref));
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+            return false;
+        }
+
+        @Override
+        public void badMessage(int status, String reason)
+        {
+            throw new RuntimeException(reason);
+        }
+
+        public ByteBuffer generate()
+        {
+            try
+            {
+                HttpGenerator generator = new HttpGenerator();
+                HttpGenerator.Info info = getInfo();
+                // System.err.println(info.getClass());
+                // System.err.println(info);
+
+                ByteArrayOutputStream out = new ByteArrayOutputStream();
+                ByteBuffer header=null;
+                ByteBuffer chunk=null;
+                ByteBuffer content=_content==null?null:ByteBuffer.wrap(_content.toByteArray());
+
+
+                loop: while(!generator.isEnd())
+                {
+                    HttpGenerator.Result result =  info instanceof RequestInfo
+                        ?generator.generateRequest((RequestInfo)info,header,chunk,content,true)
+                        :generator.generateResponse((ResponseInfo)info,header,chunk,content,true);
+                    switch(result)
+                    {
+                        case NEED_HEADER:
+                            header=BufferUtil.allocate(8192);
+                            continue;
+
+                        case NEED_CHUNK:
+                            chunk=BufferUtil.allocate(HttpGenerator.CHUNK_SIZE);
+                            continue;
+
+                        case NEED_INFO:
+                            throw new IllegalStateException();
+
+                        case FLUSH:
+                            if (BufferUtil.hasContent(header))
+                            {
+                                out.write(BufferUtil.toArray(header));
+                                BufferUtil.clear(header);
+                            }
+                            if (BufferUtil.hasContent(chunk))
+                            {
+                                out.write(BufferUtil.toArray(chunk));
+                                BufferUtil.clear(chunk);
+                            }
+                            if (BufferUtil.hasContent(content))
+                            {
+                                out.write(BufferUtil.toArray(content));
+                                BufferUtil.clear(content);
+                            }
+                            break;
+
+                        case SHUTDOWN_OUT:
+                            break loop;
+                    }
+                }
+
+                return ByteBuffer.wrap(out.toByteArray());
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+
+        }
+        abstract public HttpGenerator.Info getInfo();
+
+        @Override
+        public int getHeaderCacheSize()
+        {
+            return 0;
+        }
+
+    }
+
+    public static class Request extends Message implements HttpParser.RequestHandler<ByteBuffer>
+    {
+        private String _method;
+        private String _uri;
+
+        @Override
+        public boolean startRequest(HttpMethod method, String methodString, ByteBuffer uri, HttpVersion version)
+        {
+            _method=methodString;
+            _uri=BufferUtil.toUTF8String(uri);
+            _version=version;
+            return false;
+        }
+
+        public String getMethod()
+        {
+            return _method;
+        }
+
+        public String getUri()
+        {
+            return _uri;
+        }
+
+        public void setMethod(String method)
+        {
+            _method=method;
+        }
+
+        public void setURI(String uri)
+        {
+            _uri=uri;
+        }
+
+        @Override
+        public HttpGenerator.RequestInfo getInfo()
+        {
+            return new HttpGenerator.RequestInfo(_version,this,_content==null?0:_content.size(),_method,_uri);
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s %s %s\n%s\n",_method,_uri,_version,super.toString());
+        }
+
+        public void setHeader(String name, String value)
+        {
+            put(name,value);
+        }
+
+        @Override
+        public boolean parsedHostHeader(String host,int port)
+        {
+            return false;
+        }
+    }
+
+    public static class Response extends Message implements HttpParser.ResponseHandler<ByteBuffer>
+    {
+        private int _status;
+        private String _reason;
+
+        @Override
+        public boolean startResponse(HttpVersion version, int status, String reason)
+        {
+            _version=version;
+            _status=status;
+            _reason=reason;
+            return false;
+        }
+
+        public int getStatus()
+        {
+            return _status;
+        }
+
+        public String getReason()
+        {
+            return _reason;
+        }
+
+        public byte[] getContentBytes()
+        {
+            if (_content==null)
+                return null;
+            return _content.toByteArray();
+        }
+
+        public String getContent()
+        {
+            if (_content==null)
+                return null;
+            byte[] bytes=_content.toByteArray();
+
+            String content_type=get(HttpHeader.CONTENT_TYPE);
+            String encoding=MimeTypes.getCharsetFromContentType(content_type);
+            Charset charset=encoding==null?StandardCharsets.UTF_8:Charset.forName(encoding);
+
+            return new String(bytes,charset);
+        }
+
+        @Override
+        public HttpGenerator.ResponseInfo getInfo()
+        {
+            return new HttpGenerator.ResponseInfo(_version,this,_content==null?-1:_content.size(),_status,_reason,false);
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s %s %s\n%s\n",_version,_status,_reason,super.toString());
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpTokens.java b/lib/jetty/org/eclipse/jetty/http/HttpTokens.java
new file mode 100644 (file)
index 0000000..4138334
--- /dev/null
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+/**
+ * HTTP constants
+ */
+public interface HttpTokens
+{
+    // Terminal symbols.
+    static final byte COLON= (byte)':';
+    static final byte TAB= 0x09;
+    static final byte LINE_FEED= 0x0A;
+    static final byte CARRIAGE_RETURN= 0x0D;
+    static final byte SPACE= 0x20;
+    static final byte[] CRLF = {CARRIAGE_RETURN,LINE_FEED};
+    static final byte SEMI_COLON= (byte)';';
+
+    public enum EndOfContent { UNKNOWN_CONTENT,NO_CONTENT,EOF_CONTENT,CONTENT_LENGTH,CHUNKED_CONTENT,SELF_DEFINING_CONTENT }
+
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpURI.java b/lib/jetty/org/eclipse/jetty/http/HttpURI.java
new file mode 100644 (file)
index 0000000..afe925e
--- /dev/null
@@ -0,0 +1,784 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.util.Utf8StringBuilder;
+
+
+/* ------------------------------------------------------------ */
+/** Http URI.
+ * Parse a HTTP URI from a string or byte array.  Given a URI
+ * <code>http://user@host:port/path/info;param?query#fragment</code>
+ * this class will split it into the following undecoded optional elements:<ul>
+ * <li>{@link #getScheme()} - http:</li>
+ * <li>{@link #getAuthority()} - //name@host:port</li>
+ * <li>{@link #getHost()} - host</li>
+ * <li>{@link #getPort()} - port</li>
+ * <li>{@link #getPath()} - /path/info</li>
+ * <li>{@link #getParam()} - param</li>
+ * <li>{@link #getQuery()} - query</li>
+ * <li>{@link #getFragment()} - fragment</li>
+ * </ul>
+ *
+ */
+public class HttpURI
+{
+    private static final byte[] __empty={};
+    private final static int
+    START=0,
+    AUTH_OR_PATH=1,
+    SCHEME_OR_PATH=2,
+    AUTH=4,
+    IPV6=5,
+    PORT=6,
+    PATH=7,
+    PARAM=8,
+    QUERY=9,
+    ASTERISK=10;
+
+    final Charset _charset;
+    boolean _partial=false;
+    byte[] _raw=__empty;
+    String _rawString;
+    int _scheme;
+    int _authority;
+    int _host;
+    int _port;
+    int _portValue;
+    int _path;
+    int _param;
+    int _query;
+    int _fragment;
+    int _end;
+    boolean _encoded=false;
+
+    public HttpURI()
+    {
+        _charset = URIUtil.__CHARSET;
+    }
+
+    public HttpURI(Charset charset)
+    {
+        _charset = charset;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param parsePartialAuth If True, parse auth without prior scheme, else treat all URIs starting with / as paths
+     */
+    public HttpURI(boolean parsePartialAuth)
+    {
+        _partial=parsePartialAuth;
+        _charset = URIUtil.__CHARSET;
+    }
+
+    public HttpURI(String raw)
+    {
+        _rawString=raw;
+        byte[] b = raw.getBytes(StandardCharsets.UTF_8);
+        parse(b,0,b.length);
+        _charset = URIUtil.__CHARSET;
+    }
+
+    public HttpURI(byte[] raw,int offset, int length)
+    {
+        parse2(raw,offset,length);
+        _charset = URIUtil.__CHARSET;
+    }
+
+    public HttpURI(URI uri)
+    {
+        parse(uri.toASCIIString());
+        _charset = URIUtil.__CHARSET;
+    }
+
+    public void parse(String raw)
+    {
+        byte[] b = StringUtil.getUtf8Bytes(raw);
+        parse2(b,0,b.length);
+        _rawString=raw;
+    }
+
+    public void parseConnect(String raw)
+    {
+        byte[] b = StringUtil.getBytes(raw);
+        parseConnect(b,0,b.length);
+        _rawString=raw;
+    }
+
+    public void parse(byte[] raw,int offset, int length)
+    {
+        _rawString=null;
+        parse2(raw,offset,length);
+    }
+
+
+    public void parseConnect(byte[] raw,int offset, int length)
+    {
+        _rawString=null;
+        _encoded=false;
+        _raw=raw;
+        int i=offset;
+        int e=offset+length;
+        int state=AUTH;
+        _end=offset+length;
+        _scheme=offset;
+        _authority=offset;
+        _host=offset;
+        _port=_end;
+        _portValue=-1;
+        _path=_end;
+        _param=_end;
+        _query=_end;
+        _fragment=_end;
+
+        loop: while (i<e)
+        {
+            char c=(char)(0xff&_raw[i]);
+            int s=i++;
+
+            switch (state)
+            {
+                case AUTH:
+                {
+                    switch (c)
+                    {
+                        case ':':
+                        {
+                            _port = s;
+                            break loop;
+                        }
+                        case '[':
+                        {
+                            state = IPV6;
+                            break;
+                        }
+                    }
+                    continue;
+                }
+
+                case IPV6:
+                {
+                    switch (c)
+                    {
+                        case '/':
+                        {
+                            throw new IllegalArgumentException("No closing ']' for " + new String(_raw,offset,length,_charset));
+                        }
+                        case ']':
+                        {
+                            state = AUTH;
+                            break;
+                        }
+                    }
+
+                    continue;
+                }
+            }
+        }
+
+        if (_port<_path)
+            _portValue=TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10);
+        else
+            throw new IllegalArgumentException("No port");
+        _path=offset;
+    }
+
+
+    private void parse2(byte[] raw,int offset, int length)
+    {
+        _encoded=false;
+        _raw=raw;
+        int i=offset;
+        int e=offset+length;
+        int state=START;
+        int m=offset;
+        _end=offset+length;
+        _scheme=offset;
+        _authority=offset;
+        _host=offset;
+        _port=offset;
+        _portValue=-1;
+        _path=offset;
+        _param=_end;
+        _query=_end;
+        _fragment=_end;
+        while (i<e)
+        {
+            char c=(char)(0xff&_raw[i]);
+            int s=i++;
+
+            state: switch (state)
+            {
+                case START:
+                {
+                    m=s;
+                    switch(c)
+                    {
+                        case '/':
+                            state=AUTH_OR_PATH;
+                            break;
+                        case ';':
+                            _param=s;
+                            state=PARAM;
+                            break;
+                        case '?':
+                            _param=s;
+                            _query=s;
+                            state=QUERY;
+                            break;
+                        case '#':
+                            _param=s;
+                            _query=s;
+                            _fragment=s;
+                            break;
+                        case '*':
+                            _path=s;
+                            state=ASTERISK;
+                            break;
+
+                        default:
+                            state=SCHEME_OR_PATH;
+                    }
+
+                    continue;
+                }
+
+                case AUTH_OR_PATH:
+                {
+                    if ((_partial||_scheme!=_authority) && c=='/')
+                    {
+                        _host=i;
+                        _port=_end;
+                        _path=_end;
+                        state=AUTH;
+                    }
+                    else if (c==';' || c=='?' || c=='#')
+                    {
+                        i--;
+                        state=PATH;
+                    }
+                    else
+                    {
+                        _host=m;
+                        _port=m;
+                        state=PATH;
+                    }
+                    continue;
+                }
+
+                case SCHEME_OR_PATH:
+                {
+                    // short cut for http and https
+                    if (length>6 && c=='t')
+                    {
+                        if (_raw[offset+3]==':')
+                        {
+                            s=offset+3;
+                            i=offset+4;
+                            c=':';
+                        }
+                        else if (_raw[offset+4]==':')
+                        {
+                            s=offset+4;
+                            i=offset+5;
+                            c=':';
+                        }
+                        else if (_raw[offset+5]==':')
+                        {
+                            s=offset+5;
+                            i=offset+6;
+                            c=':';
+                        }
+                    }
+
+                    switch (c)
+                    {
+                        case ':':
+                        {
+                            m = i++;
+                            _authority = m;
+                            _path = m;
+                            c = (char)(0xff & _raw[i]);
+                            if (c == '/')
+                                state = AUTH_OR_PATH;
+                            else
+                            {
+                                _host = m;
+                                _port = m;
+                                state = PATH;
+                            }
+                            break;
+                        }
+
+                        case '/':
+                        {
+                            state = PATH;
+                            break;
+                        }
+
+                        case ';':
+                        {
+                            _param = s;
+                            state = PARAM;
+                            break;
+                        }
+
+                        case '?':
+                        {
+                            _param = s;
+                            _query = s;
+                            state = QUERY;
+                            break;
+                        }
+
+                        case '#':
+                        {
+                            _param = s;
+                            _query = s;
+                            _fragment = s;
+                            break;
+                        }
+                    }
+                    continue;
+                }
+
+                case AUTH:
+                {
+                    switch (c)
+                    {
+
+                        case '/':
+                        {
+                            m = s;
+                            _path = m;
+                            _port = _path;
+                            state = PATH;
+                            break;
+                        }
+                        case '@':
+                        {
+                            _host = i;
+                            break;
+                        }
+                        case ':':
+                        {
+                            _port = s;
+                            state = PORT;
+                            break;
+                        }
+                        case '[':
+                        {
+                            state = IPV6;
+                            break;
+                        }
+                    }
+                    continue;
+                }
+
+                case IPV6:
+                {
+                    switch (c)
+                    {
+                        case '/':
+                        {
+                            throw new IllegalArgumentException("No closing ']' for " + new String(_raw,offset,length,_charset));
+                        }
+                        case ']':
+                        {
+                            state = AUTH;
+                            break;
+                        }
+                    }
+
+                    continue;
+                }
+
+                case PORT:
+                {
+                    if (c=='/')
+                    {
+                        m=s;
+                        _path=m;
+                        if (_port<=_authority)
+                            _port=_path;
+                        state=PATH;
+                    }
+                    continue;
+                }
+
+                case PATH:
+                {
+                    switch (c)
+                    {
+                        case ';':
+                        {
+                            _param = s;
+                            state = PARAM;
+                            break;
+                        }
+                        case '?':
+                        {
+                            _param = s;
+                            _query = s;
+                            state = QUERY;
+                            break;
+                        }
+                        case '#':
+                        {
+                            _param = s;
+                            _query = s;
+                            _fragment = s;
+                            break state;
+                        }
+                        case '%':
+                        {
+                            _encoded=true;
+                        }
+                    }
+                    continue;
+                }
+
+                case PARAM:
+                {
+                    switch (c)
+                    {
+                        case '?':
+                        {
+                            _query = s;
+                            state = QUERY;
+                            break;
+                        }
+                        case '#':
+                        {
+                            _query = s;
+                            _fragment = s;
+                            break state;
+                        }
+                    }
+                    continue;
+                }
+
+                case QUERY:
+                {
+                    if (c=='#')
+                    {
+                        _fragment=s;
+                        break state;
+                    }
+                    continue;
+                }
+
+                case ASTERISK:
+                {
+                    throw new IllegalArgumentException("only '*'");
+                }
+            }
+        }
+
+        if (_port<_path)
+            _portValue=TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10);
+    }
+
+    public String getScheme()
+    {
+        if (_scheme==_authority)
+            return null;
+        int l=_authority-_scheme;
+        if (l==5 &&
+                _raw[_scheme]=='h' &&
+                _raw[_scheme+1]=='t' &&
+                _raw[_scheme+2]=='t' &&
+                _raw[_scheme+3]=='p' )
+            return HttpScheme.HTTP.asString();
+        if (l==6 &&
+                _raw[_scheme]=='h' &&
+                _raw[_scheme+1]=='t' &&
+                _raw[_scheme+2]=='t' &&
+                _raw[_scheme+3]=='p' &&
+                _raw[_scheme+4]=='s' )
+            return HttpScheme.HTTPS.asString();
+
+        return new String(_raw,_scheme,_authority-_scheme-1,_charset);
+    }
+
+    public String getAuthority()
+    {
+        if (_authority==_path)
+            return null;
+        return new String(_raw,_authority,_path-_authority,_charset);
+    }
+
+    public String getHost()
+    {
+        if (_host==_port)
+            return null;
+        if (_raw[_host]=='[')
+            return new String(_raw,_host+1,_port-_host-2,_charset);
+        return new String(_raw,_host,_port-_host,_charset);
+    }
+
+    public int getPort()
+    {
+        return _portValue;
+    }
+
+    public String getPath()
+    {
+        if (_path==_param)
+            return null;
+        return new String(_raw,_path,_param-_path,_charset);
+    }
+
+    public String getDecodedPath()
+    {
+        if (_path==_param)
+            return null;
+
+        Utf8StringBuilder utf8b=null;
+
+        for (int i=_path;i<_param;i++)
+        {
+            byte b = _raw[i];
+
+            if (b=='%')
+            {
+                if (utf8b==null)
+                {
+                    utf8b=new Utf8StringBuilder();
+                    utf8b.append(_raw,_path,i-_path);
+                }
+                
+                if ((i+2)>=_param)
+                    throw new IllegalArgumentException("Bad % encoding: "+this);
+                if (_raw[i+1]=='u')
+                {
+                    if ((i+5)>=_param)
+                        throw new IllegalArgumentException("Bad %u encoding: "+this);
+                    try
+                    {
+                        String unicode = new String(Character.toChars(TypeUtil.parseInt(_raw,i+2,4,16)));
+                        utf8b.getStringBuilder().append(unicode);
+                        i+=5;
+                    }
+                    catch(Exception e)
+                    {
+                        throw new RuntimeException(e);
+                    }
+                }
+                else
+                {
+                    b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16));
+                    utf8b.append(b);
+                    i+=2;
+                }
+                continue;
+            }
+            else if (utf8b!=null)
+            {
+                utf8b.append(b);
+            }
+        }
+
+        if (utf8b==null)
+            return StringUtil.toUTF8String(_raw, _path, _param-_path);
+        return utf8b.toString();
+    }
+
+    public String getDecodedPath(String encoding)
+    {
+        return getDecodedPath(Charset.forName(encoding));
+    }
+
+    public String getDecodedPath(Charset encoding)
+    {
+        if (_path==_param)
+            return null;
+
+        int length = _param-_path;
+        byte[] bytes=null;
+        int n=0;
+
+        for (int i=_path;i<_param;i++)
+        {
+            byte b = _raw[i];
+
+            if (b=='%')
+            {
+                if (bytes==null)
+                {
+                    bytes=new byte[length];
+                    System.arraycopy(_raw,_path,bytes,0,n);
+                }
+                
+                if ((i+2)>=_param)
+                    throw new IllegalArgumentException("Bad % encoding: "+this);
+                if (_raw[i+1]=='u')
+                {
+                    if ((i+5)>=_param)
+                        throw new IllegalArgumentException("Bad %u encoding: "+this);
+
+                    try
+                    {
+                        String unicode = new String(Character.toChars(TypeUtil.parseInt(_raw,i+2,4,16)));
+                        byte[] encoded = unicode.getBytes(encoding);
+                        System.arraycopy(encoded,0,bytes,n,encoded.length);
+                        n+=encoded.length;
+                        i+=5;
+                    }
+                    catch(Exception e)
+                    {
+                        throw new RuntimeException(e);
+                    }
+                }
+                else
+                {
+                    b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16));
+                    bytes[n++]=b;
+                    i+=2;
+                }
+                continue;
+            }
+            else if (bytes==null)
+            {
+                n++;
+                continue;
+            }
+
+            bytes[n++]=b;
+        }
+
+
+        if (bytes==null)
+            return new String(_raw,_path,_param-_path,encoding);
+
+        return new String(bytes,0,n,encoding);
+    }
+
+    public String getPathAndParam()
+    {
+        if (_path==_query)
+            return null;
+        return new String(_raw,_path,_query-_path,_charset);
+    }
+
+    public String getCompletePath()
+    {
+        if (_path==_end)
+            return null;
+        return new String(_raw,_path,_end-_path,_charset);
+    }
+
+    public String getParam()
+    {
+        if (_param==_query)
+            return null;
+        return new String(_raw,_param+1,_query-_param-1,_charset);
+    }
+
+    public String getQuery()
+    {
+        if (_query==_fragment)
+            return null;
+        return new String(_raw,_query+1,_fragment-_query-1,_charset);
+    }
+
+    public String getQuery(String encoding)
+    {
+        if (_query==_fragment)
+            return null;
+        return StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding);
+    }
+
+    public boolean hasQuery()
+    {
+        return (_fragment>_query);
+    }
+
+    public String getFragment()
+    {
+        if (_fragment==_end)
+            return null;
+        return new String(_raw,_fragment+1,_end-_fragment-1,_charset);
+    }
+
+    public void decodeQueryTo(MultiMap<String> parameters)
+    {
+        if (_query==_fragment)
+            return;
+        if (_charset.equals(StandardCharsets.UTF_8))
+            UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
+        else
+            UrlEncoded.decodeTo(new String(_raw,_query+1,_fragment-_query-1,_charset),parameters,_charset,-1);
+    }
+
+    public void decodeQueryTo(MultiMap<String> parameters, String encoding) throws UnsupportedEncodingException
+    {
+        if (_query==_fragment)
+            return;
+
+        if (encoding==null || StringUtil.isUTF8(encoding))
+            UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
+        else
+            UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding,-1);
+    }
+
+    public void decodeQueryTo(MultiMap<String> parameters, Charset encoding) throws UnsupportedEncodingException
+    {
+        if (_query==_fragment)
+            return;
+
+        if (encoding==null || StandardCharsets.UTF_8.equals(encoding))
+            UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
+        else
+            UrlEncoded.decodeTo(new String(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding,-1);
+    }
+
+    public void clear()
+    {
+        _scheme=_authority=_host=_port=_path=_param=_query=_fragment=_end=0;
+        _raw=__empty;
+        _rawString="";
+        _encoded=false;
+    }
+
+    @Override
+    public String toString()
+    {
+        if (_rawString==null)
+            _rawString=new String(_raw,_scheme,_end-_scheme,_charset);
+        return _rawString;
+    }
+
+    public void writeTo(Utf8StringBuilder buf)
+    {
+        buf.append(_raw,_scheme,_end-_scheme);
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpVersion.java b/lib/jetty/org/eclipse/jetty/http/HttpVersion.java
new file mode 100644 (file)
index 0000000..eb889e5
--- /dev/null
@@ -0,0 +1,172 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/* ------------------------------------------------------------------------------- */
+public enum HttpVersion
+{
+    HTTP_0_9("HTTP/0.9",9),
+    HTTP_1_0("HTTP/1.0",10),
+    HTTP_1_1("HTTP/1.1",11),
+    HTTP_2_0("HTTP/2.0",20);
+
+    /* ------------------------------------------------------------ */
+    public final static Trie<HttpVersion> CACHE= new ArrayTrie<HttpVersion>();
+    static
+    {
+        for (HttpVersion version : HttpVersion.values())
+            CACHE.put(version.toString(),version);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * Optimised lookup to find a Http Version and whitespace in a byte array.
+     * @param bytes Array containing ISO-8859-1 characters
+     * @param position The first valid index
+     * @param limit The first non valid index
+     * @return A HttpMethod if a match or null if no easy match.
+     */
+    public static HttpVersion lookAheadGet(byte[] bytes, int position, int limit)
+    {
+        int length=limit-position;
+        if (length<9)
+            return null;
+
+        if (bytes[position+4]=='/' && bytes[position+6]=='.' && Character.isWhitespace((char)bytes[position+8]) &&
+            ((bytes[position]=='H' &&  bytes[position+1]=='T' && bytes[position+2]=='T' && bytes[position+3]=='P') ||
+             (bytes[position]=='h' &&  bytes[position+1]=='t' && bytes[position+2]=='t' && bytes[position+3]=='p')))
+        {
+            switch(bytes[position+5])
+            {
+                case '1':
+                    switch(bytes[position+7])
+                    {
+                        case '0':
+                            return HTTP_1_0;
+                        case '1':
+                            return HTTP_1_1;
+                    }
+                    break;
+                case '2':
+                    switch(bytes[position+7])
+                    {
+                        case '0':
+                            return HTTP_2_0;
+                    }
+                    break;
+            }
+        }
+        
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * Optimised lookup to find a HTTP Version and trailing white space in a byte array.
+     * @param buffer buffer containing ISO-8859-1 characters
+     * @return A HttpVersion if a match or null if no easy match.
+     */
+    public static HttpVersion lookAheadGet(ByteBuffer buffer)
+    {
+        if (buffer.hasArray())
+            return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
+        return null;
+    }
+    
+    
+    private final String _string;
+    private final byte[] _bytes;
+    private final ByteBuffer _buffer;
+    private final int _version;
+
+    /* ------------------------------------------------------------ */
+    HttpVersion(String s,int version)
+    {
+        _string=s;
+        _bytes=StringUtil.getBytes(s);
+        _buffer=ByteBuffer.wrap(_bytes);
+        _version=version;
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] toBytes()
+    {
+        return _bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteBuffer toBuffer()
+    {
+        return _buffer.asReadOnlyBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getVersion()
+    {
+        return _version;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean is(String s)
+    {
+        return _string.equalsIgnoreCase(s);    
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String asString()
+    {
+        return _string;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _string;
+    }
+
+    /**
+     * Case insensitive fromString() conversion
+     * @param version the String to convert to enum constant
+     * @return the enum constant or null if version unknown
+     */
+    public static HttpVersion fromString(String version)
+    {
+        return CACHE.get(version);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static HttpVersion fromVersion(int version)
+    {
+        switch(version)
+        {
+            case 9: return HttpVersion.HTTP_0_9;
+            case 10: return HttpVersion.HTTP_1_0;
+            case 11: return HttpVersion.HTTP_1_1;
+            default: throw new IllegalArgumentException();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/MimeTypes.java b/lib/jetty/org/eclipse/jetty/http/MimeTypes.java
new file mode 100644 (file)
index 0000000..9cac4ab
--- /dev/null
@@ -0,0 +1,485 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * 
+ */
+public class MimeTypes
+{
+    public enum Type
+    {
+        FORM_ENCODED("application/x-www-form-urlencoded"),
+        MESSAGE_HTTP("message/http"),
+        MULTIPART_BYTERANGES("multipart/byteranges"),
+
+        TEXT_HTML("text/html"),
+        TEXT_PLAIN("text/plain"),
+        TEXT_XML("text/xml"),
+        TEXT_JSON("text/json",StandardCharsets.UTF_8),
+        APPLICATION_JSON("application/json",StandardCharsets.UTF_8),
+
+        TEXT_HTML_8859_1("text/html; charset=ISO-8859-1",TEXT_HTML),
+        TEXT_HTML_UTF_8("text/html; charset=UTF-8",TEXT_HTML),
+        
+        TEXT_PLAIN_8859_1("text/plain; charset=ISO-8859-1",TEXT_PLAIN),
+        TEXT_PLAIN_UTF_8("text/plain; charset=UTF-8",TEXT_PLAIN),
+        
+        TEXT_XML_8859_1("text/xml; charset=ISO-8859-1",TEXT_XML),
+        TEXT_XML_UTF_8("text/xml; charset=UTF-8",TEXT_XML),
+        
+        TEXT_JSON_8859_1("text/json; charset=ISO-8859-1",TEXT_JSON),
+        TEXT_JSON_UTF_8("text/json; charset=UTF-8",TEXT_JSON),
+        
+        APPLICATION_JSON_8859_1("text/json; charset=ISO-8859-1",APPLICATION_JSON),
+        APPLICATION_JSON_UTF_8("text/json; charset=UTF-8",APPLICATION_JSON);
+
+
+        /* ------------------------------------------------------------ */
+        private final String _string;
+        private final Type _base;
+        private final ByteBuffer _buffer;
+        private final Charset _charset;
+        private final boolean _assumedCharset;
+        private final HttpField _field;
+
+        /* ------------------------------------------------------------ */
+        Type(String s)
+        {
+            _string=s;
+            _buffer=BufferUtil.toBuffer(s);
+            _base=this;
+            _charset=null;
+            _assumedCharset=false;
+            _field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
+        } 
+
+        /* ------------------------------------------------------------ */
+        Type(String s,Type base)
+        {
+            _string=s;
+            _buffer=BufferUtil.toBuffer(s);
+            _base=base;
+            int i=s.indexOf("; charset=");
+            _charset=Charset.forName(s.substring(i+10));
+            _assumedCharset=false;
+            _field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
+        }
+
+        /* ------------------------------------------------------------ */
+        Type(String s,Charset cs)
+        {
+            _string=s;
+            _base=this;
+            _buffer=BufferUtil.toBuffer(s);
+            _charset=cs;
+            _assumedCharset=true;
+            _field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
+        }
+
+        /* ------------------------------------------------------------ */
+        public ByteBuffer asBuffer()
+        {
+            return _buffer.asReadOnlyBuffer();
+        }
+        
+        /* ------------------------------------------------------------ */
+        public Charset getCharset()
+        {
+            return _charset;
+        }
+        
+        /* ------------------------------------------------------------ */
+        public boolean is(String s)
+        {
+            return _string.equalsIgnoreCase(s);    
+        }
+
+        /* ------------------------------------------------------------ */
+        public String asString()
+        {
+            return _string;
+        }
+        
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString()
+        {
+            return _string;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isCharsetAssumed()
+        {
+            return _assumedCharset;
+        }
+
+        /* ------------------------------------------------------------ */
+        public HttpField getContentTypeField()
+        {
+            return _field;
+        }
+
+        /* ------------------------------------------------------------ */
+        public Type getBaseType()
+        {
+            return _base;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private static final Logger LOG = Log.getLogger(MimeTypes.class);
+    public  final static Trie<MimeTypes.Type> CACHE= new ArrayTrie<>(512);
+    private final static Trie<ByteBuffer> TYPES= new ArrayTrie<ByteBuffer>(512);
+    private final static Map<String,String> __dftMimeMap = new HashMap<String,String>();
+    private final static Map<String,String> __encodings = new HashMap<String,String>();
+
+    static
+    {
+        for (MimeTypes.Type type : MimeTypes.Type.values())
+        {
+            CACHE.put(type.toString(),type);
+            TYPES.put(type.toString(),type.asBuffer());
+
+            int charset=type.toString().indexOf(";charset=");
+            if (charset>0)
+            {
+                CACHE.put(type.toString().replace(";charset=","; charset="),type);
+                TYPES.put(type.toString().replace(";charset=","; charset="),type.asBuffer());
+            }
+        }
+
+        try
+        {
+            ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime");
+            Enumeration<String> i = mime.getKeys();
+            while(i.hasMoreElements())
+            {
+                String ext = i.nextElement();
+                String m = mime.getString(ext);
+                __dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m));
+            }
+        }
+        catch(MissingResourceException e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+        }
+
+        try
+        {
+            ResourceBundle encoding = ResourceBundle.getBundle("org/eclipse/jetty/http/encoding");
+            Enumeration<String> i = encoding.getKeys();
+            while(i.hasMoreElements())
+            {
+                String type = i.nextElement();
+                __encodings.put(type,encoding.getString(type));
+            }
+        }
+        catch(MissingResourceException e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    private final Map<String,String> _mimeMap=new HashMap<String,String>();
+
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     */
+    public MimeTypes()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized Map<String,String> getMimeMap()
+    {
+        return _mimeMap;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param mimeMap A Map of file extension to mime-type.
+     */
+    public void setMimeMap(Map<String,String> mimeMap)
+    {
+        _mimeMap.clear();
+        if (mimeMap!=null)
+        {
+            for (Entry<String, String> ext : mimeMap.entrySet())
+                _mimeMap.put(StringUtil.asciiToLowerCase(ext.getKey()),normalizeMimeType(ext.getValue()));
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the MIME type by filename extension.
+     * @param filename A file name
+     * @return MIME type matching the longest dot extension of the
+     * file name.
+     */
+    public String getMimeByExtension(String filename)
+    {
+        String type=null;
+
+        if (filename!=null)
+        {
+            int i=-1;
+            while(type==null)
+            {
+                i=filename.indexOf(".",i+1);
+
+                if (i<0 || i>=filename.length())
+                    break;
+
+                String ext=StringUtil.asciiToLowerCase(filename.substring(i+1));
+                if (_mimeMap!=null)
+                    type=_mimeMap.get(ext);
+                if (type==null)
+                    type=__dftMimeMap.get(ext);
+            }
+        }
+
+        if (type==null)
+        {
+            if (_mimeMap!=null)
+                type=_mimeMap.get("*");
+            if (type==null)
+                type=__dftMimeMap.get("*");
+        }
+
+        return type;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set a mime mapping
+     * @param extension
+     * @param type
+     */
+    public void addMimeMapping(String extension,String type)
+    {
+        _mimeMap.put(StringUtil.asciiToLowerCase(extension),normalizeMimeType(type));
+    }
+
+    /* ------------------------------------------------------------ */
+    public static Set<String> getKnownMimeTypes()
+    {
+        return new HashSet<>(__dftMimeMap.values());
+    }
+    
+    /* ------------------------------------------------------------ */
+    private static String normalizeMimeType(String type)
+    {
+        MimeTypes.Type t =CACHE.get(type);
+        if (t!=null)
+            return t.asString();
+
+        return StringUtil.asciiToLowerCase(type);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String getCharsetFromContentType(String value)
+    {
+        if (value==null)
+            return null;
+        int end=value.length();
+        int state=0;
+        int start=0;
+        boolean quote=false;
+        int i=0;
+        for (;i<end;i++)
+        {
+            char b = value.charAt(i);
+
+            if (quote && state!=10)
+            {
+                if ('"'==b)
+                    quote=false;
+                continue;
+            }
+
+            switch(state)
+            {
+                case 0:
+                    if ('"'==b)
+                    {
+                        quote=true;
+                        break;
+                    }
+                    if (';'==b)
+                        state=1;
+                    break;
+
+                case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;
+                case 2: if ('h'==b) state=3; else state=0;break;
+                case 3: if ('a'==b) state=4; else state=0;break;
+                case 4: if ('r'==b) state=5; else state=0;break;
+                case 5: if ('s'==b) state=6; else state=0;break;
+                case 6: if ('e'==b) state=7; else state=0;break;
+                case 7: if ('t'==b) state=8; else state=0;break;
+
+                case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
+
+                case 9:
+                    if (' '==b)
+                        break;
+                    if ('"'==b)
+                    {
+                        quote=true;
+                        start=i+1;
+                        state=10;
+                        break;
+                    }
+                    start=i;
+                    state=10;
+                    break;
+
+                case 10:
+                    if (!quote && (';'==b || ' '==b )||
+                            (quote && '"'==b ))
+                        return StringUtil.normalizeCharset(value,start,i-start);
+            }
+        }
+
+        if (state==10)
+            return StringUtil.normalizeCharset(value,start,i-start);
+
+        return null;
+    }
+
+    public static String inferCharsetFromContentType(String value)
+    {
+        return __encodings.get(value);
+    }
+    
+    public static String getContentTypeWithoutCharset(String value)
+    {
+        int end=value.length();
+        int state=0;
+        int start=0;
+        boolean quote=false;
+        int i=0;
+        StringBuilder builder=null;
+        for (;i<end;i++)
+        {
+            char b = value.charAt(i);
+
+            if ('"'==b)
+            {
+                if (quote)
+                {
+                    quote=false;
+                }
+                else
+                {
+                    quote=true;
+                }
+                
+                switch(state)
+                {
+                    case 11:
+                        builder.append(b);break;
+                    case 10:
+                        break;
+                    case 9:
+                        builder=new StringBuilder();
+                        builder.append(value,0,start+1);
+                        state=10;
+                        break;
+                    default:
+                        start=i;
+                        state=0;           
+                }
+                continue;
+            }
+            
+            if (quote)
+            {
+                if (builder!=null && state!=10)
+                    builder.append(b);
+                continue;
+            }
+
+            switch(state)
+            {
+                case 0:
+                    if (';'==b)
+                        state=1;
+                    else if (' '!=b)
+                        start=i;
+                    break;
+
+                case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;
+                case 2: if ('h'==b) state=3; else state=0;break;
+                case 3: if ('a'==b) state=4; else state=0;break;
+                case 4: if ('r'==b) state=5; else state=0;break;
+                case 5: if ('s'==b) state=6; else state=0;break;
+                case 6: if ('e'==b) state=7; else state=0;break;
+                case 7: if ('t'==b) state=8; else state=0;break;
+                case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
+
+                case 9:
+                    if (' '==b)
+                        break;
+                    builder=new StringBuilder();
+                    builder.append(value,0,start+1);
+                    state=10;
+                    break;
+
+                case 10:
+                    if (';'==b)
+                    {
+                        builder.append(b);
+                        state=11;
+                    }
+                    break;
+                case 11:
+                    if (' '!=b)
+                        builder.append(b);
+            }
+        }
+        if (builder==null)
+            return value;
+        return builder.toString();
+
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/PathMap.java b/lib/jetty/org/eclipse/jetty/http/PathMap.java
new file mode 100644 (file)
index 0000000..b78f572
--- /dev/null
@@ -0,0 +1,572 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.URIUtil;
+
+/* ------------------------------------------------------------ */
+/** URI path map to Object.
+ * This mapping implements the path specification recommended
+ * in the 2.2 Servlet API.
+ *
+ * Path specifications can be of the following forms:<PRE>
+ * /foo/bar           - an exact path specification.
+ * /foo/*             - a prefix path specification (must end '/*').
+ * *.ext              - a suffix path specification.
+ * /                  - the default path specification.
+ * ""                 - the / path specification
+ * </PRE>
+ * Matching is performed in the following order <NL>
+ * <LI>Exact match.
+ * <LI>Longest prefix match.
+ * <LI>Longest suffix match.
+ * <LI>default.
+ * </NL>
+ * Multiple path specifications can be mapped by providing a list of
+ * specifications. By default this class uses characters ":," as path
+ * separators, unless configured differently by calling the static
+ * method @see PathMap#setPathSpecSeparators(String)
+ * <P>
+ * Special characters within paths such as '?� and ';' are not treated specially
+ * as it is assumed they would have been either encoded in the original URL or
+ * stripped from the path.
+ * <P>
+ * This class is not synchronized.  If concurrent modifications are
+ * possible then it should be synchronized at a higher level.
+ *
+ *
+ */
+public class PathMap<O> extends HashMap<String,O>
+{
+    /* ------------------------------------------------------------ */
+    private static String __pathSpecSeparators = ":,";
+
+    /* ------------------------------------------------------------ */
+    /** Set the path spec separator.
+     * Multiple path specification may be included in a single string
+     * if they are separated by the characters set in this string.
+     * By default this class uses ":," characters as path separators.
+     * @param s separators
+     */
+    public static void setPathSpecSeparators(String s)
+    {
+        __pathSpecSeparators=s;
+    }
+
+    /* --------------------------------------------------------------- */
+    Trie<MappedEntry<O>> _prefixMap=new ArrayTernaryTrie<>(false);
+    Trie<MappedEntry<O>> _suffixMap=new ArrayTernaryTrie<>(false);
+    final Map<String,MappedEntry<O>> _exactMap=new HashMap<>();
+
+    List<MappedEntry<O>> _defaultSingletonList=null;
+    MappedEntry<O> _prefixDefault=null;
+    MappedEntry<O> _default=null;
+    boolean _nodefault=false;
+
+    /* --------------------------------------------------------------- */
+    public PathMap()
+    {
+        this(11);
+    }
+
+    /* --------------------------------------------------------------- */
+    public PathMap(boolean noDefault)
+    {
+        this(11, noDefault);
+    }
+
+    /* --------------------------------------------------------------- */
+    public PathMap(int capacity)
+    {
+        this(capacity, false);
+    }
+
+    /* --------------------------------------------------------------- */
+    private PathMap(int capacity, boolean noDefault)
+    {
+        super(capacity);
+        _nodefault=noDefault;
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Construct from dictionary PathMap.
+     */
+    public PathMap(Map<String, ? extends O> m)
+    {
+        putAll(m);
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Add a single path match to the PathMap.
+     * @param pathSpec The path specification, or comma separated list of
+     * path specifications.
+     * @param object The object the path maps to
+     */
+    @Override
+    public O put(String pathSpec, O object)
+    {
+        if ("".equals(pathSpec.trim()))
+        {
+            MappedEntry<O> entry = new MappedEntry<>("",object);
+            entry.setMapped("");
+            _exactMap.put("", entry);
+            return super.put("", object);
+        }
+
+        StringTokenizer tok = new StringTokenizer(pathSpec,__pathSpecSeparators);
+        O old =null;
+
+        while (tok.hasMoreTokens())
+        {
+            String spec=tok.nextToken();
+
+            if (!spec.startsWith("/") && !spec.startsWith("*."))
+                throw new IllegalArgumentException("PathSpec "+spec+". must start with '/' or '*.'");
+
+            old = super.put(spec,object);
+
+            // Make entry that was just created.
+            MappedEntry<O> entry = new MappedEntry<>(spec,object);
+
+            if (entry.getKey().equals(spec))
+            {
+                if (spec.equals("/*"))
+                    _prefixDefault=entry;
+                else if (spec.endsWith("/*"))
+                {
+                    String mapped=spec.substring(0,spec.length()-2);
+                    entry.setMapped(mapped);
+                    while (!_prefixMap.put(mapped,entry))
+                        _prefixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie<MappedEntry<O>>)_prefixMap,1.5);
+                }
+                else if (spec.startsWith("*."))
+                {
+                    String suffix=spec.substring(2);
+                    while(!_suffixMap.put(suffix,entry))
+                        _suffixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie<MappedEntry<O>>)_suffixMap,1.5);
+                }
+                else if (spec.equals(URIUtil.SLASH))
+                {
+                    if (_nodefault)
+                        _exactMap.put(spec,entry);
+                    else
+                    {
+                        _default=entry;
+                        _defaultSingletonList=Collections.singletonList(_default);
+                    }
+                }
+                else
+                {
+                    entry.setMapped(spec);
+                    _exactMap.put(spec,entry);
+                }
+            }
+        }
+
+        return old;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get object matched by the path.
+     * @param path the path.
+     * @return Best matched object or null.
+     */
+    public O match(String path)
+    {
+        MappedEntry<O> entry = getMatch(path);
+        if (entry!=null)
+            return entry.getValue();
+        return null;
+    }
+
+
+    /* --------------------------------------------------------------- */
+    /** Get the entry mapped by the best specification.
+     * @param path the path.
+     * @return Map.Entry of the best matched  or null.
+     */
+    public MappedEntry<O> getMatch(String path)
+    {
+        if (path==null)
+            return null;
+
+        int l=path.length();
+
+        MappedEntry<O> entry=null;
+
+        //special case
+        if (l == 1 && path.charAt(0)=='/')
+        {
+            entry = _exactMap.get("");
+            if (entry != null)
+                return entry;
+        }
+
+        // try exact match
+        entry=_exactMap.get(path);
+        if (entry!=null)
+            return entry;
+
+        // prefix search
+        int i=l;
+        final Trie<PathMap.MappedEntry<O>> prefix_map=_prefixMap;
+        while(i>=0)
+        {
+            entry=prefix_map.getBest(path,0,i);
+            if (entry==null)
+                break;
+            String key = entry.getKey();
+            if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
+                return entry;
+            i=key.length()-3;
+        }
+
+        // Prefix Default
+        if (_prefixDefault!=null)
+            return _prefixDefault;
+
+        // Extension search
+        i=0;
+        final Trie<PathMap.MappedEntry<O>> suffix_map=_suffixMap;
+        while ((i=path.indexOf('.',i+1))>0)
+        {
+            entry=suffix_map.get(path,i+1,l-i-1);
+            if (entry!=null)
+                return entry;
+        }
+
+        // Default
+        return _default;
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Get all entries matched by the path.
+     * Best match first.
+     * @param path Path to match
+     * @return List of Map.Entry instances key=pathSpec
+     */
+    public List<? extends Map.Entry<String,O>> getMatches(String path)
+    {
+        MappedEntry<O> entry;
+        List<MappedEntry<O>> entries=new ArrayList<>();
+
+        if (path==null)
+            return entries;
+        if (path.length()==0)
+            return _defaultSingletonList;
+
+        // try exact match
+        entry=_exactMap.get(path);
+        if (entry!=null)
+            entries.add(entry);
+
+        // prefix search
+        int l=path.length();
+        int i=l;
+        final Trie<PathMap.MappedEntry<O>> prefix_map=_prefixMap;
+        while(i>=0)
+        {
+            entry=prefix_map.getBest(path,0,i);
+            if (entry==null)
+                break;
+            String key = entry.getKey();
+            if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
+                entries.add(entry);
+
+            i=key.length()-3;
+        }
+
+        // Prefix Default
+        if (_prefixDefault!=null)
+            entries.add(_prefixDefault);
+
+        // Extension search
+        i=0;
+        final Trie<PathMap.MappedEntry<O>> suffix_map=_suffixMap;
+        while ((i=path.indexOf('.',i+1))>0)
+        {
+            entry=suffix_map.get(path,i+1,l-i-1);
+            if (entry!=null)
+                entries.add(entry);
+        }
+
+        // root match
+        if ("/".equals(path))
+        {
+            entry=_exactMap.get("");
+            if (entry!=null)
+                entries.add(entry);
+        }
+            
+        // Default
+        if (_default!=null)
+            entries.add(_default);
+
+        return entries;
+    }
+
+
+    /* --------------------------------------------------------------- */
+    /** Return whether the path matches any entries in the PathMap,
+     * excluding the default entry
+     * @param path Path to match
+     * @return Whether the PathMap contains any entries that match this
+     */
+    public boolean containsMatch(String path)
+    {
+        MappedEntry<?> match = getMatch(path);
+        return match!=null && !match.equals(_default);
+    }
+
+    /* --------------------------------------------------------------- */
+    @Override
+    public O remove(Object pathSpec)
+    {
+        if (pathSpec!=null)
+        {
+            String spec=(String) pathSpec;
+            if (spec.equals("/*"))
+                _prefixDefault=null;
+            else if (spec.endsWith("/*"))
+                _prefixMap.remove(spec.substring(0,spec.length()-2));
+            else if (spec.startsWith("*."))
+                _suffixMap.remove(spec.substring(2));
+            else if (spec.equals(URIUtil.SLASH))
+            {
+                _default=null;
+                _defaultSingletonList=null;
+            }
+            else
+                _exactMap.remove(spec);
+        }
+        return super.remove(pathSpec);
+    }
+
+    /* --------------------------------------------------------------- */
+    @Override
+    public void clear()
+    {
+        _exactMap.clear();
+        _prefixMap=new ArrayTernaryTrie<>(false);
+        _suffixMap=new ArrayTernaryTrie<>(false);
+        _default=null;
+        _defaultSingletonList=null;
+        _prefixDefault=null;
+        super.clear();
+    }
+
+    /* --------------------------------------------------------------- */
+    /**
+     * @return true if match.
+     */
+    public static boolean match(String pathSpec, String path)
+        throws IllegalArgumentException
+    {
+        return match(pathSpec, path, false);
+    }
+
+    /* --------------------------------------------------------------- */
+    /**
+     * @return true if match.
+     */
+    public static boolean match(String pathSpec, String path, boolean noDefault)
+        throws IllegalArgumentException
+    {
+        if (pathSpec.length()==0)
+            return "/".equals(path);
+            
+        char c = pathSpec.charAt(0);
+        if (c=='/')
+        {
+            if (!noDefault && pathSpec.length()==1 || pathSpec.equals(path))
+                return true;
+
+            if(isPathWildcardMatch(pathSpec, path))
+                return true;
+        }
+        else if (c=='*')
+            return path.regionMatches(path.length()-pathSpec.length()+1,
+                    pathSpec,1,pathSpec.length()-1);
+        return false;
+    }
+
+    /* --------------------------------------------------------------- */
+    private static boolean isPathWildcardMatch(String pathSpec, String path)
+    {
+        // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar"
+        int cpl=pathSpec.length()-2;
+        if (pathSpec.endsWith("/*") && path.regionMatches(0,pathSpec,0,cpl))
+        {
+            if (path.length()==cpl || '/'==path.charAt(cpl))
+                return true;
+        }
+        return false;
+    }
+
+
+    /* --------------------------------------------------------------- */
+    /** Return the portion of a path that matches a path spec.
+     * @return null if no match at all.
+     */
+    public static String pathMatch(String pathSpec, String path)
+    {
+        char c = pathSpec.charAt(0);
+
+        if (c=='/')
+        {
+            if (pathSpec.length()==1)
+                return path;
+
+            if (pathSpec.equals(path))
+                return path;
+
+            if (isPathWildcardMatch(pathSpec, path))
+                return path.substring(0,pathSpec.length()-2);
+        }
+        else if (c=='*')
+        {
+            if (path.regionMatches(path.length()-(pathSpec.length()-1),
+                    pathSpec,1,pathSpec.length()-1))
+                return path;
+        }
+        return null;
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Return the portion of a path that is after a path spec.
+     * @return The path info string
+     */
+    public static String pathInfo(String pathSpec, String path)
+    {
+        if ("".equals(pathSpec))
+            return path; //servlet 3 spec sec 12.2 will be '/'
+
+        char c = pathSpec.charAt(0);
+
+        if (c=='/')
+        {
+            if (pathSpec.length()==1)
+                return null;
+
+            boolean wildcard = isPathWildcardMatch(pathSpec, path);
+
+            // handle the case where pathSpec uses a wildcard and path info is "/*"
+            if (pathSpec.equals(path) && !wildcard)
+                return null;
+
+            if (wildcard)
+            {
+                if (path.length()==pathSpec.length()-2)
+                    return null;
+                return path.substring(pathSpec.length()-2);
+            }
+        }
+        return null;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Relative path.
+     * @param base The base the path is relative to.
+     * @param pathSpec The spec of the path segment to ignore.
+     * @param path the additional path
+     * @return base plus path with pathspec removed
+     */
+    public static String relativePath(String base,
+            String pathSpec,
+            String path )
+    {
+        String info=pathInfo(pathSpec,path);
+        if (info==null)
+            info=path;
+
+        if( info.startsWith( "./"))
+            info = info.substring( 2);
+        if( base.endsWith( URIUtil.SLASH))
+            if( info.startsWith( URIUtil.SLASH))
+                path = base + info.substring(1);
+            else
+                path = base + info;
+        else
+            if( info.startsWith( URIUtil.SLASH))
+                path = base + info;
+            else
+                path = base + URIUtil.SLASH + info;
+        return path;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class MappedEntry<O> implements Map.Entry<String,O>
+    {
+        private final String key;
+        private final O value;
+        private String mapped;
+
+        MappedEntry(String key, O value)
+        {
+            this.key=key;
+            this.value=value;
+        }
+
+        @Override
+        public String getKey()
+        {
+            return key;
+        }
+
+        @Override
+        public O getValue()
+        {
+            return value;
+        }
+
+        @Override
+        public O setValue(O o)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String toString()
+        {
+            return key+"="+value;
+        }
+
+        public String getMapped()
+        {
+            return mapped;
+        }
+
+        void setMapped(String mapped)
+        {
+            this.mapped = mapped;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/encoding.properties b/lib/jetty/org/eclipse/jetty/http/encoding.properties
new file mode 100644 (file)
index 0000000..311c802
--- /dev/null
@@ -0,0 +1,4 @@
+text/html      = ISO-8859-1
+text/plain     = ISO-8859-1
+text/xml       = UTF-8
+text/json   = UTF-8
diff --git a/lib/jetty/org/eclipse/jetty/http/mime.properties b/lib/jetty/org/eclipse/jetty/http/mime.properties
new file mode 100644 (file)
index 0000000..b270989
--- /dev/null
@@ -0,0 +1,183 @@
+ai=application/postscript
+aif=audio/x-aiff
+aifc=audio/x-aiff
+aiff=audio/x-aiff
+apk=application/vnd.android.package-archive
+asc=text/plain
+asf=video/x.ms.asf
+asx=video/x.ms.asx
+au=audio/basic
+avi=video/x-msvideo
+bcpio=application/x-bcpio
+bin=application/octet-stream
+cab=application/x-cabinet
+cdf=application/x-netcdf
+class=application/java-vm
+cpio=application/x-cpio
+cpt=application/mac-compactpro
+crt=application/x-x509-ca-cert
+csh=application/x-csh
+css=text/css
+csv=text/comma-separated-values
+dcr=application/x-director
+dir=application/x-director
+dll=application/x-msdownload
+dms=application/octet-stream
+doc=application/msword
+dtd=application/xml-dtd
+dvi=application/x-dvi
+dxr=application/x-director
+eps=application/postscript
+etx=text/x-setext
+exe=application/octet-stream
+ez=application/andrew-inset
+gif=image/gif
+gtar=application/x-gtar
+gz=application/gzip
+gzip=application/gzip
+hdf=application/x-hdf
+hqx=application/mac-binhex40
+htc=text/x-component
+htm=text/html
+html=text/html
+ice=x-conference/x-cooltalk
+ico=image/x-icon
+ief=image/ief
+iges=model/iges
+igs=model/iges
+jad=text/vnd.sun.j2me.app-descriptor
+jar=application/java-archive
+java=text/plain
+jnlp=application/x-java-jnlp-file
+jpe=image/jpeg
+jpeg=image/jpeg
+jpg=image/jpeg
+js=application/javascript
+json=application/json
+jsp=text/html
+kar=audio/midi
+latex=application/x-latex
+lha=application/octet-stream
+lzh=application/octet-stream
+man=application/x-troff-man
+mathml=application/mathml+xml
+me=application/x-troff-me
+mesh=model/mesh
+mid=audio/midi
+midi=audio/midi
+mif=application/vnd.mif
+mol=chemical/x-mdl-molfile
+mov=video/quicktime
+movie=video/x-sgi-movie
+mp2=audio/mpeg
+mp3=audio/mpeg
+mpe=video/mpeg
+mpeg=video/mpeg
+mpg=video/mpeg
+mpga=audio/mpeg
+ms=application/x-troff-ms
+msh=model/mesh
+msi=application/octet-stream
+nc=application/x-netcdf
+oda=application/oda
+odb=application/vnd.oasis.opendocument.database
+odc=application/vnd.oasis.opendocument.chart
+odf=application/vnd.oasis.opendocument.formula
+odg=application/vnd.oasis.opendocument.graphics
+odi=application/vnd.oasis.opendocument.image
+odm=application/vnd.oasis.opendocument.text-master
+odp=application/vnd.oasis.opendocument.presentation
+ods=application/vnd.oasis.opendocument.spreadsheet
+odt=application/vnd.oasis.opendocument.text
+ogg=application/ogg
+otc=application/vnd.oasis.opendocument.chart-template
+otf=application/vnd.oasis.opendocument.formula-template
+otg=application/vnd.oasis.opendocument.graphics-template
+oth=application/vnd.oasis.opendocument.text-web
+oti=application/vnd.oasis.opendocument.image-template
+otp=application/vnd.oasis.opendocument.presentation-template
+ots=application/vnd.oasis.opendocument.spreadsheet-template
+ott=application/vnd.oasis.opendocument.text-template
+pbm=image/x-portable-bitmap
+pdb=chemical/x-pdb
+pdf=application/pdf
+pgm=image/x-portable-graymap
+pgn=application/x-chess-pgn
+png=image/png
+pnm=image/x-portable-anymap
+ppm=image/x-portable-pixmap
+pps=application/vnd.ms-powerpoint
+ppt=application/vnd.ms-powerpoint
+ps=application/postscript
+qml=text/x-qml
+qt=video/quicktime
+ra=audio/x-pn-realaudio
+ram=audio/x-pn-realaudio
+ras=image/x-cmu-raster
+rdf=application/rdf+xml
+rgb=image/x-rgb
+rm=audio/x-pn-realaudio
+roff=application/x-troff
+rpm=application/x-rpm
+rtf=application/rtf
+rtx=text/richtext
+rv=video/vnd.rn-realvideo
+ser=application/java-serialized-object
+sgm=text/sgml
+sgml=text/sgml
+sh=application/x-sh
+shar=application/x-shar
+silo=model/mesh
+sit=application/x-stuffit
+skd=application/x-koan
+skm=application/x-koan
+skp=application/x-koan
+skt=application/x-koan
+smi=application/smil
+smil=application/smil
+snd=audio/basic
+spl=application/x-futuresplash
+src=application/x-wais-source
+sv4cpio=application/x-sv4cpio
+sv4crc=application/x-sv4crc
+svg=image/svg+xml
+swf=application/x-shockwave-flash
+t=application/x-troff
+tar=application/x-tar
+tar.gz=application/x-gtar
+tcl=application/x-tcl
+tex=application/x-tex
+texi=application/x-texinfo
+texinfo=application/x-texinfo
+tgz=application/x-gtar
+tif=image/tiff
+tiff=image/tiff
+tr=application/x-troff
+tsv=text/tab-separated-values
+txt=text/plain
+ustar=application/x-ustar
+vcd=application/x-cdlink
+vrml=model/vrml
+vxml=application/voicexml+xml
+wav=audio/x-wav
+wbmp=image/vnd.wap.wbmp
+wml=text/vnd.wap.wml
+wmlc=application/vnd.wap.wmlc
+wmls=text/vnd.wap.wmlscript
+wmlsc=application/vnd.wap.wmlscriptc
+wrl=model/vrml
+wtls-ca-certificate=application/vnd.wap.wtls-ca-certificate
+xbm=image/x-xbitmap
+xht=application/xhtml+xml
+xhtml=application/xhtml+xml
+xls=application/vnd.ms-excel
+xml=application/xml
+xpm=image/x-xpixmap
+xsd=application/xml
+xsl=application/xml
+xslt=application/xslt+xml
+xul=application/vnd.mozilla.xul+xml
+xwd=image/x-xwindowdump
+xyz=chemical/x-xyz
+z=application/compress
+zip=application/zip
diff --git a/lib/jetty/org/eclipse/jetty/http/package-info.java b/lib/jetty/org/eclipse/jetty/http/package-info.java
new file mode 100644 (file)
index 0000000..825422a
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Http : Tools for Http processing
+ */
+package org.eclipse.jetty.http;
+
diff --git a/lib/jetty/org/eclipse/jetty/http/useragents b/lib/jetty/org/eclipse/jetty/http/useragents
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lib/jetty/org/eclipse/jetty/io/AbstractConnection.java b/lib/jetty/org/eclipse/jetty/io/AbstractConnection.java
new file mode 100644 (file)
index 0000000..7f8e9f8
--- /dev/null
@@ -0,0 +1,587 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+
+/**
+ * <p>A convenience base implementation of {@link Connection}.</p>
+ * <p>This class uses the capabilities of the {@link EndPoint} API to provide a
+ * more traditional style of async reading.  A call to {@link #fillInterested()}
+ * will schedule a callback to {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)}
+ * as appropriate.</p>
+ */
+public abstract class AbstractConnection implements Connection
+{
+    private static final Logger LOG = Log.getLogger(AbstractConnection.class);
+    
+    public static final boolean EXECUTE_ONFILLABLE=true;
+
+    private final List<Listener> listeners = new CopyOnWriteArrayList<>();
+    private final AtomicReference<State> _state = new AtomicReference<>(IDLE);
+    private final long _created=System.currentTimeMillis();
+    private final EndPoint _endPoint;
+    private final Executor _executor;
+    private final Callback _readCallback;
+    private final boolean _executeOnfillable;
+    private int _inputBufferSize=2048;
+
+    protected AbstractConnection(EndPoint endp, Executor executor)
+    {
+        this(endp,executor,EXECUTE_ONFILLABLE);
+    }
+    
+    protected AbstractConnection(EndPoint endp, Executor executor, final boolean executeOnfillable)
+    {
+        if (executor == null)
+            throw new IllegalArgumentException("Executor must not be null!");
+        _endPoint = endp;
+        _executor = executor;
+        _readCallback = new ReadCallback();
+        _executeOnfillable=executeOnfillable;
+        _state.set(IDLE);
+    }
+
+    @Override
+    public void addListener(Listener listener)
+    {
+        listeners.add(listener);
+    }
+
+    public int getInputBufferSize()
+    {
+        return _inputBufferSize;
+    }
+
+    public void setInputBufferSize(int inputBufferSize)
+    {
+        _inputBufferSize = inputBufferSize;
+    }
+
+    protected Executor getExecutor()
+    {
+        return _executor;
+    }
+    
+    protected void failedCallback(final Callback callback, final Throwable x)
+    {
+        if (NonBlockingThread.isNonBlockingThread())
+        {
+            try
+            {
+                getExecutor().execute(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        callback.failed(x);
+                    }
+                });
+            }
+            catch(RejectedExecutionException e)
+            {
+                LOG.debug(e);
+                callback.failed(x);
+            }
+        }
+        else
+        {
+            callback.failed(x);
+        }
+    }
+    
+    /**
+     * <p>Utility method to be called to register read interest.</p>
+     * <p>After a call to this method, {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)}
+     * will be called back as appropriate.</p>
+     * @see #onFillable()
+     */
+    public void fillInterested()
+    {
+        LOG.debug("fillInterested {}",this);           
+        
+        while(true)
+        {
+            State state=_state.get();
+            if (next(state,state.fillInterested()))
+                break;
+        }
+    }
+    
+    public void fillInterested(Callback callback)
+    {
+        LOG.debug("fillInterested {}",this);
+
+        while(true)
+        {
+            State state=_state.get();
+            // TODO yuck
+            if (state instanceof FillingInterestedCallback && ((FillingInterestedCallback)state)._callback==callback)
+                break;
+            State next=new FillingInterestedCallback(callback,state);
+            if (next(state,next))
+                break;
+        }
+    }
+    
+    /**
+     * <p>Callback method invoked when the endpoint is ready to be read.</p>
+     * @see #fillInterested()
+     */
+    public abstract void onFillable();
+
+    /**
+     * <p>Callback method invoked when the endpoint failed to be ready to be read.</p>
+     * @param cause the exception that caused the failure
+     */
+    protected void onFillInterestedFailed(Throwable cause)
+    {
+        LOG.debug("{} onFillInterestedFailed {}", this, cause);
+        if (_endPoint.isOpen())
+        {
+            boolean close = true;
+            if (cause instanceof TimeoutException)
+                close = onReadTimeout();
+            if (close)
+            {
+                if (_endPoint.isOutputShutdown())
+                    _endPoint.close();
+                else
+                    _endPoint.shutdownOutput();
+            }
+        }
+
+        if (_endPoint.isOpen())
+            fillInterested();        
+    }
+
+    /**
+     * <p>Callback method invoked when the endpoint failed to be ready to be read after a timeout</p>
+     * @return true to signal that the endpoint must be closed, false to keep the endpoint open
+     */
+    protected boolean onReadTimeout()
+    {
+        return true;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        LOG.debug("onOpen {}", this);
+
+        for (Listener listener : listeners)
+            listener.onOpened(this);
+    }
+
+    @Override
+    public void onClose()
+    {
+        LOG.debug("onClose {}",this);
+
+        for (Listener listener : listeners)
+            listener.onClosed(this);
+    }
+
+    @Override
+    public EndPoint getEndPoint()
+    {
+        return _endPoint;
+    }
+
+    @Override
+    public void close()
+    {
+        getEndPoint().close();
+    }
+
+    @Override
+    public int getMessagesIn()
+    {
+        return -1;
+    }
+
+    @Override
+    public int getMessagesOut()
+    {
+        return -1;
+    }
+
+    @Override
+    public long getBytesIn()
+    {
+        return -1;
+    }
+
+    @Override
+    public long getBytesOut()
+    {
+        return -1;
+    }
+
+    @Override
+    public long getCreatedTimeStamp()
+    {
+        return _created;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}", getClass().getSimpleName(), hashCode(), _state.get());
+    }
+    
+    public boolean next(State state, State next)
+    {
+        if (next==null)
+            return true;
+        if(_state.compareAndSet(state,next))
+        {
+            LOG.debug("{}-->{} {}",state,next,this);
+            if (next!=state)
+                next.onEnter(AbstractConnection.this);
+            return true;
+        }
+        return false;
+    }
+    
+    private static final class IdleState extends State
+    {
+        private IdleState()
+        {
+            super("IDLE");
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return FILL_INTERESTED;
+        }
+    }
+
+
+    private static final class FillInterestedState extends State
+    {
+        private FillInterestedState()
+        {
+            super("FILL_INTERESTED");
+        }
+
+        @Override
+        public void onEnter(AbstractConnection connection)
+        {
+            connection.getEndPoint().fillInterested(connection._readCallback);
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return this;
+        }
+
+        @Override
+        public State onFillable()
+        {
+            return FILLING;
+        }
+
+        @Override
+        State onFailed()
+        {
+            return IDLE;
+        }
+    }
+
+
+    private static final class RefillingState extends State
+    {
+        private RefillingState()
+        {
+            super("REFILLING");
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return FILLING_FILL_INTERESTED;
+        }
+
+        @Override
+        public State onFilled()
+        {
+            return IDLE;
+        }
+    }
+
+
+    private static final class FillingFillInterestedState extends State
+    {
+        private FillingFillInterestedState(String name)
+        {
+            super(name);
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return this;
+        }
+
+        State onFilled()
+        {
+            return FILL_INTERESTED;
+        }
+    }
+
+
+    private static final class FillingState extends State
+    {
+        private FillingState()
+        {
+            super("FILLING");
+        }
+
+        @Override
+        public void onEnter(AbstractConnection connection)
+        {
+            if (connection._executeOnfillable)
+                connection.getExecutor().execute(connection._runOnFillable);
+            else
+                connection._runOnFillable.run();
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return FILLING_FILL_INTERESTED;
+        }
+
+        @Override
+        public State onFilled()
+        {
+            return IDLE;
+        }
+    }
+
+
+    public static class State
+    {
+        private final String _name;
+        State(String name)
+        {
+            _name=name;
+        }
+
+        @Override
+        public String toString()
+        {
+            return _name;
+        }
+        
+        void onEnter(AbstractConnection connection)
+        {
+        }
+        
+        State fillInterested()
+        {
+            throw new IllegalStateException(this.toString());
+        }
+
+        State onFillable()
+        {
+            throw new IllegalStateException(this.toString());
+        }
+
+        State onFilled()
+        {
+            throw new IllegalStateException(this.toString());
+        }
+        
+        State onFailed()
+        {
+            throw new IllegalStateException(this.toString());
+        }
+    }
+    
+
+    public static final State IDLE=new IdleState();
+    
+    public static final State FILL_INTERESTED=new FillInterestedState();
+    
+    public static final State FILLING=new FillingState();
+    
+    public static final State REFILLING=new RefillingState();
+
+    public static final State FILLING_FILL_INTERESTED=new FillingFillInterestedState("FILLING_FILL_INTERESTED");
+    
+    public class NestedState extends State
+    {
+        private final State _nested;
+        
+        NestedState(State nested)
+        {
+            super("NESTED("+nested+")");
+            _nested=nested;
+        }
+        NestedState(String name,State nested)
+        {
+            super(name+"("+nested+")");
+            _nested=nested;
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return new NestedState(_nested.fillInterested());
+        }
+
+        @Override
+        State onFillable()
+        {
+            return new NestedState(_nested.onFillable());
+        }
+        
+        @Override
+        State onFilled()
+        {
+            return new NestedState(_nested.onFilled());
+        }
+    }
+    
+    
+    public class FillingInterestedCallback extends NestedState
+    {
+        private final Callback _callback;
+        
+        FillingInterestedCallback(Callback callback,State nested)
+        {
+            super("FILLING_INTERESTED_CALLBACK",nested==FILLING?REFILLING:nested);
+            _callback=callback;
+        }
+
+        @Override
+        void onEnter(final AbstractConnection connection)
+        {
+            Callback callback=new Callback()
+            {
+                @Override
+                public void succeeded()
+                {
+                    while(true)
+                    {
+                        State state = connection._state.get();
+                        if (!(state instanceof NestedState))
+                            break;
+                        State nested=((NestedState)state)._nested;
+                        if (connection.next(state,nested))
+                            break;
+                    }
+                    _callback.succeeded();
+                }
+
+                @Override
+                public void failed(Throwable x)
+                {
+                    while(true)
+                    {
+                        State state = connection._state.get();
+                        if (!(state instanceof NestedState))
+                            break;
+                        State nested=((NestedState)state)._nested;
+                        if (connection.next(state,nested))
+                            break;
+                    }
+                    _callback.failed(x);
+                }  
+            };
+            
+            connection.getEndPoint().fillInterested(callback);
+        }
+    }
+    
+    private final Runnable _runOnFillable = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                onFillable();
+            }
+            finally
+            {
+                while(true)
+                {
+                    State state=_state.get();
+                    if (next(state,state.onFilled()))
+                        break;
+                }
+            }
+        }
+    };
+    
+    
+    private class ReadCallback implements Callback
+    {   
+        @Override
+        public void succeeded()
+        {
+            while(true)
+            {
+                State state=_state.get();
+                if (next(state,state.onFillable()))
+                    break;
+            }
+        }
+
+        @Override
+        public void failed(final Throwable x)
+        {
+            _executor.execute(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    while(true)
+                    {
+                        State state=_state.get();
+                        if (next(state,state.onFailed()))
+                            break;
+                    }
+                    onFillInterestedFailed(x);
+                }
+            });
+        }
+        
+        @Override
+        public String toString()
+        {
+            return String.format("AC.ReadCB@%x{%s}", AbstractConnection.this.hashCode(),AbstractConnection.this);
+        }
+    };
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/AbstractEndPoint.java b/lib/jetty/org/eclipse/jetty/io/AbstractEndPoint.java
new file mode 100644 (file)
index 0000000..8fa2cc8
--- /dev/null
@@ -0,0 +1,180 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
+{
+    private static final Logger LOG = Log.getLogger(AbstractEndPoint.class);
+    private final long _created=System.currentTimeMillis();
+    private final InetSocketAddress _local;
+    private final InetSocketAddress _remote;
+    private volatile Connection _connection;
+
+    private final FillInterest _fillInterest = new FillInterest()
+    {
+        @Override
+        protected boolean needsFill() throws IOException
+        {
+            return AbstractEndPoint.this.needsFill();
+        }
+    };
+    
+    private final WriteFlusher _writeFlusher = new WriteFlusher(this)
+    {
+        @Override
+        protected void onIncompleteFlushed()
+        {
+            AbstractEndPoint.this.onIncompleteFlush();
+        }
+    };
+
+    protected AbstractEndPoint(Scheduler scheduler,InetSocketAddress local,InetSocketAddress remote)
+    {
+        super(scheduler);
+        _local=local;
+        _remote=remote;
+    }
+
+    @Override
+    public long getCreatedTimeStamp()
+    {
+        return _created;
+    }
+
+    @Override
+    public InetSocketAddress getLocalAddress()
+    {
+        return _local;
+    }
+
+    @Override
+    public InetSocketAddress getRemoteAddress()
+    {
+        return _remote;
+    }
+    
+    @Override
+    public Connection getConnection()
+    {
+        return _connection;
+    }
+
+    @Override
+    public void setConnection(Connection connection)
+    {
+        _connection = connection;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        LOG.debug("onOpen {}",this);
+        super.onOpen();
+    }
+
+    @Override
+    public void onClose()
+    {
+        super.onClose();
+        LOG.debug("onClose {}",this);
+        _writeFlusher.onClose();
+        _fillInterest.onClose();
+    }
+    
+    @Override
+    public void close()
+    {
+        onClose();
+    }
+
+    @Override
+    public void fillInterested(Callback callback) throws IllegalStateException
+    {
+        notIdle();
+        _fillInterest.register(callback);
+    }
+
+    @Override
+    public void write(Callback callback, ByteBuffer... buffers) throws IllegalStateException
+    {
+        _writeFlusher.write(callback, buffers);
+    }
+
+    protected abstract void onIncompleteFlush();
+
+    protected abstract boolean needsFill() throws IOException;
+
+    protected FillInterest getFillInterest()
+    {
+        return _fillInterest;
+    }
+
+    protected WriteFlusher getWriteFlusher()
+    {
+        return _writeFlusher;
+    }
+
+    @Override
+    protected void onIdleExpired(TimeoutException timeout)
+    {
+        boolean output_shutdown=isOutputShutdown();
+        boolean input_shutdown=isInputShutdown();
+        boolean fillFailed = _fillInterest.onFail(timeout);
+        boolean writeFailed = _writeFlusher.onFail(timeout);
+        
+        // If the endpoint is half closed and there was no onFail handling, the close here
+        // This handles the situation where the connection has completed its close handling 
+        // and the endpoint is half closed, but the other party does not complete the close.
+        // This perhaps should not check for half closed, however the servlet spec case allows
+        // for a dispatched servlet or suspended request to extend beyond the connections idle 
+        // time.  So if this test would always close an idle endpoint that is not handled, then 
+        // we would need a mode to ignore timeouts for some HTTP states
+        if (isOpen() && (output_shutdown || input_shutdown) && !(fillFailed || writeFailed))
+            close();
+        else 
+            LOG.debug("Ignored idle endpoint {}",this);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s<->%d,%s,%s,%s,%s,%s,%d,%s}",
+                getClass().getSimpleName(),
+                hashCode(),
+                getRemoteAddress(),
+                getLocalAddress().getPort(),
+                isOpen()?"Open":"CLOSED",
+                isInputShutdown()?"ISHUT":"in",
+                isOutputShutdown()?"OSHUT":"out",
+                _fillInterest.isInterested()?"R":"-",
+                _writeFlusher.isInProgress()?"W":"-",
+                getIdleTimeout(),
+                getConnection()==null?null:getConnection().getClass().getSimpleName());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ArrayByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/ArrayByteBufferPool.java
new file mode 100644 (file)
index 0000000..fa3a01d
--- /dev/null
@@ -0,0 +1,133 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class ArrayByteBufferPool implements ByteBufferPool
+{
+    private final int _min;
+    private final Bucket[] _direct;
+    private final Bucket[] _indirect;
+    private final int _inc;
+
+    public ArrayByteBufferPool()
+    {
+        this(0,1024,64*1024);
+    }
+
+    public ArrayByteBufferPool(int minSize, int increment, int maxSize)
+    {
+        if (minSize>=increment)
+            throw new IllegalArgumentException("minSize >= increment");
+        if ((maxSize%increment)!=0 || increment>=maxSize)
+            throw new IllegalArgumentException("increment must be a divisor of maxSize");
+        _min=minSize;
+        _inc=increment;
+
+        _direct=new Bucket[maxSize/increment];
+        _indirect=new Bucket[maxSize/increment];
+
+        int size=0;
+        for (int i=0;i<_direct.length;i++)
+        {
+            size+=_inc;
+            _direct[i]=new Bucket(size);
+            _indirect[i]=new Bucket(size);
+        }
+    }
+
+    @Override
+    public ByteBuffer acquire(int size, boolean direct)
+    {
+        Bucket bucket = bucketFor(size,direct);
+        ByteBuffer buffer = bucket==null?null:bucket._queue.poll();
+
+        if (buffer == null)
+        {
+            int capacity = bucket==null?size:bucket._size;
+            buffer = direct ? BufferUtil.allocateDirect(capacity) : BufferUtil.allocate(capacity);
+        }
+
+        return buffer;
+    }
+
+    @Override
+    public void release(ByteBuffer buffer)
+    {
+        if (buffer!=null)
+        {    
+            Bucket bucket = bucketFor(buffer.capacity(),buffer.isDirect());
+            if (bucket!=null)
+            {
+                BufferUtil.clear(buffer);
+                bucket._queue.offer(buffer);
+            }
+        }
+    }
+
+    public void clear()
+    {
+        for (int i=0;i<_direct.length;i++)
+        {
+            _direct[i]._queue.clear();
+            _indirect[i]._queue.clear();
+        }
+    }
+
+    private Bucket bucketFor(int size,boolean direct)
+    {
+        if (size<=_min)
+            return null;
+        int b=(size-1)/_inc;
+        if (b>=_direct.length)
+            return null;
+        Bucket bucket = direct?_direct[b]:_indirect[b];
+                
+        return bucket;
+    }
+
+    public static class Bucket
+    {
+        public final int _size;
+        public final Queue<ByteBuffer> _queue= new ConcurrentLinkedQueue<>();
+
+        Bucket(int size)
+        {
+            _size=size;
+        }
+        
+        @Override
+        public String toString()
+        {
+            return String.format("Bucket@%x{%d,%d}",hashCode(),_size,_queue.size());
+        }
+    }
+    
+
+    // Package local for testing
+    Bucket[] bucketsFor(boolean direct)
+    {
+        return direct ? _direct : _indirect;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ByteArrayEndPoint.java b/lib/jetty/org/eclipse/jetty/io/ByteArrayEndPoint.java
new file mode 100644 (file)
index 0000000..4b8c527
--- /dev/null
@@ -0,0 +1,409 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+/* ------------------------------------------------------------ */
+/** ByteArrayEndPoint.
+ *
+ */
+public class ByteArrayEndPoint extends AbstractEndPoint
+{
+    static final Logger LOG = Log.getLogger(ByteArrayEndPoint.class);
+    public final static InetSocketAddress NOIP=new InetSocketAddress(0);
+
+    protected volatile ByteBuffer _in;
+    protected volatile ByteBuffer _out;
+    protected volatile boolean _ishut;
+    protected volatile boolean _oshut;
+    protected volatile boolean _closed;
+    protected volatile boolean _growOutput;
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ByteArrayEndPoint()
+    {
+        this(null,0,null,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ByteArrayEndPoint(byte[] input, int outputSize)
+    {
+        this(null,0,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ByteArrayEndPoint(String input, int outputSize)
+    {
+        this(null,0,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteArrayEndPoint(Scheduler scheduler, long idleTimeoutMs)
+    {
+        this(scheduler,idleTimeoutMs,null,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteArrayEndPoint(Scheduler timer, long idleTimeoutMs, byte[] input, int outputSize)
+    {
+        this(timer,idleTimeoutMs,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteArrayEndPoint(Scheduler timer, long idleTimeoutMs, String input, int outputSize)
+    {
+        this(timer,idleTimeoutMs,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteArrayEndPoint(Scheduler timer, long idleTimeoutMs, ByteBuffer input, ByteBuffer output)
+    {
+        super(timer,NOIP,NOIP);
+        _in=input==null?BufferUtil.EMPTY_BUFFER:input;
+        _out=output==null?BufferUtil.allocate(1024):output;
+        setIdleTimeout(idleTimeoutMs);
+    }
+
+
+
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void onIncompleteFlush()
+    {
+        // Don't need to do anything here as takeOutput does the signalling.
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected boolean needsFill() throws IOException
+    {
+        if (_closed)
+            throw new ClosedChannelException();
+        return _in == null || BufferUtil.hasContent(_in);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the in.
+     */
+    public ByteBuffer getIn()
+    {
+        return _in;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public void setInputEOF()
+    {
+        _in = null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param in The in to set.
+     */
+    public void setInput(ByteBuffer in)
+    {
+        _in = in;
+        if (in == null || BufferUtil.hasContent(in))
+            getFillInterest().fillable();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setInput(String s)
+    {
+        setInput(BufferUtil.toBuffer(s,StandardCharsets.UTF_8));
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setInput(String s,Charset charset)
+    {
+        setInput(BufferUtil.toBuffer(s,charset));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the out.
+     */
+    public ByteBuffer getOutput()
+    {
+        return _out;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the out.
+     */
+    public String getOutputString()
+    {
+        return getOutputString(StandardCharsets.UTF_8);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the out.
+     */
+    public String getOutputString(Charset charset)
+    {
+        return BufferUtil.toString(_out,charset);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the out.
+     */
+    public ByteBuffer takeOutput()
+    {
+        ByteBuffer b=_out;
+        _out=BufferUtil.allocate(b.capacity());
+        getWriteFlusher().completeWrite();
+        return b;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the out.
+     */
+    public String takeOutputString()
+    {
+        return takeOutputString(StandardCharsets.UTF_8);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the out.
+     */
+    public String takeOutputString(Charset charset)
+    {
+        ByteBuffer buffer=takeOutput();
+        return BufferUtil.toString(buffer,charset);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param out The out to set.
+     */
+    public void setOutput(ByteBuffer out)
+    {
+        _out = out;
+        getWriteFlusher().completeWrite();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#isOpen()
+     */
+    @Override
+    public boolean isOpen()
+    {
+        return !_closed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    @Override
+    public boolean isInputShutdown()
+    {
+        return _ishut||_closed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    @Override
+    public boolean isOutputShutdown()
+    {
+        return _oshut||_closed;
+    }
+
+    /* ------------------------------------------------------------ */
+    private void shutdownInput()
+    {
+        _ishut=true;
+        if (_oshut)
+            close();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#shutdownOutput()
+     */
+    @Override
+    public void shutdownOutput()
+    {
+        _oshut=true;
+        if (_ishut)
+            close();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#close()
+     */
+    @Override
+    public void close()
+    {
+        super.close();
+        _closed=true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return <code>true</code> if there are bytes remaining to be read from the encoded input
+     */
+    public boolean hasMore()
+    {
+        return getOutput().position()>0;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#fill(org.eclipse.io.Buffer)
+     */
+    @Override
+    public int fill(ByteBuffer buffer) throws IOException
+    {
+        if (_closed)
+            throw new EofException("CLOSED");
+        if (_in==null)
+            shutdownInput();
+        if (_ishut)
+            return -1;
+        int filled=BufferUtil.append(buffer,_in);
+        if (filled>0)
+            notIdle();
+        return filled;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer)
+     */
+    @Override
+    public boolean flush(ByteBuffer... buffers) throws IOException
+    {
+        if (_closed)
+            throw new IOException("CLOSED");
+        if (_oshut)
+            throw new IOException("OSHUT");
+
+        boolean flushed=true;
+        boolean idle=true;
+
+        for (ByteBuffer b : buffers)
+        {
+            if (BufferUtil.hasContent(b))
+            {
+                if (_growOutput && b.remaining()>BufferUtil.space(_out))
+                {
+                    BufferUtil.compact(_out);
+                    if (b.remaining()>BufferUtil.space(_out))
+                    {
+                        ByteBuffer n = BufferUtil.allocate(_out.capacity()+b.remaining()*2);
+                        BufferUtil.append(n,_out);
+                        _out=n;
+                    }
+                }
+
+                if (BufferUtil.append(_out,b)>0)
+                    idle=false;
+
+                if (BufferUtil.hasContent(b))
+                {
+                    flushed=false;
+                    break;
+                }
+            }
+        }
+        if (!idle)
+            notIdle();
+        return flushed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public void reset()
+    {
+        getFillInterest().onClose();
+        getWriteFlusher().onClose();
+        _ishut=false;
+        _oshut=false;
+        _closed=false;
+        _in=null;
+        BufferUtil.clear(_out);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getConnection()
+     */
+    @Override
+    public Object getTransport()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the growOutput
+     */
+    public boolean isGrowOutput()
+    {
+        return _growOutput;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param growOutput the growOutput to set
+     */
+    public void setGrowOutput(boolean growOutput)
+    {
+        _growOutput=growOutput;
+    }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/ByteBufferPool.java
new file mode 100644 (file)
index 0000000..302adde
--- /dev/null
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+
+/**
+ * <p>A {@link ByteBuffer} pool.</p>
+ * <p>Acquired buffers may be {@link #release(ByteBuffer) released} but they do not need to;
+ * if they are released, they may be recycled and reused, otherwise they will be garbage
+ * collected as usual.</p>
+ */
+public interface ByteBufferPool
+{
+    /**
+     * <p>Requests a {@link ByteBuffer} of the given size.</p>
+     * <p>The returned buffer may have a bigger capacity than the size being
+     * requested but it will have the limit set to the given size.</p>
+     *
+     * @param size   the size of the buffer
+     * @param direct whether the buffer must be direct or not
+     * @return the requested buffer
+     * @see #release(ByteBuffer)
+     */
+    public ByteBuffer acquire(int size, boolean direct);
+
+    /**
+     * <p>Returns a {@link ByteBuffer}, usually obtained with {@link #acquire(int, boolean)}
+     * (but not necessarily), making it available for recycling and reuse.</p>
+     *
+     * @param buffer the buffer to return
+     * @see #acquire(int, boolean)
+     */
+    public void release(ByteBuffer buffer);
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ChannelEndPoint.java b/lib/jetty/org/eclipse/jetty/io/ChannelEndPoint.java
new file mode 100644 (file)
index 0000000..c65ca0c
--- /dev/null
@@ -0,0 +1,229 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.SocketChannel;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * Channel End Point.
+ * <p>Holds the channel and socket for an NIO endpoint.
+ */
+public class ChannelEndPoint extends AbstractEndPoint
+{
+    private static final Logger LOG = Log.getLogger(ChannelEndPoint.class);
+
+    private final ByteChannel _channel;
+    private final Socket _socket;
+    private volatile boolean _ishut;
+    private volatile boolean _oshut;
+
+    public ChannelEndPoint(Scheduler scheduler,SocketChannel channel)
+    {
+        super(scheduler,
+            (InetSocketAddress)channel.socket().getLocalSocketAddress(),
+            (InetSocketAddress)channel.socket().getRemoteSocketAddress());
+        _channel = channel;
+        _socket=channel.socket();
+    }
+
+    @Override
+    public boolean isOpen()
+    {
+        return _channel.isOpen();
+    }
+
+    protected void shutdownInput()
+    {
+        LOG.debug("ishut {}", this);
+        _ishut=true;
+        if (_oshut)
+            close();
+    }
+
+    @Override
+    public void shutdownOutput()
+    {
+        LOG.debug("oshut {}", this);
+        _oshut = true;
+        if (_channel.isOpen())
+        {
+            try
+            {
+                if (!_socket.isOutputShutdown())
+                    _socket.shutdownOutput();
+            }
+            catch (IOException e)
+            {
+                LOG.debug(e);
+            }
+            finally
+            {
+                if (_ishut)
+                {
+                    close();
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean isOutputShutdown()
+    {
+        return _oshut || !_channel.isOpen() || _socket.isOutputShutdown();
+    }
+
+    @Override
+    public boolean isInputShutdown()
+    {
+        return _ishut || !_channel.isOpen() || _socket.isInputShutdown();
+    }
+
+    @Override
+    public void close()
+    {
+        super.close();
+        LOG.debug("close {}", this);
+        try
+        {
+            _channel.close();
+        }
+        catch (IOException e)
+        {
+            LOG.debug(e);
+        }
+        finally
+        {
+            _ishut=true;
+            _oshut=true;
+        }
+    }
+
+    @Override
+    public int fill(ByteBuffer buffer) throws IOException
+    {
+        if (_ishut)
+            return -1;
+
+        int pos=BufferUtil.flipToFill(buffer);
+        try
+        {
+            int filled = _channel.read(buffer);
+            if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
+                LOG.debug("filled {} {}", filled, this);
+
+            if (filled>0)
+                notIdle();
+            else if (filled==-1)
+                shutdownInput();
+
+            return filled;
+        }
+        catch(IOException e)
+        {
+            LOG.debug(e);
+            shutdownInput();
+            return -1;
+        }
+        finally
+        {
+            BufferUtil.flipToFlush(buffer,pos);
+        }
+    }
+
+    @Override
+    public boolean flush(ByteBuffer... buffers) throws IOException
+    {
+        int flushed=0;
+        try
+        {
+            if (buffers.length==1)
+                flushed=_channel.write(buffers[0]);
+            else if (buffers.length>1 && _channel instanceof GatheringByteChannel)
+                flushed= (int)((GatheringByteChannel)_channel).write(buffers,0,buffers.length);
+            else
+            {
+                for (ByteBuffer b : buffers)
+                {
+                    if (b.hasRemaining())
+                    {
+                        int l=_channel.write(b);
+                        if (l>0)
+                            flushed+=l;
+                        if (b.hasRemaining())
+                            break;
+                    }
+                }
+            }
+            if (LOG.isDebugEnabled())
+                LOG.debug("flushed {} {}", flushed, this);
+        }
+        catch (IOException e)
+        {
+            throw new EofException(e);
+        }
+
+        if (flushed>0)
+            notIdle();
+
+        for (ByteBuffer b : buffers)
+            if (!BufferUtil.isEmpty(b))
+                return false;
+
+        return true;
+    }
+
+    public ByteChannel getChannel()
+    {
+        return _channel;
+    }
+
+    @Override
+    public Object getTransport()
+    {
+        return _channel;
+    }
+
+    public Socket getSocket()
+    {
+        return _socket;
+    }
+
+    @Override
+    protected void onIncompleteFlush()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected boolean needsFill() throws IOException
+    {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ClientConnectionFactory.java b/lib/jetty/org/eclipse/jetty/io/ClientConnectionFactory.java
new file mode 100644 (file)
index 0000000..7eb5931
--- /dev/null
@@ -0,0 +1,89 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Factory for client-side {@link Connection} instances.
+ */
+public interface ClientConnectionFactory
+{
+    /**
+     *
+     * @param endPoint the {@link org.eclipse.jetty.io.EndPoint} to link the newly created connection to
+     * @param context the context data to create the connection
+     * @return a new {@link Connection}
+     * @throws IOException if the connection cannot be created
+     */
+    public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException;
+
+    public static class Helper
+    {
+        private static Logger LOG = Log.getLogger(Helper.class);
+
+        private Helper()
+        {
+        }
+
+        /**
+         * Replaces the given {@code oldConnection} with the given {@code newConnection} on the
+         * {@link EndPoint} associated with {@code oldConnection}, performing connection lifecycle management.
+         * <p />
+         * The {@code oldConnection} will be closed by invoking {@link org.eclipse.jetty.io.Connection#onClose()}
+         * and the {@code newConnection} will be opened by invoking {@link org.eclipse.jetty.io.Connection#onOpen()}.
+         * @param oldConnection the old connection to replace
+         * @param newConnection the new connection replacement
+         */
+        public static void replaceConnection(Connection oldConnection, Connection newConnection)
+        {
+            close(oldConnection);
+            oldConnection.getEndPoint().setConnection(newConnection);
+            open(newConnection);
+        }
+
+        private static void open(Connection connection)
+        {
+            try
+            {
+                connection.onOpen();
+            }
+            catch (Throwable x)
+            {
+                LOG.debug(x);
+            }
+        }
+
+        private static void close(Connection connection)
+        {
+            try
+            {
+                connection.onClose();
+            }
+            catch (Throwable x)
+            {
+                LOG.debug(x);
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/Connection.java b/lib/jetty/org/eclipse/jetty/io/Connection.java
new file mode 100644 (file)
index 0000000..96baa01
--- /dev/null
@@ -0,0 +1,89 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.Closeable;
+
+import org.eclipse.jetty.util.Callback;
+
+/**
+ * <p>A {@link Connection} is associated to an {@link EndPoint} so that I/O events
+ * happening on the {@link EndPoint} can be processed by the {@link Connection}.</p>
+ * <p>A typical implementation of {@link Connection} overrides {@link #onOpen()} to
+ * {@link EndPoint#fillInterested(Callback) set read interest} on the {@link EndPoint},
+ * and when the {@link EndPoint} signals read readyness, this {@link Connection} can
+ * read bytes from the network and interpret them.</p>
+ */
+public interface Connection extends Closeable
+{
+    public void addListener(Listener listener);
+
+    /**
+     * <p>Callback method invoked when this {@link Connection} is opened.</p>
+     * <p>Creators of the connection implementation are responsible for calling this method.</p>
+     */
+    public void onOpen();
+
+    /**
+     * <p>Callback method invoked when this {@link Connection} is closed.</p>
+     * <p>Creators of the connection implementation are responsible for calling this method.</p>
+     */
+    public void onClose();
+
+    /**
+     * @return the {@link EndPoint} associated with this {@link Connection}
+     */
+    public EndPoint getEndPoint();
+
+    /**
+     * <p>Performs a logical close of this connection.</p>
+     * <p>For simple connections, this may just mean to delegate the close to the associated
+     * {@link EndPoint} but, for example, SSL connections should write the SSL close message
+     * before closing the associated {@link EndPoint}.</p>
+     */
+    @Override
+    public void close();
+
+    public int getMessagesIn();
+    public int getMessagesOut();
+    public long getBytesIn();
+    public long getBytesOut();
+    public long getCreatedTimeStamp();
+    
+    
+    public interface Listener
+    {
+        public void onOpened(Connection connection);
+
+        public void onClosed(Connection connection);
+
+        public static class Adapter implements Listener
+        {
+            @Override
+            public void onOpened(Connection connection)
+            {
+            }
+
+            @Override
+            public void onClosed(Connection connection)
+            {
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/EndPoint.java b/lib/jetty/org/eclipse/jetty/io/EndPoint.java
new file mode 100644 (file)
index 0000000..87adb40
--- /dev/null
@@ -0,0 +1,243 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadPendingException;
+import java.nio.channels.WritePendingException;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.FutureCallback;
+
+
+/**
+ *
+ * A transport EndPoint
+ * 
+ * <h3>Asynchronous Methods</h3>
+ * <p>The asynchronous scheduling methods of {@link EndPoint} 
+ * has been influenced by NIO.2 Futures and Completion
+ * handlers, but does not use those actual interfaces because they have
+ * some inefficiencies.</p>
+ * <p>This class will frequently be used in conjunction with some of the utility
+ * implementations of {@link Callback}, such as {@link FutureCallback} and
+ * {@link ExecutorCallback}. Examples are:</p>
+ *
+ * <h3>Blocking Read</h3>
+ * <p>A FutureCallback can be used to block until an endpoint is ready to be filled
+ * from:
+ * <blockquote><pre>
+ * FutureCallback&lt;String&gt; future = new FutureCallback&lt;&gt;();
+ * endpoint.fillInterested("ContextObj",future);
+ * ...
+ * String context = future.get(); // This blocks
+ * int filled=endpoint.fill(mybuffer);
+ * </pre></blockquote></p>
+ *
+ * <h3>Dispatched Read</h3>
+ * <p>By using a different callback, the read can be done asynchronously in its own dispatched thread:
+ * <blockquote><pre>
+ * endpoint.fillInterested("ContextObj",new ExecutorCallback&lt;String&gt;(executor)
+ * {
+ *   public void onCompleted(String context)
+ *   {
+ *     int filled=endpoint.fill(mybuffer);
+ *     ...
+ *   }
+ *   public void onFailed(String context,Throwable cause) {...}
+ * });
+ * </pre></blockquote></p>
+ * <p>The executor callback can also be customized to not dispatch in some circumstances when
+ * it knows it can use the callback thread and does not need to dispatch.</p>
+ *
+ * <h3>Blocking Write</h3>
+ * <p>The write contract is that the callback complete is not called until all data has been
+ * written or there is a failure.  For blocking this looks like:
+ * <blockquote><pre>
+ * FutureCallback&lt;String&gt; future = new FutureCallback&lt;&gt;();
+ * endpoint.write("ContextObj",future,headerBuffer,contentBuffer);
+ * String context = future.get(); // This blocks
+ * </pre></blockquote></p>
+ *
+ * <h3>Dispatched Write</h3>
+ * <p>Note also that multiple buffers may be passed in write so that gather writes
+ * can be done:
+ * <blockquote><pre>
+ * endpoint.write("ContextObj",new ExecutorCallback&lt;String&gt;(executor)
+ * {
+ *   public void onCompleted(String context)
+ *   {
+ *     int filled=endpoint.fill(mybuffer);
+ *     ...
+ *   }
+ *   public void onFailed(String context,Throwable cause) {...}
+ * },headerBuffer,contentBuffer);
+ * </pre></blockquote></p>
+ */
+public interface EndPoint extends Closeable
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The local Inet address to which this <code>EndPoint</code> is bound, or <code>null</code>
+     * if this <code>EndPoint</code> does not represent a network connection.
+     */
+    InetSocketAddress getLocalAddress();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The remote Inet address to which this <code>EndPoint</code> is bound, or <code>null</code>
+     * if this <code>EndPoint</code> does not represent a network connection.
+     */
+    InetSocketAddress getRemoteAddress();
+
+    /* ------------------------------------------------------------ */
+    boolean isOpen();
+
+    /* ------------------------------------------------------------ */
+    long getCreatedTimeStamp();
+
+    /* ------------------------------------------------------------ */
+    /** Shutdown the output.
+     * <p>This call indicates that no more data will be sent on this endpoint that
+     * that the remote end should read an EOF once all previously sent data has been
+     * consumed. Shutdown may be done either at the TCP/IP level, as a protocol exchange (Eg
+     * TLS close handshake) or both.
+     * <p>
+     * If the endpoint has {@link #isInputShutdown()} true, then this call has the same effect
+     * as {@link #close()}.
+     */
+    void shutdownOutput();
+
+    /* ------------------------------------------------------------ */
+    /** Test if output is shutdown.
+     * The output is shutdown by a call to {@link #shutdownOutput()}
+     * or {@link #close()}.
+     * @return true if the output is shutdown or the endpoint is closed.
+     */
+    boolean isOutputShutdown();
+
+    /* ------------------------------------------------------------ */
+    /** Test if the input is shutdown.
+     * The input is shutdown if an EOF has been read while doing
+     * a {@link #fill(ByteBuffer)}.   Once the input is shutdown, all calls to
+     * {@link #fill(ByteBuffer)} will  return -1, until such time as the
+     * end point is close, when they will return {@link EofException}.
+     * @return True if the input is shutdown or the endpoint is closed.
+     */
+    boolean isInputShutdown();
+
+    /**
+     * Close any backing stream associated with the endpoint
+     */
+    @Override
+    void close();
+
+    /**
+     * Fill the passed buffer with data from this endpoint.  The bytes are appended to any
+     * data already in the buffer by writing from the buffers limit up to it's capacity.
+     * The limit is updated to include the filled bytes.
+     *
+     * @param buffer The buffer to fill. The position and limit are modified during the fill. After the
+     * operation, the position is unchanged and the limit is increased to reflect the new data filled.
+     * @return an <code>int</code> value indicating the number of bytes
+     * filled or -1 if EOF is read or the input is shutdown.
+     * @throws EofException If the endpoint is closed.
+     */
+    int fill(ByteBuffer buffer) throws IOException;
+
+
+    /**
+     * Flush data from the passed header/buffer to this endpoint.  As many bytes as can be consumed
+     * are taken from the header/buffer position up until the buffer limit.  The header/buffers position
+     * is updated to indicate how many bytes have been consumed.
+     * @return True IFF all the buffers have been consumed and the endpoint has flushed the data to its 
+     * destination (ie is not buffering any data).
+     *
+     * @throws EofException If the endpoint is closed or output is shutdown.
+     */
+    boolean flush(ByteBuffer... buffer) throws IOException;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The underlying transport object (socket, channel, etc.)
+     */
+    Object getTransport();
+
+    /* ------------------------------------------------------------ */
+    /** Get the max idle time in ms.
+     * <p>The max idle time is the time the endpoint can be idle before
+     * extraordinary handling takes place.
+     * @return the max idle time in ms or if ms <= 0 implies an infinite timeout
+     */
+    long getIdleTimeout();
+
+    /* ------------------------------------------------------------ */
+    /** Set the idle timeout.
+     * @param idleTimeout the idle timeout in MS. Timeout <= 0 implies an infinite timeout
+     */
+    void setIdleTimeout(long idleTimeout);
+
+
+    /**
+     * <p>Requests callback methods to be invoked when a call to {@link #fill(ByteBuffer)} would return data or EOF.</p>
+     *
+     * @param callback the callback to call when an error occurs or we are readable.
+     * @throws ReadPendingException if another read operation is concurrent.
+     */
+    void fillInterested(Callback callback) throws ReadPendingException;
+
+    /**
+     * <p>Writes the given buffers via {@link #flush(ByteBuffer...)} and invokes callback methods when either
+     * all the data has been flushed or an error occurs.</p>
+     *
+     * @param callback the callback to call when an error occurs or the write completed.
+     * @param buffers one or more {@link ByteBuffer}s that will be flushed.
+     * @throws WritePendingException if another write operation is concurrent.
+     */
+    void write(Callback callback, ByteBuffer... buffers) throws WritePendingException;
+
+    /**
+     * @return the {@link Connection} associated with this {@link EndPoint}
+     * @see #setConnection(Connection)
+     */
+    Connection getConnection();
+
+    /**
+     * @param connection the {@link Connection} associated with this {@link EndPoint}
+     * @see #getConnection()
+     */
+    void setConnection(Connection connection);
+
+    /**
+     * <p>Callback method invoked when this {@link EndPoint} is opened.</p>
+     * @see #onClose()
+     */
+    void onOpen();
+
+    /**
+     * <p>Callback method invoked when this {@link EndPoint} is close.</p>
+     * @see #onOpen()
+     */
+    void onClose();
+
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/EofException.java b/lib/jetty/org/eclipse/jetty/io/EofException.java
new file mode 100644 (file)
index 0000000..72042f4
--- /dev/null
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.EOFException;
+
+
+/* ------------------------------------------------------------ */
+/** A Jetty specialization of EOFException.
+ * <p> This is thrown by Jetty to distinguish between EOF received from 
+ * the connection, vs and EOF thrown by some application talking to some other file/socket etc.
+ * The only difference in handling is that Jetty EOFs are logged less verbosely.
+ */
+public class EofException extends EOFException
+{
+    public EofException()
+    {
+    }
+    
+    public EofException(String reason)
+    {
+        super(reason);
+    }
+    
+    public EofException(Throwable th)
+    {
+        if (th!=null)
+            initCause(th);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/FillInterest.java b/lib/jetty/org/eclipse/jetty/io/FillInterest.java
new file mode 100644 (file)
index 0000000..b2c3f68
--- /dev/null
@@ -0,0 +1,135 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.ReadPendingException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * A Utility class to help implement {@link EndPoint#fillInterested(Callback)}
+ * by keeping state and calling the context and callback objects.
+ * 
+ */
+public abstract class FillInterest
+{
+    private final static Logger LOG = Log.getLogger(FillInterest.class);
+    private final AtomicReference<Callback> _interested = new AtomicReference<>(null);
+
+    /* ------------------------------------------------------------ */
+    protected FillInterest()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Call to register interest in a callback when a read is possible.
+     * The callback will be called either immediately if {@link #needsFill()} 
+     * returns true or eventually once {@link #fillable()} is called.
+     * @param callback
+     * @throws ReadPendingException
+     */
+    public <C> void register(Callback callback) throws ReadPendingException
+    {
+        if (callback==null)
+            throw new IllegalArgumentException();
+        
+        if (!_interested.compareAndSet(null,callback))
+        {
+            LOG.warn("Read pending for "+_interested.get()+" pervented "+callback);
+            throw new ReadPendingException();
+        }
+        try
+        {
+            if (needsFill())
+                fillable();
+        }
+        catch(IOException e)
+        {
+            onFail(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Call to signal that a read is now possible.
+     */
+    public void fillable()
+    {
+        Callback callback=_interested.get();
+        if (callback!=null && _interested.compareAndSet(callback,null))
+            callback.succeeded();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if a read callback has been registered
+     */
+    public boolean isInterested()
+    {
+        return _interested.get()!=null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Call to signal a failure to a registered interest
+     * @return true if the cause was passed to a {@link Callback} instance
+     */
+    public boolean onFail(Throwable cause)
+    {
+        Callback callback=_interested.get();
+        if (callback!=null && _interested.compareAndSet(callback,null))
+        {
+            callback.failed(cause);
+            return true;
+        }
+        return false;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void onClose()
+    {
+        Callback callback=_interested.get();
+        if (callback!=null && _interested.compareAndSet(callback,null))
+            callback.failed(new ClosedChannelException());
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("FillInterest@%x{%b,%s}",hashCode(),_interested.get(),_interested.get());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Register the read interest 
+     * Abstract method to be implemented by the Specific ReadInterest to
+     * enquire if a read is immediately possible and if not to schedule a future
+     * call to {@link #fillable()} or {@link #onFail(Throwable)}
+     * @return true if a read is possible
+     * @throws IOException
+     */
+    abstract protected boolean needsFill() throws IOException;
+    
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/IdleTimeout.java b/lib/jetty/org/eclipse/jetty/io/IdleTimeout.java
new file mode 100644 (file)
index 0000000..8b251ac
--- /dev/null
@@ -0,0 +1,182 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An Abstract implementation of an Idle Timeout.
+ * <p/>
+ * This implementation is optimised that timeout operations are not cancelled on
+ * every operation. Rather timeout are allowed to expire and a check is then made
+ * to see when the last operation took place.  If the idle timeout has not expired,
+ * the timeout is rescheduled for the earliest possible time a timeout could occur.
+ */
+public abstract class IdleTimeout
+{
+    private static final Logger LOG = Log.getLogger(IdleTimeout.class);
+    private final Scheduler _scheduler;
+    private final AtomicReference<Scheduler.Task> _timeout = new AtomicReference<>();
+    private volatile long _idleTimeout;
+    private volatile long _idleTimestamp = System.currentTimeMillis();
+
+    private final Runnable _idleTask = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            long idleLeft = checkIdleTimeout();
+            if (idleLeft >= 0)
+                scheduleIdleTimeout(idleLeft > 0 ? idleLeft : getIdleTimeout());
+        }
+    };
+
+    /**
+     * @param scheduler A scheduler used to schedule checks for the idle timeout.
+     */
+    public IdleTimeout(Scheduler scheduler)
+    {
+        _scheduler = scheduler;
+    }
+
+    public long getIdleTimestamp()
+    {
+        return _idleTimestamp;
+    }
+
+    public long getIdleTimeout()
+    {
+        return _idleTimeout;
+    }
+
+    public void setIdleTimeout(long idleTimeout)
+    {
+        long old = _idleTimeout;
+        _idleTimeout = idleTimeout;
+
+        // Do we have an old timeout
+        if (old > 0)
+        {
+            // if the old was less than or equal to the new timeout, then nothing more to do
+            if (old <= idleTimeout)
+                return;
+
+            // old timeout is too long, so cancel it.
+            deactivate();
+        }
+
+        // If we have a new timeout, then check and reschedule
+        if (isOpen())
+            activate();
+    }
+
+    /**
+     * This method should be called when non-idle activity has taken place.
+     */
+    public void notIdle()
+    {
+        _idleTimestamp = System.currentTimeMillis();
+    }
+
+    private void scheduleIdleTimeout(long delay)
+    {
+        Scheduler.Task newTimeout = null;
+        if (isOpen() && delay > 0 && _scheduler != null)
+            newTimeout = _scheduler.schedule(_idleTask, delay, TimeUnit.MILLISECONDS);
+        Scheduler.Task oldTimeout = _timeout.getAndSet(newTimeout);
+        if (oldTimeout != null)
+            oldTimeout.cancel();
+    }
+
+    public void onOpen()
+    {
+        activate();
+    }
+
+    private void activate()
+    {
+        if (_idleTimeout > 0)
+            _idleTask.run();
+    }
+
+    public void onClose()
+    {
+        deactivate();
+    }
+
+    private void deactivate()
+    {
+        Scheduler.Task oldTimeout = _timeout.getAndSet(null);
+        if (oldTimeout != null)
+            oldTimeout.cancel();
+    }
+
+    protected long checkIdleTimeout()
+    {
+        if (isOpen())
+        {
+            long idleTimestamp = getIdleTimestamp();
+            long idleTimeout = getIdleTimeout();
+            long idleElapsed = System.currentTimeMillis() - idleTimestamp;
+            long idleLeft = idleTimeout - idleElapsed;
+
+            LOG.debug("{} idle timeout check, elapsed: {} ms, remaining: {} ms", this, idleElapsed, idleLeft);
+
+            if (idleTimestamp != 0 && idleTimeout > 0)
+            {
+                if (idleLeft <= 0)
+                {
+                    LOG.debug("{} idle timeout expired", this);
+                    try
+                    {
+                        onIdleExpired(new TimeoutException("Idle timeout expired: " + idleElapsed + "/" + idleTimeout + " ms"));
+                    }
+                    finally
+                    {
+                        notIdle();
+                    }
+                }
+            }
+
+            return idleLeft >= 0 ? idleLeft : 0;
+        }
+        return -1;
+    }
+
+    /**
+     * This abstract method is called when the idle timeout has expired.
+     *
+     * @param timeout a TimeoutException
+     */
+    protected abstract void onIdleExpired(TimeoutException timeout);
+
+    /**
+     * This abstract method should be called to check if idle timeouts
+     * should still be checked.
+     *
+     * @return True if the entity monitored should still be checked for idle timeouts
+     */
+    public abstract boolean isOpen();
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java
new file mode 100644 (file)
index 0000000..a6fc756
--- /dev/null
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.LeakDetector;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class LeakTrackingByteBufferPool extends ContainerLifeCycle implements ByteBufferPool
+{
+    private static final Logger LOG = Log.getLogger(LeakTrackingByteBufferPool.class);
+
+    private final LeakDetector<ByteBuffer> leakDetector = new LeakDetector<ByteBuffer>()
+    {
+        @Override
+        protected void leaked(LeakInfo leakInfo)
+        {
+            LeakTrackingByteBufferPool.this.leaked(leakInfo);
+        }
+    };
+    
+    private final ByteBufferPool delegate;
+
+    public LeakTrackingByteBufferPool(ByteBufferPool delegate)
+    {
+        this.delegate = delegate;
+        addBean(leakDetector);
+        addBean(delegate);
+    }
+
+    @Override
+    public ByteBuffer acquire(int size, boolean direct)
+    {
+        ByteBuffer buffer = delegate.acquire(size, direct);
+        if (!leakDetector.acquired(buffer))
+            LOG.warn("ByteBuffer {}@{} not tracked", buffer, System.identityHashCode(buffer));
+        return buffer;
+    }
+
+    @Override
+    public void release(ByteBuffer buffer)
+    {
+        if (buffer == null)
+            return;
+        if (!leakDetector.released(buffer))
+            LOG.warn("ByteBuffer {}@{} released but not acquired", buffer, System.identityHashCode(buffer));
+        delegate.release(buffer);
+    }
+
+    protected void leaked(LeakDetector<ByteBuffer>.LeakInfo leakInfo)
+    {
+        LOG.warn("ByteBuffer " + leakInfo.getResourceDescription() + " leaked at:", leakInfo.getStackFrames());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/MappedByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/MappedByteBufferPool.java
new file mode 100644 (file)
index 0000000..b331904
--- /dev/null
@@ -0,0 +1,111 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class MappedByteBufferPool implements ByteBufferPool
+{
+    private final ConcurrentMap<Integer, Queue<ByteBuffer>> directBuffers = new ConcurrentHashMap<>();
+    private final ConcurrentMap<Integer, Queue<ByteBuffer>> heapBuffers = new ConcurrentHashMap<>();
+    private final int factor;
+
+    public MappedByteBufferPool()
+    {
+        this(1024);
+    }
+
+    public MappedByteBufferPool(int factor)
+    {
+        this.factor = factor;
+    }
+
+    @Override
+    public ByteBuffer acquire(int size, boolean direct)
+    {
+        int bucket = bucketFor(size);
+        ConcurrentMap<Integer, Queue<ByteBuffer>> buffers = buffersFor(direct);
+
+        ByteBuffer result = null;
+        Queue<ByteBuffer> byteBuffers = buffers.get(bucket);
+        if (byteBuffers != null)
+            result = byteBuffers.poll();
+
+        if (result == null)
+        {
+            int capacity = bucket * factor;
+            result = direct ? BufferUtil.allocateDirect(capacity) : BufferUtil.allocate(capacity);
+        }
+
+        BufferUtil.clear(result);
+        return result;
+    }
+
+    @Override
+    public void release(ByteBuffer buffer)
+    {
+        if (buffer == null)
+            return; // nothing to do
+        
+        // validate that this buffer is from this pool
+        assert((buffer.capacity() % factor) == 0);
+        
+        int bucket = bucketFor(buffer.capacity());
+        ConcurrentMap<Integer, Queue<ByteBuffer>> buffers = buffersFor(buffer.isDirect());
+
+        // Avoid to create a new queue every time, just to be discarded immediately
+        Queue<ByteBuffer> byteBuffers = buffers.get(bucket);
+        if (byteBuffers == null)
+        {
+            byteBuffers = new ConcurrentLinkedQueue<>();
+            Queue<ByteBuffer> existing = buffers.putIfAbsent(bucket, byteBuffers);
+            if (existing != null)
+                byteBuffers = existing;
+        }
+
+        BufferUtil.clear(buffer);
+        byteBuffers.offer(buffer);
+    }
+
+    public void clear()
+    {
+        directBuffers.clear();
+        heapBuffers.clear();
+    }
+
+    private int bucketFor(int size)
+    {
+        int bucket = size / factor;
+        if (size % factor > 0)
+            ++bucket;
+        return bucket;
+    }
+
+    // Package local for testing
+    ConcurrentMap<Integer, Queue<ByteBuffer>> buffersFor(boolean direct)
+    {
+        return direct ? directBuffers : heapBuffers;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnection.java b/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnection.java
new file mode 100644 (file)
index 0000000..cd05630
--- /dev/null
@@ -0,0 +1,128 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class NegotiatingClientConnection extends AbstractConnection
+{
+    private static final Logger LOG = Log.getLogger(NegotiatingClientConnection.class);
+
+    private final SSLEngine engine;
+    private final ClientConnectionFactory connectionFactory;
+    private final Map<String, Object> context;
+    private volatile boolean completed;
+
+    protected NegotiatingClientConnection(EndPoint endp, Executor executor, SSLEngine sslEngine, ClientConnectionFactory connectionFactory, Map<String, Object> context)
+    {
+        super(endp, executor);
+        this.engine = sslEngine;
+        this.connectionFactory = connectionFactory;
+        this.context = context;
+    }
+
+    protected SSLEngine getSSLEngine()
+    {
+        return engine;
+    }
+
+    protected void completed()
+    {
+        completed = true;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        try
+        {
+            getEndPoint().flush(BufferUtil.EMPTY_BUFFER);
+            if (completed)
+                replaceConnection();
+            else
+                fillInterested();
+        }
+        catch (IOException x)
+        {
+            close();
+            throw new RuntimeIOException(x);
+        }
+    }
+
+    @Override
+    public void onFillable()
+    {
+        while (true)
+        {
+            int filled = fill();
+            if (filled == 0 && !completed)
+                fillInterested();
+            if (filled <= 0 || completed)
+                break;
+        }
+        if (completed)
+            replaceConnection();
+    }
+
+    private int fill()
+    {
+        try
+        {
+            return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
+        }
+        catch (IOException x)
+        {
+            LOG.debug(x);
+            close();
+            return -1;
+        }
+    }
+
+    private void replaceConnection()
+    {
+        EndPoint endPoint = getEndPoint();
+        try
+        {
+            Connection oldConnection = endPoint.getConnection();
+            Connection newConnection = connectionFactory.newConnection(endPoint, context);
+            ClientConnectionFactory.Helper.replaceConnection(oldConnection, newConnection);
+        }
+        catch (Throwable x)
+        {
+            LOG.debug(x);
+            close();
+        }
+    }
+
+    @Override
+    public void close()
+    {
+        // Gentler close for SSL.
+        getEndPoint().shutdownOutput();
+        super.close();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java b/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java
new file mode 100644 (file)
index 0000000..ff38600
--- /dev/null
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+
+public abstract class NegotiatingClientConnectionFactory implements ClientConnectionFactory
+{
+    private final ClientConnectionFactory connectionFactory;
+
+    protected NegotiatingClientConnectionFactory(ClientConnectionFactory connectionFactory)
+    {
+        this.connectionFactory = connectionFactory;
+    }
+
+    public ClientConnectionFactory getClientConnectionFactory()
+    {
+        return connectionFactory;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NetworkTrafficListener.java b/lib/jetty/org/eclipse/jetty/io/NetworkTrafficListener.java
new file mode 100644 (file)
index 0000000..6a4239f
--- /dev/null
@@ -0,0 +1,100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.net.Socket;
+import java.nio.ByteBuffer;
+
+/**
+ * <p>A listener for raw network traffic within Jetty.</p>
+ * <p>{@link NetworkTrafficListener}s can be installed in a
+ * <code>org.eclipse.jetty.server.nio.NetworkTrafficSelectChannelConnector</code>,
+ * and are notified of the following network traffic events:</p>
+ * <ul>
+ * <li>Connection opened, when the server has accepted the connection from a remote client</li>
+ * <li>Incoming bytes, when the server receives bytes sent from a remote client</li>
+ * <li>Outgoing bytes, when the server sends bytes to a remote client</li>
+ * <li>Connection closed, when the server has closed the connection to a remote client</li>
+ * </ul>
+ * <p>{@link NetworkTrafficListener}s can be used to log the network traffic viewed by
+ * a Jetty server (for example logging to filesystem) for activities such as debugging
+ * or request/response cycles or for replaying request/response cycles to other servers.</p>
+ */
+public interface NetworkTrafficListener
+{
+    /**
+     * <p>Callback method invoked when a connection from a remote client has been accepted.</p>
+     * <p>The {@code socket} parameter can be used to extract socket address information of
+     * the remote client.</p>
+     *
+     * @param socket the socket associated with the remote client
+     */
+    public void opened(Socket socket);
+
+    /**
+     * <p>Callback method invoked when bytes sent by a remote client arrived on the server.</p>
+     *
+     * @param socket the socket associated with the remote client
+     * @param bytes  the read-only buffer containing the incoming bytes
+     */
+    public void incoming(Socket socket, ByteBuffer bytes);
+
+    /**
+     * <p>Callback method invoked when bytes are sent to a remote client from the server.</p>
+     * <p>This method is invoked after the bytes have been actually written to the remote client.</p>
+     *
+     * @param socket the socket associated with the remote client
+     * @param bytes  the read-only buffer containing the outgoing bytes
+     */
+    public void outgoing(Socket socket, ByteBuffer bytes);
+
+    /**
+     * <p>Callback method invoked when a connection to a remote client has been closed.</p>
+     * <p>The {@code socket} parameter is already closed when this method is called, so it
+     * cannot be queried for socket address information of the remote client.<br />
+     * However, the {@code socket} parameter is the same object passed to {@link #opened(Socket)},
+     * so it is possible to map socket information in {@link #opened(Socket)} and retrieve it
+     * in this method.
+     *
+     * @param socket the (closed) socket associated with the remote client
+     */
+    public void closed(Socket socket);
+
+    /**
+     * <p>A commodity class that implements {@link NetworkTrafficListener} with empty methods.</p>
+     */
+    public static class Adapter implements NetworkTrafficListener
+    {
+        public void opened(Socket socket)
+        {
+        }
+
+        public void incoming(Socket socket, ByteBuffer bytes)
+        {
+        }
+
+        public void outgoing(Socket socket, ByteBuffer bytes)
+        {
+        }
+
+        public void closed(Socket socket)
+        {
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java b/lib/jetty/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java
new file mode 100644 (file)
index 0000000..a4b6f7d
--- /dev/null
@@ -0,0 +1,155 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
+{
+    private static final Logger LOG = Log.getLogger(NetworkTrafficSelectChannelEndPoint.class);
+
+    private final List<NetworkTrafficListener> listeners;
+
+    public NetworkTrafficSelectChannelEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key, Scheduler scheduler, long idleTimeout, List<NetworkTrafficListener> listeners) throws IOException
+    {
+        super(channel, selectSet, key, scheduler, idleTimeout);
+        this.listeners = listeners;
+    }
+
+    @Override
+    public int fill(ByteBuffer buffer) throws IOException
+    {
+        int read = super.fill(buffer);
+        notifyIncoming(buffer, read);
+        return read;
+    }
+
+    @Override
+    public boolean flush(ByteBuffer... buffers) throws IOException
+    {
+        boolean flushed=true;
+        for (ByteBuffer b : buffers)
+        {
+            if (b.hasRemaining())
+            {
+                int position = b.position();
+                ByteBuffer view=b.slice();
+                flushed&=super.flush(b);
+                int l=b.position()-position;
+                view.limit(view.position()+l);
+                notifyOutgoing(view);
+                if (!flushed)
+                    break;
+            }
+        }
+        return flushed;
+    }
+
+    
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        if (listeners != null && !listeners.isEmpty())
+        {
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    listener.opened(getSocket());
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onClose()
+    {
+        super.onClose();
+        if (listeners != null && !listeners.isEmpty())
+        {
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    listener.closed(getSocket());
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+
+
+    public void notifyIncoming(ByteBuffer buffer, int read)
+    {
+        if (listeners != null && !listeners.isEmpty() && read > 0)
+        {
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    ByteBuffer view = buffer.asReadOnlyBuffer();
+                    listener.incoming(getSocket(), view);
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+
+    public void notifyOutgoing(ByteBuffer view)
+    {
+        if (listeners != null && !listeners.isEmpty() && view.hasRemaining())
+        {
+            Socket socket=getSocket();
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    listener.outgoing(socket, view);   
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/RuntimeIOException.java b/lib/jetty/org/eclipse/jetty/io/RuntimeIOException.java
new file mode 100644 (file)
index 0000000..88c33f7
--- /dev/null
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.io;
+
+/* ------------------------------------------------------------ */
+/**
+ * Subclass of {@link java.lang.RuntimeException} used to signal that there
+ * was an {@link java.io.IOException} thrown by underlying {@link java.io.Writer}
+ */
+public class RuntimeIOException extends RuntimeException
+{
+    public RuntimeIOException()
+    {
+        super();
+    }
+
+    public RuntimeIOException(String message)
+    {
+        super(message);
+    }
+
+    public RuntimeIOException(Throwable cause)
+    {
+        super(cause);
+    }
+
+    public RuntimeIOException(String message, Throwable cause)
+    {
+        super(message,cause);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/SelectChannelEndPoint.java b/lib/jetty/org/eclipse/jetty/io/SelectChannelEndPoint.java
new file mode 100644 (file)
index 0000000..3050402
--- /dev/null
@@ -0,0 +1,207 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.io.SelectorManager.ManagedSelector;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An ChannelEndpoint that can be scheduled by {@link SelectorManager}.
+ */
+public class SelectChannelEndPoint extends ChannelEndPoint implements SelectorManager.SelectableEndPoint
+{
+    public static final Logger LOG = Log.getLogger(SelectChannelEndPoint.class);
+
+    private final Runnable _updateTask = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                if (getChannel().isOpen())
+                {
+                    int oldInterestOps = _key.interestOps();
+                    int newInterestOps = _interestOps.get();
+                    if (newInterestOps != oldInterestOps)
+                        setKeyInterests(oldInterestOps, newInterestOps);
+                }
+            }
+            catch (CancelledKeyException x)
+            {
+                LOG.debug("Ignoring key update for concurrently closed channel {}", this);
+                close();
+            }
+            catch (Exception x)
+            {
+                LOG.warn("Ignoring key update for " + this, x);
+                close();
+            }
+        }
+    };
+
+    /**
+     * true if {@link ManagedSelector#destroyEndPoint(EndPoint)} has not been called
+     */
+    private final AtomicBoolean _open = new AtomicBoolean();
+    private final SelectorManager.ManagedSelector _selector;
+    private final SelectionKey _key;
+    /**
+     * The desired value for {@link SelectionKey#interestOps()}
+     */
+    private final AtomicInteger _interestOps = new AtomicInteger();
+
+    public SelectChannelEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler, long idleTimeout)
+    {
+        super(scheduler,channel);
+        _selector = selector;
+        _key = key;
+        setIdleTimeout(idleTimeout);
+    }
+
+    @Override
+    protected boolean needsFill()
+    {
+        updateLocalInterests(SelectionKey.OP_READ, true);
+        return false;
+    }
+
+    @Override
+    protected void onIncompleteFlush()
+    {
+        updateLocalInterests(SelectionKey.OP_WRITE, true);
+    }
+
+    @Override
+    public void onSelected()
+    {
+        assert _selector.isSelectorThread();
+        int oldInterestOps = _key.interestOps();
+        int readyOps = _key.readyOps();
+        int newInterestOps = oldInterestOps & ~readyOps;
+        setKeyInterests(oldInterestOps, newInterestOps);
+        updateLocalInterests(readyOps, false);
+        if (_key.isReadable())
+            getFillInterest().fillable();
+        if (_key.isWritable())
+            getWriteFlusher().completeWrite();
+    }
+
+
+    private void updateLocalInterests(int operation, boolean add)
+    {
+        while (true)
+        {
+            int oldInterestOps = _interestOps.get();
+            int newInterestOps;
+            if (add)
+                newInterestOps = oldInterestOps | operation;
+            else
+                newInterestOps = oldInterestOps & ~operation;
+
+            if (isInputShutdown())
+                newInterestOps &= ~SelectionKey.OP_READ;
+            if (isOutputShutdown())
+                newInterestOps &= ~SelectionKey.OP_WRITE;
+
+            if (newInterestOps != oldInterestOps)
+            {
+                if (_interestOps.compareAndSet(oldInterestOps, newInterestOps))
+                {
+                    LOG.debug("Local interests updated {} -> {} for {}", oldInterestOps, newInterestOps, this);
+                    _selector.updateKey(_updateTask);
+                }
+                else
+                {
+                    LOG.debug("Local interests update conflict: now {}, was {}, attempted {} for {}", _interestOps.get(), oldInterestOps, newInterestOps, this);
+                    continue;
+                }
+            }
+            else
+            {
+                LOG.debug("Ignoring local interests update {} -> {} for {}", oldInterestOps, newInterestOps, this);
+            }
+            break;
+        }
+    }
+
+
+    private void setKeyInterests(int oldInterestOps, int newInterestOps)
+    {
+        LOG.debug("Key interests updated {} -> {}", oldInterestOps, newInterestOps);
+        _key.interestOps(newInterestOps);
+    }
+
+    @Override
+    public void close()
+    {
+        if (_open.compareAndSet(true, false))
+        {
+            super.close();
+            _selector.destroyEndPoint(this);
+        }
+    }
+
+    @Override
+    public boolean isOpen()
+    {
+        // We cannot rely on super.isOpen(), because there is a race between calls to close() and isOpen():
+        // a thread may call close(), which flips the boolean but has not yet called super.close(), and
+        // another thread calls isOpen() which would return true - wrong - if based on super.isOpen().
+        return _open.get();
+    }
+
+    @Override
+    public void onOpen()
+    {
+        if (_open.compareAndSet(false, true))
+            super.onOpen();
+    }
+
+    @Override
+    public String toString()
+    {
+        // Do NOT use synchronized (this)
+        // because it's very easy to deadlock when debugging is enabled.
+        // We do a best effort to print the right toString() and that's it.
+        try
+        {
+            boolean valid = _key!=null && _key.isValid();
+            int keyInterests = valid ? _key.interestOps() : -1;
+            int keyReadiness = valid ? _key.readyOps() : -1;
+            return String.format("%s{io=%d,kio=%d,kro=%d}",
+                    super.toString(),
+                    _interestOps.get(),
+                    keyInterests,
+                    keyReadiness);
+        }
+        catch (CancelledKeyException x)
+        {
+            return String.format("%s{io=%s,kio=-2,kro=-2}", super.toString(), _interestOps.get());
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/SelectorManager.java b/lib/jetty/org/eclipse/jetty/io/SelectorManager.java
new file mode 100644 (file)
index 0000000..fd3c6c6
--- /dev/null
@@ -0,0 +1,984 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.ConcurrentArrayQueue;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * <p>{@link SelectorManager} manages a number of {@link ManagedSelector}s that
+ * simplify the non-blocking primitives provided by the JVM via the {@code java.nio} package.</p>
+ * <p>{@link SelectorManager} subclasses implement methods to return protocol-specific
+ * {@link EndPoint}s and {@link Connection}s.</p>
+ */
+public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable
+{
+    public static final String SUBMIT_KEY_UPDATES = "org.eclipse.jetty.io.SelectorManager.submitKeyUpdates";
+    public static final int DEFAULT_CONNECT_TIMEOUT = 15000;
+    protected static final Logger LOG = Log.getLogger(SelectorManager.class);
+    private final static boolean __submitKeyUpdates = Boolean.valueOf(System.getProperty(SUBMIT_KEY_UPDATES, "false"));
+    
+    private final Executor executor;
+    private final Scheduler scheduler;
+    private final ManagedSelector[] _selectors;
+    private long _connectTimeout = DEFAULT_CONNECT_TIMEOUT;
+    private long _selectorIndex;
+    
+    protected SelectorManager(Executor executor, Scheduler scheduler)
+    {
+        this(executor, scheduler, (Runtime.getRuntime().availableProcessors() + 1) / 2);
+    }
+
+    protected SelectorManager(Executor executor, Scheduler scheduler, int selectors)
+    {
+        if (selectors<=0)
+            throw new IllegalArgumentException("No selectors");
+        this.executor = executor;
+        this.scheduler = scheduler;
+        _selectors = new ManagedSelector[selectors];
+    }
+
+    public Executor getExecutor()
+    {
+        return executor;
+    }
+
+    public Scheduler getScheduler()
+    {
+        return scheduler;
+    }
+
+    /**
+     * Get the connect timeout
+     *
+     * @return the connect timeout (in milliseconds)
+     */
+    public long getConnectTimeout()
+    {
+        return _connectTimeout;
+    }
+
+    /**
+     * Set the connect timeout (in milliseconds)
+     *
+     * @param milliseconds the number of milliseconds for the timeout
+     */
+    public void setConnectTimeout(long milliseconds)
+    {
+        _connectTimeout = milliseconds;
+    }
+
+    /**
+     * Executes the given task in a different thread.
+     *
+     * @param task the task to execute
+     */
+    protected void execute(Runnable task)
+    {
+        executor.execute(task);
+    }
+
+    /**
+     * @return the number of selectors in use
+     */
+    public int getSelectorCount()
+    {
+        return _selectors.length;
+    }
+
+    private ManagedSelector chooseSelector()
+    {
+        // The ++ increment here is not atomic, but it does not matter,
+        // so long as the value changes sometimes, then connections will
+        // be distributed over the available selectors.
+        long s = _selectorIndex++;
+        int index = (int)(s % getSelectorCount());
+        return _selectors[index];
+    }
+
+    /**
+     * <p>Registers a channel to perform a non-blocking connect.</p>
+     * <p>The channel must be set in non-blocking mode, and {@link SocketChannel#connect(SocketAddress)}
+     * must be called prior to calling this method.</p>
+     *
+     * @param channel    the channel to register
+     * @param attachment the attachment object
+     */
+    public void connect(SocketChannel channel, Object attachment)
+    {
+        ManagedSelector set = chooseSelector();
+        set.submit(set.new Connect(channel, attachment));
+    }
+
+    /**
+     * <p>Registers a channel to perform non-blocking read/write operations.</p>
+     * <p>This method is called just after a channel has been accepted by {@link ServerSocketChannel#accept()},
+     * or just after having performed a blocking connect via {@link Socket#connect(SocketAddress, int)}.</p>
+     *
+     * @param channel the channel to register
+     */
+    public void accept(final SocketChannel channel)
+    {
+        final ManagedSelector selector = chooseSelector();
+        selector.submit(selector.new Accept(channel));
+    }
+    
+    /**
+     * <p>Registers a server channel for accept operations.
+     * When a {@link SocketChannel} is accepted from the given {@link ServerSocketChannel}
+     * then the {@link #accepted(SocketChannel)} method is called, which must be
+     * overridden by a derivation of this class to handle the accepted channel
+     * 
+     * @param server the server channel to register
+     */
+    public void acceptor(final ServerSocketChannel server)
+    {
+        final ManagedSelector selector = chooseSelector();
+        selector.submit(selector.new Acceptor(server));
+    }
+    
+    /**
+     * Callback method when a channel is accepted from the {@link ServerSocketChannel}
+     * passed to {@link #acceptor(ServerSocketChannel)}.
+     * The default impl throws an {@link UnsupportedOperationException}, so it must
+     * be overridden by subclasses if a server channel is provided.
+     *
+     * @param channel the
+     * @throws IOException
+     */
+    protected void accepted(SocketChannel channel) throws IOException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        for (int i = 0; i < _selectors.length; i++)
+        {
+            ManagedSelector selector = newSelector(i);
+            _selectors[i] = selector;
+            selector.start();
+            execute(new NonBlockingThread(selector));
+        }
+    }
+
+    /**
+     * <p>Factory method for {@link ManagedSelector}.</p>
+     *
+     * @param id an identifier for the {@link ManagedSelector to create}
+     * @return a new {@link ManagedSelector}
+     */
+    protected ManagedSelector newSelector(int id)
+    {
+        return new ManagedSelector(id);
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        for (ManagedSelector selector : _selectors)
+            selector.stop();
+        super.doStop();
+    }
+
+    /**
+     * <p>Callback method invoked when an endpoint is opened.</p>
+     *
+     * @param endpoint the endpoint being opened
+     */
+    protected void endPointOpened(EndPoint endpoint)
+    {
+        endpoint.onOpen();
+    }
+
+    /**
+     * <p>Callback method invoked when an endpoint is closed.</p>
+     *
+     * @param endpoint the endpoint being closed
+     */
+    protected void endPointClosed(EndPoint endpoint)
+    {
+        endpoint.onClose();
+    }
+
+    /**
+     * <p>Callback method invoked when a connection is opened.</p>
+     *
+     * @param connection the connection just opened
+     */
+    public void connectionOpened(Connection connection)
+    {
+        try
+        {
+            connection.onOpen();
+        }
+        catch (Throwable x)
+        {
+            if (isRunning())
+                LOG.warn("Exception while notifying connection " + connection, x);
+            else
+                LOG.debug("Exception while notifying connection {}",connection, x);
+        }
+    }
+
+    /**
+     * <p>Callback method invoked when a connection is closed.</p>
+     *
+     * @param connection the connection just closed
+     */
+    public void connectionClosed(Connection connection)
+    {
+        try
+        {
+            connection.onClose();
+        }
+        catch (Throwable x)
+        {
+            LOG.debug("Exception while notifying connection " + connection, x);
+        }
+    }
+
+    protected boolean finishConnect(SocketChannel channel) throws IOException
+    {
+        return channel.finishConnect();
+    }
+
+    /**
+     * <p>Callback method invoked when a non-blocking connect cannot be completed.</p>
+     * <p>By default it just logs with level warning.</p>
+     *
+     * @param channel the channel that attempted the connect
+     * @param ex the exception that caused the connect to fail
+     * @param attachment the attachment object associated at registration
+     */
+    protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
+    {
+        LOG.warn(String.format("%s - %s", channel, attachment), ex);
+    }
+
+    /**
+     * <p>Factory method to create {@link EndPoint}.</p>
+     * <p>This method is invoked as a result of the registration of a channel via {@link #connect(SocketChannel, Object)}
+     * or {@link #accept(SocketChannel)}.</p>
+     *
+     * @param channel   the channel associated to the endpoint
+     * @param selector the selector the channel is registered to
+     * @param selectionKey      the selection key
+     * @return a new endpoint
+     * @throws IOException if the endPoint cannot be created
+     * @see #newConnection(SocketChannel, EndPoint, Object)
+     */
+    protected abstract EndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selector, SelectionKey selectionKey) throws IOException;
+
+    /**
+     * <p>Factory method to create {@link Connection}.</p>
+     *
+     * @param channel    the channel associated to the connection
+     * @param endpoint   the endpoint
+     * @param attachment the attachment
+     * @return a new connection
+     * @throws IOException
+     * @see #newEndPoint(SocketChannel, ManagedSelector, SelectionKey)
+     */
+    public abstract Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException;
+
+    @Override
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        ContainerLifeCycle.dumpObject(out, this);
+        ContainerLifeCycle.dump(out, indent, TypeUtil.asList(_selectors));
+    }
+
+    private enum State
+    {
+        CHANGES, MORE_CHANGES, SELECT, WAKEUP, PROCESS
+    }
+
+    /**
+     * <p>{@link ManagedSelector} wraps a {@link Selector} simplifying non-blocking operations on channels.</p>
+     * <p>{@link ManagedSelector} runs the select loop, which waits on {@link Selector#select()} until events
+     * happen for registered channels. When events happen, it notifies the {@link EndPoint} associated
+     * with the channel.</p>
+     */
+    public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable
+    {
+        private final AtomicReference<State> _state= new AtomicReference<>(State.PROCESS);
+        private final Queue<Runnable> _changes = new ConcurrentArrayQueue<>();
+        private final int _id;
+        private Selector _selector;
+        private volatile Thread _thread;
+
+        public ManagedSelector(int id)
+        {
+            _id = id;
+            setStopTimeout(5000);
+        }
+
+        @Override
+        protected void doStart() throws Exception
+        {
+            super.doStart();
+            _selector = Selector.open();
+            _state.set(State.PROCESS);
+        }
+
+        @Override
+        protected void doStop() throws Exception
+        {
+            LOG.debug("Stopping {}", this);
+            Stop stop = new Stop();
+            submit(stop);
+            stop.await(getStopTimeout());
+            LOG.debug("Stopped {}", this);
+        }
+
+        /**
+         * Submit a task to update a selector key.  If the System property {@link SelectorManager#SUBMIT_KEY_UPDATES}
+         * is set true (default is false), the task is passed to {@link #submit(Runnable)}.   Otherwise it is run immediately and the selector 
+         * woken up if need be.   
+         * @param update the update to a key
+         */
+        public void updateKey(Runnable update)
+        {
+            if (__submitKeyUpdates)
+            {
+                submit(update);
+            }
+            else
+            {
+                runChange(update);
+                if (_state.compareAndSet(State.SELECT, State.WAKEUP))
+                   wakeup();
+            }
+        }
+        
+        /**
+         * <p>Submits a change to be executed in the selector thread.</p>
+         * <p>Changes may be submitted from any thread, and the selector thread woken up
+         * (if necessary) to execute the change.</p>
+         *
+         * @param change the change to submit
+         */
+        public void submit(Runnable change)
+        {
+            // This method may be called from the selector thread, and therefore
+            // we could directly run the change without queueing, but this may
+            // lead to stack overflows on a busy server, so we always offer the
+            // change to the queue and process the state.
+
+            _changes.offer(change);
+            LOG.debug("Queued change {}", change);
+
+            out: while (true)
+            {
+                switch (_state.get())
+                {
+                    case SELECT:
+                        // Avoid multiple wakeup() calls if we the CAS fails
+                        if (!_state.compareAndSet(State.SELECT, State.WAKEUP))
+                            continue;
+                        wakeup();
+                        break out;
+                    case CHANGES:
+                        // Tell the selector thread that we have more changes.
+                        // If we fail to CAS, we possibly need to wakeup(), so loop.
+                        if (_state.compareAndSet(State.CHANGES, State.MORE_CHANGES))
+                            break out;
+                        continue;
+                    case WAKEUP:
+                        // Do nothing, we have already a wakeup scheduled
+                        break out;
+                    case MORE_CHANGES:
+                        // Do nothing, we already notified the selector thread of more changes
+                        break out;
+                    case PROCESS:
+                        // Do nothing, the changes will be run after the processing
+                        break out;
+                    default:
+                        throw new IllegalStateException();
+                }
+            }
+        }
+
+        private void runChanges()
+        {
+            Runnable change;
+            while ((change = _changes.poll()) != null)
+                runChange(change);
+        }
+
+        protected void runChange(Runnable change)
+        {
+            try
+            {
+                LOG.debug("Running change {}", change);
+                change.run();
+            }
+            catch (Throwable x)
+            {
+                LOG.debug("Could not run change " + change, x);
+            }
+        }
+
+        @Override
+        public void run()
+        {
+            _thread = Thread.currentThread();
+            String name = _thread.getName();
+            try
+            {
+                _thread.setName(name + "-selector-" + SelectorManager.this.getClass().getSimpleName()+"@"+Integer.toHexString(SelectorManager.this.hashCode())+"/"+_id);
+                LOG.debug("Starting {} on {}", _thread, this);
+                while (isRunning())
+                    select();
+                runChanges();
+            }
+            finally
+            {
+                LOG.debug("Stopped {} on {}", _thread, this);
+                _thread.setName(name);
+            }
+        }
+
+        /**
+         * <p>Process changes and waits on {@link Selector#select()}.</p>
+         *
+         * @see #submit(Runnable)
+         */
+        public void select()
+        {
+            boolean debug = LOG.isDebugEnabled();
+            try
+            {
+                _state.set(State.CHANGES);
+
+                // Run the changes, and only exit if we ran all changes
+                out: while(true)
+                {
+                    switch (_state.get())
+                    {
+                        case CHANGES:
+                            runChanges();
+                            if (_state.compareAndSet(State.CHANGES, State.SELECT))
+                                break out;
+                            continue;
+                        case MORE_CHANGES:
+                            runChanges();
+                            _state.set(State.CHANGES);
+                            continue;
+                        default:
+                            throw new IllegalStateException();    
+                    }
+                }
+                // Must check first for SELECT and *then* for WAKEUP
+                // because we read the state twice in the assert, and
+                // it could change from SELECT to WAKEUP in between.
+                assert _state.get() == State.SELECT || _state.get() == State.WAKEUP;
+
+                if (debug)
+                    LOG.debug("Selector loop waiting on select");
+                int selected = _selector.select();
+                if (debug)
+                    LOG.debug("Selector loop woken up from select, {}/{} selected", selected, _selector.keys().size());
+
+                _state.set(State.PROCESS);
+
+                Set<SelectionKey> selectedKeys = _selector.selectedKeys();
+                for (SelectionKey key : selectedKeys)
+                {
+                    if (key.isValid())
+                    {
+                        processKey(key);
+                    }
+                    else
+                    {
+                        if (debug)
+                            LOG.debug("Selector loop ignoring invalid key for channel {}", key.channel());
+                        Object attachment = key.attachment();
+                        if (attachment instanceof EndPoint)
+                            ((EndPoint)attachment).close();
+                    }
+                }
+                selectedKeys.clear();
+            }
+            catch (Throwable x)
+            {
+                if (isRunning())
+                    LOG.warn(x);
+                else
+                    LOG.ignore(x);
+            }
+        }
+
+        private void processKey(SelectionKey key)
+        {
+            Object attachment = key.attachment();
+            try
+            {
+                if (attachment instanceof SelectableEndPoint)
+                {
+                    ((SelectableEndPoint)attachment).onSelected();
+                }
+                else if (key.isConnectable())
+                {
+                    processConnect(key, (Connect)attachment);
+                }
+                else if (key.isAcceptable())
+                {
+                    processAccept(key);
+                }
+                else
+                {
+                    throw new IllegalStateException();
+                }
+            }
+            catch (CancelledKeyException x)
+            {
+                LOG.debug("Ignoring cancelled key for channel {}", key.channel());
+                if (attachment instanceof EndPoint)
+                    closeNoExceptions((EndPoint)attachment);
+            }
+            catch (Throwable x)
+            {
+                LOG.warn("Could not process key for channel " + key.channel(), x);
+                if (attachment instanceof EndPoint)
+                    closeNoExceptions((EndPoint)attachment);
+            }
+        }
+
+        private void processConnect(SelectionKey key, Connect connect)
+        {
+            SocketChannel channel = (SocketChannel)key.channel();
+            try
+            {
+                key.attach(connect.attachment);
+                boolean connected = finishConnect(channel);
+                if (connected)
+                {
+                    connect.timeout.cancel();
+                    key.interestOps(0);
+                    EndPoint endpoint = createEndPoint(channel, key);
+                    key.attach(endpoint);
+                }
+                else
+                {
+                    throw new ConnectException();
+                }
+            }
+            catch (Throwable x)
+            {
+                connect.failed(x);
+            }
+        }
+        
+        private void processAccept(SelectionKey key)
+        {
+            ServerSocketChannel server = (ServerSocketChannel)key.channel();
+            SocketChannel channel = null;
+            try
+            {
+                while ((channel = server.accept()) != null)
+                {
+                    accepted(channel);
+                }
+            }
+            catch (Throwable x)
+            {
+                closeNoExceptions(channel);
+                LOG.warn("Accept failed for channel " + channel, x);
+            }
+        }
+
+        private void closeNoExceptions(Closeable closeable)
+        {
+            try
+            {
+                if (closeable != null)
+                    closeable.close();
+            }
+            catch (Throwable x)
+            {
+                LOG.ignore(x);
+            }
+        }
+
+        public void wakeup()
+        {
+            _selector.wakeup();
+        }
+
+        public boolean isSelectorThread()
+        {
+            return Thread.currentThread() == _thread;
+        }
+
+        private EndPoint createEndPoint(SocketChannel channel, SelectionKey selectionKey) throws IOException
+        {
+            EndPoint endPoint = newEndPoint(channel, this, selectionKey);
+            endPointOpened(endPoint);
+            Connection connection = newConnection(channel, endPoint, selectionKey.attachment());
+            endPoint.setConnection(connection);
+            connectionOpened(connection);
+            LOG.debug("Created {}", endPoint);
+            return endPoint;
+        }
+
+        public void destroyEndPoint(EndPoint endPoint)
+        {
+            LOG.debug("Destroyed {}", endPoint);
+            Connection connection = endPoint.getConnection();
+            if (connection != null)
+                connectionClosed(connection);
+            endPointClosed(endPoint);
+        }
+
+        @Override
+        public String dump()
+        {
+            return ContainerLifeCycle.dump(this);
+        }
+
+        @Override
+        public void dump(Appendable out, String indent) throws IOException
+        {
+            out.append(String.valueOf(this)).append(" id=").append(String.valueOf(_id)).append("\n");
+
+            Thread selecting = _thread;
+
+            Object where = "not selecting";
+            StackTraceElement[] trace = selecting == null ? null : selecting.getStackTrace();
+            if (trace != null)
+            {
+                for (StackTraceElement t : trace)
+                    if (t.getClassName().startsWith("org.eclipse.jetty."))
+                    {
+                        where = t;
+                        break;
+                    }
+            }
+
+            Selector selector = _selector;
+            if (selector != null && selector.isOpen())
+            {
+                final ArrayList<Object> dump = new ArrayList<>(selector.keys().size() * 2);
+                dump.add(where);
+
+                DumpKeys dumpKeys = new DumpKeys(dump);
+                submit(dumpKeys);
+                dumpKeys.await(5, TimeUnit.SECONDS);
+
+                ContainerLifeCycle.dump(out, indent, dump);
+            }
+        }
+
+        public void dumpKeysState(List<Object> dumps)
+        {
+            Selector selector = _selector;
+            Set<SelectionKey> keys = selector.keys();
+            dumps.add(selector + " keys=" + keys.size());
+            for (SelectionKey key : keys)
+            {
+                if (key.isValid())
+                    dumps.add(key.attachment() + " iOps=" + key.interestOps() + " rOps=" + key.readyOps());
+                else
+                    dumps.add(key.attachment() + " iOps=-1 rOps=-1");
+            }
+        }
+
+        @Override
+        public String toString()
+        {
+            Selector selector = _selector;
+            return String.format("%s keys=%d selected=%d",
+                    super.toString(),
+                    selector != null && selector.isOpen() ? selector.keys().size() : -1,
+                    selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1);
+        }
+
+        private class DumpKeys implements Runnable
+        {
+            private final CountDownLatch latch = new CountDownLatch(1);
+            private final List<Object> _dumps;
+
+            private DumpKeys(List<Object> dumps)
+            {
+                this._dumps = dumps;
+            }
+
+            @Override
+            public void run()
+            {
+                dumpKeysState(_dumps);
+                latch.countDown();
+            }
+
+            public boolean await(long timeout, TimeUnit unit)
+            {
+                try
+                {
+                    return latch.await(timeout, unit);
+                }
+                catch (InterruptedException x)
+                {
+                    return false;
+                }
+            }
+        }
+
+        private class Acceptor implements Runnable
+        {
+            private final ServerSocketChannel _channel;
+
+            public Acceptor(ServerSocketChannel channel)
+            {
+                this._channel = channel;
+            }
+
+            @Override
+            public void run()
+            {
+                try
+                {
+                    SelectionKey key = _channel.register(_selector, SelectionKey.OP_ACCEPT, null);
+                    LOG.debug("{} acceptor={}", this, key);
+                }
+                catch (Throwable x)
+                {
+                    closeNoExceptions(_channel);
+                    LOG.warn(x);
+                }
+            }
+        }
+
+        private class Accept implements Runnable
+        {
+            private final SocketChannel _channel;
+
+            public Accept(SocketChannel channel)
+            {
+                this._channel = channel;
+            }
+
+            @Override
+            public void run()
+            {
+                try
+                {
+                    SelectionKey key = _channel.register(_selector, 0, null);
+                    EndPoint endpoint = createEndPoint(_channel, key);
+                    key.attach(endpoint);
+                }
+                catch (Throwable x)
+                {
+                    closeNoExceptions(_channel);
+                    LOG.debug(x);
+                }
+            }
+        }
+
+        private class Connect implements Runnable
+        {
+            private final AtomicBoolean failed = new AtomicBoolean();
+            private final SocketChannel channel;
+            private final Object attachment;
+            private final Scheduler.Task timeout;
+
+            public Connect(SocketChannel channel, Object attachment)
+            {
+                this.channel = channel;
+                this.attachment = attachment;
+                this.timeout = scheduler.schedule(new ConnectTimeout(this), getConnectTimeout(), TimeUnit.MILLISECONDS);
+            }
+
+            @Override
+            public void run()
+            {
+                try
+                {
+                    channel.register(_selector, SelectionKey.OP_CONNECT, this);
+                }
+                catch (Throwable x)
+                {
+                    failed(x);
+                }
+            }
+
+            protected void failed(Throwable failure)
+            {
+                if (failed.compareAndSet(false, true))
+                {
+                    timeout.cancel();
+                    closeNoExceptions(channel);
+                    connectionFailed(channel, failure, attachment);
+                }
+            }
+        }
+
+        private class ConnectTimeout implements Runnable
+        {
+            private final Connect connect;
+
+            private ConnectTimeout(Connect connect)
+            {
+                this.connect = connect;
+            }
+
+            @Override
+            public void run()
+            {
+                SocketChannel channel = connect.channel;
+                if (channel.isConnectionPending())
+                {
+                    LOG.debug("Channel {} timed out while connecting, closing it", channel);
+                    connect.failed(new SocketTimeoutException());
+                }
+            }
+        }
+
+        private class Stop implements Runnable
+        {
+            private final CountDownLatch latch = new CountDownLatch(1);
+
+            @Override
+            public void run()
+            {
+                try
+                {
+                    for (SelectionKey key : _selector.keys())
+                    {
+                        Object attachment = key.attachment();
+                        if (attachment instanceof EndPoint)
+                        {
+                            EndPointCloser closer = new EndPointCloser((EndPoint)attachment);
+                            execute(closer);
+                            // We are closing the SelectorManager, so we want to block the
+                            // selector thread here until we have closed all EndPoints.
+                            // This is different than calling close() directly, because close()
+                            // can wait forever, while here we are limited by the stop timeout.
+                            closer.await(getStopTimeout());
+                        }
+                    }
+
+                    closeNoExceptions(_selector);
+                }
+                finally
+                {
+                    latch.countDown();
+                }
+            }
+
+            public boolean await(long timeout)
+            {
+                try
+                {
+                    return latch.await(timeout, TimeUnit.MILLISECONDS);
+                }
+                catch (InterruptedException x)
+                {
+                    return false;
+                }
+            }
+        }
+
+        private class EndPointCloser implements Runnable
+        {
+            private final CountDownLatch latch = new CountDownLatch(1);
+            private final EndPoint endPoint;
+
+            private EndPointCloser(EndPoint endPoint)
+            {
+                this.endPoint = endPoint;
+            }
+
+            @Override
+            public void run()
+            {
+                try
+                {
+                    closeNoExceptions(endPoint.getConnection());
+                }
+                finally
+                {
+                    latch.countDown();
+                }
+            }
+
+            private boolean await(long timeout)
+            {
+                try
+                {
+                    return latch.await(timeout, TimeUnit.MILLISECONDS);
+                }
+                catch (InterruptedException x)
+                {
+                    return false;
+                }
+            }
+        }
+    }
+
+    /**
+     * A {@link SelectableEndPoint} is an {@link EndPoint} that wish to be notified of
+     * non-blocking events by the {@link ManagedSelector}.
+     */
+    public interface SelectableEndPoint extends EndPoint
+    {
+        /**
+         * <p>Callback method invoked when a read or write events has been detected by the {@link ManagedSelector}
+         * for this endpoint.</p>
+         */
+        void onSelected();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/UncheckedPrintWriter.java b/lib/jetty/org/eclipse/jetty/io/UncheckedPrintWriter.java
new file mode 100644 (file)
index 0000000..6898ddf
--- /dev/null
@@ -0,0 +1,682 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * A wrapper for the {@link java.io.PrintWriter} that re-throws the instances of
+ * {@link java.io.IOException} thrown by the underlying implementation of
+ * {@link java.io.Writer} as {@link RuntimeIOException} instances.
+ */
+public class UncheckedPrintWriter extends PrintWriter
+{
+    private static final Logger LOG = Log.getLogger(UncheckedPrintWriter.class);
+
+    private boolean _autoFlush = false;
+    private IOException _ioException;
+    private boolean _isClosed = false;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Line separator string. This is the value of the line.separator property
+     * at the moment that the stream was created.
+     */
+    private String _lineSeparator;
+
+    public UncheckedPrintWriter(Writer out)
+    {
+        this(out,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new PrintWriter.
+     * 
+     * @param out
+     *            A character-output stream
+     * @param autoFlush
+     *            A boolean; if true, the println() methods will flush the
+     *            output buffer
+     */
+    public UncheckedPrintWriter(Writer out, boolean autoFlush)
+    {
+        super(out,autoFlush);
+        this._autoFlush = autoFlush;
+        this._lineSeparator = System.getProperty("line.separator");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new PrintWriter, without automatic line flushing, from an
+     * existing OutputStream. This convenience constructor creates the necessary
+     * intermediate OutputStreamWriter, which will convert characters into bytes
+     * using the default character encoding.
+     * 
+     * @param out
+     *            An output stream
+     * 
+     * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream)
+     */
+    public UncheckedPrintWriter(OutputStream out)
+    {
+        this(out,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new PrintWriter from an existing OutputStream. This convenience
+     * constructor creates the necessary intermediate OutputStreamWriter, which
+     * will convert characters into bytes using the default character encoding.
+     * 
+     * @param out
+     *            An output stream
+     * @param autoFlush
+     *            A boolean; if true, the println() methods will flush the
+     *            output buffer
+     * 
+     * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream)
+     */
+    public UncheckedPrintWriter(OutputStream out, boolean autoFlush)
+    {
+        this(new BufferedWriter(new OutputStreamWriter(out)),autoFlush);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public boolean checkError()
+    {
+        return _ioException!=null || super.checkError();
+    }
+    
+    /* ------------------------------------------------------------ */
+    private void setError(Throwable th)
+    {
+      
+        super.setError();
+
+        if (th instanceof IOException)
+            _ioException=(IOException)th;
+        else
+        {
+            _ioException=new IOException(String.valueOf(th));
+            _ioException.initCause(th);
+        }
+
+        LOG.debug(th);
+    }
+
+
+    @Override
+    protected void setError()
+    {
+        setError(new IOException());
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Check to make sure that the stream has not been closed */
+    private void isOpen() throws IOException
+    {       
+        if (_ioException!=null)
+            throw new RuntimeIOException(_ioException); 
+        
+        if (_isClosed)
+            throw new IOException("Stream closed");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Flush the stream.
+     */
+    @Override
+    public void flush()
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                isOpen();
+                out.flush();
+            }
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Close the stream.
+     */
+    @Override
+    public void close()
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                out.close();
+                _isClosed = true;
+            }
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Write a single character.
+     * 
+     * @param c
+     *            int specifying a character to be written.
+     */
+    @Override
+    public void write(int c)
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                isOpen();
+                out.write(c);
+            }
+        }
+        catch (InterruptedIOException x)
+        {
+            Thread.currentThread().interrupt();
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Write a portion of an array of characters.
+     * 
+     * @param buf
+     *            Array of characters
+     * @param off
+     *            Offset from which to start writing characters
+     * @param len
+     *            Number of characters to write
+     */
+    @Override
+    public void write(char buf[], int off, int len)
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                isOpen();
+                out.write(buf,off,len);
+            }
+        }
+        catch (InterruptedIOException x)
+        {
+            Thread.currentThread().interrupt();
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Write an array of characters. This method cannot be inherited from the
+     * Writer class because it must suppress I/O exceptions.
+     * 
+     * @param buf
+     *            Array of characters to be written
+     */
+    @Override
+    public void write(char buf[])
+    { 
+        this.write(buf,0,buf.length);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Write a portion of a string.
+     * 
+     * @param s
+     *            A String
+     * @param off
+     *            Offset from which to start writing characters
+     * @param len
+     *            Number of characters to write
+     */
+    @Override
+    public void write(String s, int off, int len)
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                isOpen();
+                out.write(s,off,len);
+            }
+        }
+        catch (InterruptedIOException x)
+        {
+            Thread.currentThread().interrupt();
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Write a string. This method cannot be inherited from the Writer class
+     * because it must suppress I/O exceptions.
+     * 
+     * @param s
+     *            String to be written
+     */
+    @Override
+    public void write(String s)
+    {
+        this.write(s,0,s.length());
+    }
+
+    private void newLine()
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                isOpen();
+                out.write(_lineSeparator);
+                if (_autoFlush)
+                    out.flush();
+            }
+        }
+        catch (InterruptedIOException x)
+        {
+            Thread.currentThread().interrupt();
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a boolean value. The string produced by <code>{@link
+     * java.lang.String#valueOf(boolean)}</code> is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     * 
+     * @param b
+     *            The <code>boolean</code> to be printed
+     */
+    @Override
+    public void print(boolean b)
+    {
+        this.write(b?"true":"false");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a character. The character is translated into one or more bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     * 
+     * @param c
+     *            The <code>char</code> to be printed
+     */
+    @Override
+    public void print(char c)
+    {
+        this.write(c);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an integer. The string produced by <code>{@link
+     * java.lang.String#valueOf(int)}</code> is translated into bytes according
+     * to the platform's default character encoding, and these bytes are written
+     * in exactly the manner of the <code>{@link #write(int)}</code> method.
+     * 
+     * @param i
+     *            The <code>int</code> to be printed
+     * @see java.lang.Integer#toString(int)
+     */
+    @Override
+    public void print(int i)
+    {
+        this.write(String.valueOf(i));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a long integer. The string produced by <code>{@link
+     * java.lang.String#valueOf(long)}</code> is translated into bytes according
+     * to the platform's default character encoding, and these bytes are written
+     * in exactly the manner of the <code>{@link #write(int)}</code> method.
+     * 
+     * @param l
+     *            The <code>long</code> to be printed
+     * @see java.lang.Long#toString(long)
+     */
+    @Override
+    public void print(long l)
+    {
+        this.write(String.valueOf(l));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a floating-point number. The string produced by <code>{@link
+     * java.lang.String#valueOf(float)}</code> is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     * 
+     * @param f
+     *            The <code>float</code> to be printed
+     * @see java.lang.Float#toString(float)
+     */
+    @Override
+    public void print(float f)
+    {
+        this.write(String.valueOf(f));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a double-precision floating-point number. The string produced by
+     * <code>{@link java.lang.String#valueOf(double)}</code> is translated into
+     * bytes according to the platform's default character encoding, and these
+     * bytes are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     * 
+     * @param d
+     *            The <code>double</code> to be printed
+     * @see java.lang.Double#toString(double)
+     */
+    @Override
+    public void print(double d)
+    {
+        this.write(String.valueOf(d));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an array of characters. The characters are converted into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     * 
+     * @param s
+     *            The array of chars to be printed
+     * 
+     * @throws NullPointerException
+     *             If <code>s</code> is <code>null</code>
+     */
+    @Override
+    public void print(char s[])
+    {
+        this.write(s);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a string. If the argument is <code>null</code> then the string
+     * <code>"null"</code> is printed. Otherwise, the string's characters are
+     * converted into bytes according to the platform's default character
+     * encoding, and these bytes are written in exactly the manner of the
+     * <code>{@link #write(int)}</code> method.
+     * 
+     * @param s
+     *            The <code>String</code> to be printed
+     */
+    @Override
+    public void print(String s)
+    {
+        if (s == null)
+        {
+            s = "null";
+        }
+        this.write(s);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an object. The string produced by the <code>{@link
+     * java.lang.String#valueOf(Object)}</code> method is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     * 
+     * @param obj
+     *            The <code>Object</code> to be printed
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public void print(Object obj)
+    {
+        this.write(String.valueOf(obj));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Terminate the current line by writing the line separator string. The line
+     * separator string is defined by the system property
+     * <code>line.separator</code>, and is not necessarily a single newline
+     * character (<code>'\n'</code>).
+     */
+    @Override
+    public void println()
+    {
+        this.newLine();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a boolean value and then terminate the line. This method behaves as
+     * though it invokes <code>{@link #print(boolean)}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>boolean</code> value to be printed
+     */
+    @Override
+    public void println(boolean x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a character and then terminate the line. This method behaves as
+     * though it invokes <code>{@link #print(char)}</code> and then <code>{@link
+     * #println()}</code>.
+     * 
+     * @param x
+     *            the <code>char</code> value to be printed
+     */
+    @Override
+    public void println(char x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an integer and then terminate the line. This method behaves as
+     * though it invokes <code>{@link #print(int)}</code> and then <code>{@link
+     * #println()}</code>.
+     * 
+     * @param x
+     *            the <code>int</code> value to be printed
+     */
+    @Override
+    public void println(int x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a long integer and then terminate the line. This method behaves as
+     * though it invokes <code>{@link #print(long)}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>long</code> value to be printed
+     */
+    @Override
+    public void println(long x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a floating-point number and then terminate the line. This method
+     * behaves as though it invokes <code>{@link #print(float)}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>float</code> value to be printed
+     */
+    @Override
+    public void println(float x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a double-precision floating-point number and then terminate the
+     * line. This method behaves as though it invokes <code>{@link
+     * #print(double)}</code> and then <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>double</code> value to be printed
+     */
+    /* ------------------------------------------------------------ */
+    @Override
+    public void println(double x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an array of characters and then terminate the line. This method
+     * behaves as though it invokes <code>{@link #print(char[])}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the array of <code>char</code> values to be printed
+     */
+    @Override
+    public void println(char x[])
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a String and then terminate the line. This method behaves as though
+     * it invokes <code>{@link #print(String)}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>String</code> value to be printed
+     */
+    @Override
+    public void println(String x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an Object and then terminate the line. This method behaves as
+     * though it invokes <code>{@link #print(Object)}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>Object</code> value to be printed
+     */
+    @Override
+    public void println(Object x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/WriteFlusher.java b/lib/jetty/org/eclipse/jetty/io/WriteFlusher.java
new file mode 100644 (file)
index 0000000..fccc622
--- /dev/null
@@ -0,0 +1,504 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.WritePendingException;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * A Utility class to help implement {@link EndPoint#write(Callback, ByteBuffer...)} by calling
+ * {@link EndPoint#flush(ByteBuffer...)} until all content is written.
+ * The abstract method {@link #onIncompleteFlushed()} is called when not all content has been written after a call to
+ * flush and should organise for the {@link #completeWrite()} method to be called when a subsequent call to flush
+ * should  be able to make more progress.
+ * <p>
+ */
+abstract public class WriteFlusher
+{
+    private static final Logger LOG = Log.getLogger(WriteFlusher.class);
+    private static final boolean DEBUG = LOG.isDebugEnabled(); // Easy for the compiler to remove the code if DEBUG==false
+    private static final ByteBuffer[] EMPTY_BUFFERS = new ByteBuffer[0];
+    private static final EnumMap<StateType, Set<StateType>> __stateTransitions = new EnumMap<>(StateType.class);
+    private static final State __IDLE = new IdleState();
+    private static final State __WRITING = new WritingState();
+    private static final State __COMPLETING = new CompletingState();
+    private final EndPoint _endPoint;
+    private final AtomicReference<State> _state = new AtomicReference<>();
+
+    static
+    {
+        // fill the state machine
+        __stateTransitions.put(StateType.IDLE, EnumSet.of(StateType.WRITING));
+        __stateTransitions.put(StateType.WRITING, EnumSet.of(StateType.IDLE, StateType.PENDING, StateType.FAILED));
+        __stateTransitions.put(StateType.PENDING, EnumSet.of(StateType.COMPLETING,StateType.IDLE));
+        __stateTransitions.put(StateType.COMPLETING, EnumSet.of(StateType.IDLE, StateType.PENDING, StateType.FAILED));
+        __stateTransitions.put(StateType.FAILED, EnumSet.of(StateType.IDLE));
+    }
+
+    // A write operation may either complete immediately:
+    //     IDLE-->WRITING-->IDLE
+    // Or it may not completely flush and go via the PENDING state
+    //     IDLE-->WRITING-->PENDING-->COMPLETING-->IDLE
+    // Or it may take several cycles to complete
+    //     IDLE-->WRITING-->PENDING-->COMPLETING-->PENDING-->COMPLETING-->IDLE
+    //
+    // If a failure happens while in IDLE, it is a noop since there is no operation to tell of the failure.
+    // If a failure happens while in WRITING, but the the write has finished successfully or with an IOExceptions,
+    // the callback's complete or respectively failed methods will be called.
+    // If a failure happens in PENDING state, then the fail method calls the pending callback and moves to IDLE state
+    //
+    //   IDLE--(fail)-->IDLE
+    //   IDLE-->WRITING--(fail)-->FAILED-->IDLE
+    //   IDLE-->WRITING-->PENDING--(fail)-->IDLE
+    //   IDLE-->WRITING-->PENDING-->COMPLETING--(fail)-->FAILED-->IDLE
+    //
+    // So a call to fail in the PENDING state will be directly handled and the state changed to IDLE
+    // A call to fail in the WRITING or COMPLETING states will just set the state to FAILED and the failure will be
+    // handled with the write or completeWrite methods try to move the state from what they thought it was.
+    //
+
+    protected WriteFlusher(EndPoint endPoint)
+    {
+        _state.set(__IDLE);
+        _endPoint = endPoint;
+    }
+
+    private enum StateType
+    {
+        IDLE,
+        WRITING,
+        PENDING,
+        COMPLETING,
+        FAILED
+    }
+
+    /**
+     * Tries to update the current state to the given new state.
+     * @param previous the expected current state
+     * @param next the desired new state
+     * @return the previous state or null if the state transition failed
+     * @throws WritePendingException if currentState is WRITING and new state is WRITING (api usage error)
+     */
+    private boolean updateState(State previous,State next)
+    {
+        if (!isTransitionAllowed(previous,next))
+            throw new IllegalStateException();
+
+        boolean updated = _state.compareAndSet(previous, next);
+        if (DEBUG)
+            LOG.debug("update {}:{}{}{}", this, previous, updated?"-->":"!->",next);
+        return updated;
+    }
+
+    private void fail(PendingState pending)
+    {
+        State current = _state.get();
+        if (current.getType()==StateType.FAILED)
+        {
+            FailedState failed=(FailedState)current;
+            if (updateState(failed,__IDLE))
+            {
+                pending.fail(failed.getCause());
+                return;
+            }
+        }
+        throw new IllegalStateException();
+    }
+
+    private void ignoreFail()
+    {
+        State current = _state.get();
+        while (current.getType()==StateType.FAILED)
+        {
+            if (updateState(current,__IDLE))
+                return;
+            current = _state.get();
+        }
+    }
+
+    private boolean isTransitionAllowed(State currentState, State newState)
+    {
+        Set<StateType> allowedNewStateTypes = __stateTransitions.get(currentState.getType());
+        if (!allowedNewStateTypes.contains(newState.getType()))
+        {
+            LOG.warn("{}: {} -> {} not allowed", this, currentState, newState);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * State represents a State of WriteFlusher.
+     */
+    private static class State
+    {
+        private final StateType _type;
+
+        private State(StateType stateType)
+        {
+            _type = stateType;
+        }
+
+        public StateType getType()
+        {
+            return _type;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s", _type);
+        }
+    }
+
+    /**
+     * In IdleState WriteFlusher is idle and accepts new writes
+     */
+    private static class IdleState extends State
+    {
+        private IdleState()
+        {
+            super(StateType.IDLE);
+        }
+    }
+
+    /**
+     * In WritingState WriteFlusher is currently writing.
+     */
+    private static class WritingState extends State
+    {
+        private WritingState()
+        {
+            super(StateType.WRITING);
+        }
+    }
+
+    /**
+     * In FailedState no more operations are allowed. The current implementation will never recover from this state.
+     */
+    private static class FailedState extends State
+    {
+        private final Throwable _cause;
+        private FailedState(Throwable cause)
+        {
+            super(StateType.FAILED);
+            _cause=cause;
+        }
+
+        public Throwable getCause()
+        {
+            return _cause;
+        }
+    }
+
+    /**
+     * In CompletingState WriteFlusher is flushing buffers that have not been fully written in write(). If write()
+     * didn't flush all buffers in one go, it'll switch the State to PendingState. completeWrite() will then switch to
+     * this state and try to flush the remaining buffers.
+     */
+    private static class CompletingState extends State
+    {
+        private CompletingState()
+        {
+            super(StateType.COMPLETING);
+        }
+    }
+
+    /**
+     * In PendingState not all buffers could be written in one go. Then write() will switch to PendingState() and
+     * preserve the state by creating a new PendingState object with the given parameters.
+     */
+    private class PendingState extends State
+    {
+        private final Callback _callback;
+        private final ByteBuffer[] _buffers;
+
+        private PendingState(ByteBuffer[] buffers, Callback callback)
+        {
+            super(StateType.PENDING);
+            _buffers = compact(buffers);
+            _callback = callback;
+        }
+
+        public ByteBuffer[] getBuffers()
+        {
+            return _buffers;
+        }
+
+        protected boolean fail(Throwable cause)
+        {
+            if (_callback!=null)
+            {
+                _callback.failed(cause);
+                return true;
+            }
+            return false;
+        }
+
+        protected void complete()
+        {
+            if (_callback!=null)
+                _callback.succeeded();
+        }
+
+        /**
+         * Compacting the buffers is needed because the semantic of WriteFlusher is
+         * to write the buffers and if the caller sees that the buffer is consumed,
+         * then it can recycle it.
+         * If we do not compact, then it is possible that we store a consumed buffer,
+         * which is then recycled and refilled; when the WriteFlusher is invoked to
+         * complete the write, it will write the refilled bytes, garbling the content.
+         *
+         * @param buffers the buffers to compact
+         * @return the compacted buffers
+         */
+        private ByteBuffer[] compact(ByteBuffer[] buffers)
+        {
+            int length = buffers.length;
+
+            // Just one element, no need to compact
+            if (length < 2)
+                return buffers;
+
+            // How many still have content ?
+            int consumed = 0;
+            while (consumed < length && BufferUtil.isEmpty(buffers[consumed]))
+                ++consumed;
+
+            // All of them still have content, no need to compact
+            if (consumed == 0)
+                return buffers;
+
+            // None has content, return empty
+            if (consumed == length)
+                return EMPTY_BUFFERS;
+
+            return Arrays.copyOfRange(buffers,consumed,length);
+        }
+    }
+
+    /**
+     * Abstract call to be implemented by specific WriteFlushers. It should schedule a call to {@link #completeWrite()}
+     * or {@link #onFail(Throwable)} when appropriate.
+     */
+    abstract protected void onIncompleteFlushed();
+
+    /**
+     * Tries to switch state to WRITING. If successful it writes the given buffers to the EndPoint. If state transition
+     * fails it'll fail the callback.
+     *
+     * If not all buffers can be written in one go it creates a new <code>PendingState</code> object to preserve the state
+     * and then calls {@link #onIncompleteFlushed()}. The remaining buffers will be written in {@link #completeWrite()}.
+     *
+     * If all buffers have been written it calls callback.complete().
+     *
+     * @param callback the callback to call on either failed or complete
+     * @param buffers the buffers to flush to the endpoint
+     */
+    public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+    {
+        if (DEBUG)
+            LOG.debug("write: {} {}", this, BufferUtil.toDetailString(buffers));
+
+        if (!updateState(__IDLE,__WRITING))
+            throw new WritePendingException();
+
+        try
+        {
+            boolean flushed=_endPoint.flush(buffers);
+            if (DEBUG)
+                LOG.debug("flushed {}", flushed);
+
+            // Are we complete?
+            for (ByteBuffer b : buffers)
+            {
+                if (!flushed||BufferUtil.hasContent(b))
+                {
+                    PendingState pending=new PendingState(buffers, callback);
+                    if (updateState(__WRITING,pending))
+                        onIncompleteFlushed();
+                    else
+                        fail(pending);
+                    return;
+                }
+            }
+
+            // If updateState didn't succeed, we don't care as our buffers have been written
+            if (!updateState(__WRITING,__IDLE))
+                ignoreFail();
+            if (callback!=null)
+                callback.succeeded();
+        }
+        catch (IOException e)
+        {
+            if (DEBUG)
+                LOG.debug("write exception", e);
+            if (updateState(__WRITING,__IDLE))
+            {
+                if (callback!=null)
+                    callback.failed(e);
+            }
+            else
+                fail(new PendingState(buffers, callback));
+        }
+    }
+
+
+    /**
+     * Complete a write that has not completed and that called {@link #onIncompleteFlushed()} to request a call to this
+     * method when a call to {@link EndPoint#flush(ByteBuffer...)} is likely to be able to progress.
+     *
+     * It tries to switch from PENDING to COMPLETING. If state transition fails, then it does nothing as the callback
+     * should have been already failed. That's because the only way to switch from PENDING outside this method is
+     * {@link #onFail(Throwable)} or {@link #onClose()}
+     */
+    public void completeWrite()
+    {
+        if (DEBUG)
+            LOG.debug("completeWrite: {}", this);
+
+        State previous = _state.get();
+
+        if (previous.getType()!=StateType.PENDING)
+            return; // failure already handled.
+
+        PendingState pending = (PendingState)previous;
+        if (!updateState(pending,__COMPLETING))
+            return; // failure already handled.
+
+        try
+        {
+            ByteBuffer[] buffers = pending.getBuffers();
+
+            boolean flushed=_endPoint.flush(buffers);
+            if (DEBUG)
+                LOG.debug("flushed {}", flushed);
+
+            // Are we complete?
+            for (ByteBuffer b : buffers)
+            {
+                if (!flushed || BufferUtil.hasContent(b))
+                {
+                    if (updateState(__COMPLETING,pending))
+                        onIncompleteFlushed();
+                    else
+                        fail(pending);
+                    return;
+                }
+            }
+
+            // If updateState didn't succeed, we don't care as our buffers have been written
+            if (!updateState(__COMPLETING,__IDLE))
+                ignoreFail();
+            pending.complete();
+        }
+        catch (IOException e)
+        {
+            if (DEBUG)
+                LOG.debug("completeWrite exception", e);
+            if(updateState(__COMPLETING,__IDLE))
+                pending.fail(e);
+            else
+                fail(pending);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Notify the flusher of a failure
+     * @param cause The cause of the failure
+     * @return true if the flusher passed the failure to a {@link Callback} instance
+     */
+    public boolean onFail(Throwable cause)
+    {
+        // Keep trying to handle the failure until we get to IDLE or FAILED state
+        while(true)
+        {
+            State current=_state.get();
+            switch(current.getType())
+            {
+                case IDLE:
+                case FAILED:
+                    if (DEBUG)
+                        LOG.debug("ignored: {} {}", this, cause);
+                    return false;
+
+                case PENDING:
+                    if (DEBUG)
+                        LOG.debug("failed: {} {}", this, cause);
+
+                    PendingState pending = (PendingState)current;
+                    if (updateState(pending,__IDLE))
+                        return pending.fail(cause);
+                    break;
+
+                default:
+                    if (DEBUG)
+                        LOG.debug("failed: {} {}", this, cause);
+
+                    if (updateState(current,new FailedState(cause)))
+                        return false;
+                    break;
+            }
+        }
+    }
+
+    public void onClose()
+    {
+        if (_state.get()==__IDLE)
+            return;
+        onFail(new ClosedChannelException());
+    }
+
+    boolean isIdle()
+    {
+        return _state.get().getType() == StateType.IDLE;
+    }
+
+    public boolean isInProgress()
+    {
+        switch(_state.get().getType())
+        {
+            case WRITING:
+            case PENDING:
+            case COMPLETING:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("WriteFlusher@%x{%s}", hashCode(), _state.get());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/WriterOutputStream.java b/lib/jetty/org/eclipse/jetty/io/WriterOutputStream.java
new file mode 100644 (file)
index 0000000..d08b473
--- /dev/null
@@ -0,0 +1,101 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+
+/* ------------------------------------------------------------ */
+/** Wrap a Writer as an OutputStream.
+ * When all you have is a Writer and only an OutputStream will do.
+ * Try not to use this as it indicates that your design is a dogs
+ * breakfast (JSP made me write it).
+ * 
+ */
+public class WriterOutputStream extends OutputStream
+{
+    protected final Writer _writer;
+    protected final Charset _encoding;
+    private final byte[] _buf=new byte[1];
+    
+    /* ------------------------------------------------------------ */
+    public WriterOutputStream(Writer writer, String encoding)
+    {
+        _writer=writer;
+        _encoding=encoding==null?null:Charset.forName(encoding);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public WriterOutputStream(Writer writer)
+    {
+        _writer=writer;
+        _encoding=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void close()
+        throws IOException
+    {
+        _writer.close();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void flush()
+        throws IOException
+    {
+        _writer.flush();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(byte[] b) 
+        throws IOException
+    {
+        if (_encoding==null)
+            _writer.write(new String(b));
+        else
+            _writer.write(new String(b,_encoding));
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(byte[] b, int off, int len)
+        throws IOException
+    {
+        if (_encoding==null)
+            _writer.write(new String(b,off,len));
+        else
+            _writer.write(new String(b,off,len,_encoding));
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public synchronized void write(int b)
+        throws IOException
+    {
+        _buf[0]=(byte)b;
+        write(_buf);
+    }
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/io/package-info.java b/lib/jetty/org/eclipse/jetty/io/package-info.java
new file mode 100644 (file)
index 0000000..6316823
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty IO : Core classes for Jetty IO subsystem
+ */
+package org.eclipse.jetty.io;
+
diff --git a/lib/jetty/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java b/lib/jetty/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java
new file mode 100644 (file)
index 0000000..ce3be14
--- /dev/null
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.ssl;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class SslClientConnectionFactory implements ClientConnectionFactory
+{
+    public static final String SSL_PEER_HOST_CONTEXT_KEY = "ssl.peer.host";
+    public static final String SSL_PEER_PORT_CONTEXT_KEY = "ssl.peer.port";
+    public static final String SSL_ENGINE_CONTEXT_KEY = "ssl.engine";
+
+    private final SslContextFactory sslContextFactory;
+    private final ByteBufferPool byteBufferPool;
+    private final Executor executor;
+    private final ClientConnectionFactory connectionFactory;
+
+    public SslClientConnectionFactory(SslContextFactory sslContextFactory, ByteBufferPool byteBufferPool, Executor executor, ClientConnectionFactory connectionFactory)
+    {
+        this.sslContextFactory = sslContextFactory;
+        this.byteBufferPool = byteBufferPool;
+        this.executor = executor;
+        this.connectionFactory = connectionFactory;
+    }
+
+    @Override
+    public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+    {
+        String host = (String)context.get(SSL_PEER_HOST_CONTEXT_KEY);
+        int port = (Integer)context.get(SSL_PEER_PORT_CONTEXT_KEY);
+        SSLEngine engine = sslContextFactory.newSSLEngine(host, port);
+        engine.setUseClientMode(true);
+        context.put(SSL_ENGINE_CONTEXT_KEY, engine);
+
+        SslConnection sslConnection = newSslConnection(byteBufferPool, executor, endPoint, engine);
+        sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
+        endPoint.setConnection(sslConnection);
+        EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
+        appEndPoint.setConnection(connectionFactory.newConnection(appEndPoint, context));
+
+        return sslConnection;
+    }
+
+    protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
+    {
+        return new SslConnection(byteBufferPool, executor, endPoint, engine);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ssl/SslConnection.java b/lib/jetty/org/eclipse/jetty/io/ssl/SslConnection.java
new file mode 100644 (file)
index 0000000..ee9e449
--- /dev/null
@@ -0,0 +1,915 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.ssl;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLException;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.AbstractEndPoint;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.FillInterest;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.WriteFlusher;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * A Connection that acts as an interceptor between an EndPoint providing SSL encrypted data
+ * and another consumer of an EndPoint (typically an {@link Connection} like HttpConnection) that
+ * wants unencrypted data.
+ * <p>
+ * The connector uses an {@link EndPoint} (typically {@link SelectChannelEndPoint}) as
+ * it's source/sink of encrypted data.   It then provides an endpoint via {@link #getDecryptedEndPoint()} to
+ * expose a source/sink of unencrypted data to another connection (eg HttpConnection).
+ * <p>
+ * The design of this class is based on a clear separation between the passive methods, which do not block nor schedule any
+ * asynchronous callbacks, and active methods that do schedule asynchronous callbacks.
+ * <p>
+ * The passive methods are {@link DecryptedEndPoint#fill(ByteBuffer)} and {@link DecryptedEndPoint#flush(ByteBuffer...)}. They make best
+ * effort attempts to progress the connection using only calls to the encrypted {@link EndPoint#fill(ByteBuffer)} and {@link EndPoint#flush(ByteBuffer...)}
+ * methods.  They will never block nor schedule any readInterest or write callbacks.   If a fill/flush cannot progress either because
+ * of network congestion or waiting for an SSL handshake message, then the fill/flush will simply return with zero bytes filled/flushed.
+ * Specifically, if a flush cannot proceed because it needs to receive a handshake message, then the flush will attempt to fill bytes from the
+ * encrypted endpoint, but if insufficient bytes are read it will NOT call {@link EndPoint#fillInterested(Callback)}.
+ * <p>
+ * It is only the active methods : {@link DecryptedEndPoint#fillInterested(Callback)} and
+ * {@link DecryptedEndPoint#write(Callback, ByteBuffer...)} that may schedule callbacks by calling the encrypted
+ * {@link EndPoint#fillInterested(Callback)} and {@link EndPoint#write(Callback, ByteBuffer...)}
+ * methods.  For normal data handling, the decrypted fillInterest method will result in an encrypted fillInterest and a decrypted
+ * write will result in an encrypted write. However, due to SSL handshaking requirements, it is also possible for a decrypted fill
+ * to call the encrypted write and for the decrypted flush to call the encrypted fillInterested methods.
+ * <p>
+ * MOST IMPORTANTLY, the encrypted callbacks from the active methods (#onFillable() and WriteFlusher#completeWrite()) do no filling or flushing
+ * themselves.  Instead they simple make the callbacks to the decrypted callbacks, so that the passive encrypted fill/flush will
+ * be called again and make another best effort attempt to progress the connection.
+ *
+ */
+public class SslConnection extends AbstractConnection
+{
+    private static final Logger LOG = Log.getLogger(SslConnection.class);
+    private static final boolean DEBUG = LOG.isDebugEnabled(); // Easy for the compiler to remove the code if DEBUG==false
+    private static final ByteBuffer __FILL_CALLED_FLUSH= BufferUtil.allocate(0);
+    private static final ByteBuffer __FLUSH_CALLED_FILL= BufferUtil.allocate(0);
+    private final ByteBufferPool _bufferPool;
+    private final SSLEngine _sslEngine;
+    private final DecryptedEndPoint _decryptedEndPoint;
+    private ByteBuffer _decryptedInput;
+    private ByteBuffer _encryptedInput;
+    private ByteBuffer _encryptedOutput;
+    private final boolean _encryptedDirectBuffers = false;
+    private final boolean _decryptedDirectBuffers = false;
+    private final Runnable _runCompletWrite = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            _decryptedEndPoint.getWriteFlusher().completeWrite();
+        }
+    };
+    private boolean _renegotiationAllowed;
+
+    public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine)
+    {
+        // This connection does not execute calls to onfillable, so they will be called by the selector thread.
+        // onfillable does not block and will only wakeup another thread to do the actual reading and handling.
+        super(endPoint, executor, !EXECUTE_ONFILLABLE);
+        this._bufferPool = byteBufferPool;
+        this._sslEngine = sslEngine;
+        this._decryptedEndPoint = newDecryptedEndPoint();
+    }
+
+    protected DecryptedEndPoint newDecryptedEndPoint()
+    {
+        return new DecryptedEndPoint();
+    }
+
+    public SSLEngine getSSLEngine()
+    {
+        return _sslEngine;
+    }
+
+    public DecryptedEndPoint getDecryptedEndPoint()
+    {
+        return _decryptedEndPoint;
+    }
+
+    public boolean isRenegotiationAllowed()
+    {
+        return _renegotiationAllowed;
+    }
+
+    public void setRenegotiationAllowed(boolean renegotiationAllowed)
+    {
+        this._renegotiationAllowed = renegotiationAllowed;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        try
+        {
+            // Begin the handshake
+            _sslEngine.beginHandshake();
+            super.onOpen();
+            getDecryptedEndPoint().getConnection().onOpen();
+        }
+        catch (SSLException x)
+        {
+            getEndPoint().close();
+            throw new RuntimeIOException(x);
+        }
+    }
+
+    @Override
+    public void onClose()
+    {
+        _decryptedEndPoint.getConnection().onClose();
+        super.onClose();
+    }
+
+    @Override
+    public void close()
+    {
+        getDecryptedEndPoint().getConnection().close();
+    }
+
+    @Override
+    public void onFillable()
+    {
+        // onFillable means that there are encrypted bytes ready to be filled.
+        // however we do not fill them here on this callback, but instead wakeup
+        // the decrypted readInterest and/or writeFlusher so that they will attempt
+        // to do the fill and/or flush again and these calls will do the actually
+        // filling.
+
+        if (DEBUG)
+            LOG.debug("onFillable enter {}", _decryptedEndPoint);
+
+        // We have received a close handshake, close the end point to send FIN.
+        if (_decryptedEndPoint.isInputShutdown())
+            _decryptedEndPoint.close();
+
+        // wake up whoever is doing the fill or the flush so they can
+        // do all the filling, unwrapping, wrapping and flushing
+        _decryptedEndPoint.getFillInterest().fillable();
+
+        // If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
+        synchronized(_decryptedEndPoint)
+        {
+            if (_decryptedEndPoint._flushRequiresFillToProgress)
+            {
+                _decryptedEndPoint._flushRequiresFillToProgress = false;
+                getExecutor().execute(_runCompletWrite);
+            }
+        }
+
+        if (DEBUG)
+            LOG.debug("onFillable exit {}", _decryptedEndPoint);
+    }
+
+    @Override
+    public void onFillInterestedFailed(Throwable cause)
+    {
+        // this means that the fill interest in encrypted bytes has failed.
+        // However we do not handle that here on this callback, but instead wakeup
+        // the decrypted readInterest and/or writeFlusher so that they will attempt
+        // to do the fill and/or flush again and these calls will do the actually
+        // handle the cause.
+        _decryptedEndPoint.getFillInterest().onFail(cause);
+
+        boolean failFlusher = false;
+        synchronized(_decryptedEndPoint)
+        {
+            if (_decryptedEndPoint._flushRequiresFillToProgress)
+            {
+                _decryptedEndPoint._flushRequiresFillToProgress = false;
+                failFlusher = true;
+            }
+        }
+        if (failFlusher)
+            _decryptedEndPoint.getWriteFlusher().onFail(cause);
+    }
+
+    @Override
+    public String toString()
+    {
+        ByteBuffer b = _encryptedInput;
+        int ei=b==null?-1:b.remaining();
+        b = _encryptedOutput;
+        int eo=b==null?-1:b.remaining();
+        b = _decryptedInput;
+        int di=b==null?-1:b.remaining();
+
+        return String.format("SslConnection@%x{%s,eio=%d/%d,di=%d} -> %s",
+                hashCode(),
+                _sslEngine.getHandshakeStatus(),
+                ei,eo,di,
+                _decryptedEndPoint.getConnection());
+    }
+
+    public class DecryptedEndPoint extends AbstractEndPoint
+    {
+        private boolean _fillRequiresFlushToProgress;
+        private boolean _flushRequiresFillToProgress;
+        private boolean _cannotAcceptMoreAppDataToFlush;
+        private boolean _handshaken;
+        private boolean _underFlown;
+
+        private final Callback _writeCallback = new Callback()
+        {
+            @Override
+            public void succeeded()
+            {
+                // This means that a write of encrypted data has completed.  Writes are done
+                // only if there is a pending writeflusher or a read needed to write
+                // data.  In either case the appropriate callback is passed on.
+                boolean fillable = false;
+                synchronized (DecryptedEndPoint.this)
+                {
+                    if (DEBUG)
+                        LOG.debug("write.complete {}", SslConnection.this.getEndPoint());
+
+                    releaseEncryptedOutputBuffer();
+
+                    _cannotAcceptMoreAppDataToFlush = false;
+
+                    if (_fillRequiresFlushToProgress)
+                    {
+                        _fillRequiresFlushToProgress = false;
+                        fillable = true;
+                    }
+                }
+                if (fillable)
+                    getFillInterest().fillable();
+                getExecutor().execute(_runCompletWrite);
+            }
+
+            @Override
+            public void failed(final Throwable x)
+            {
+                // This means that a write of data has failed.  Writes are done
+                // only if there is an active writeflusher or a read needed to write
+                // data.  In either case the appropriate callback is passed on.
+                boolean fail_filler = false;
+                synchronized (DecryptedEndPoint.this)
+                {
+                    if (DEBUG)
+                        LOG.debug("{} write.failed", SslConnection.this, x);
+                    BufferUtil.clear(_encryptedOutput);
+                    releaseEncryptedOutputBuffer();
+
+                    _cannotAcceptMoreAppDataToFlush = false;
+
+                    if (_fillRequiresFlushToProgress)
+                    {
+                        _fillRequiresFlushToProgress = false;
+                        fail_filler = true;
+                    }
+                }
+
+                final boolean filler_failed=fail_filler;
+
+                failedCallback(new Callback()
+                {
+                    @Override
+                    public void succeeded()
+                    {                        
+                    }
+
+                    @Override
+                    public void failed(Throwable x)
+                    {
+                        if (filler_failed)
+                            getFillInterest().onFail(x);
+                        getWriteFlusher().onFail(x);
+                    }
+                    
+                },x);
+            }
+        };
+
+        public DecryptedEndPoint()
+        {
+            super(null,getEndPoint().getLocalAddress(), getEndPoint().getRemoteAddress());
+            setIdleTimeout(getEndPoint().getIdleTimeout());
+        }
+
+        @Override
+        protected FillInterest getFillInterest()
+        {
+            return super.getFillInterest();
+        }
+
+        @Override
+        public void setIdleTimeout(long idleTimeout)
+        {
+            super.setIdleTimeout(idleTimeout);
+            getEndPoint().setIdleTimeout(idleTimeout);
+        }
+
+        @Override
+        protected WriteFlusher getWriteFlusher()
+        {
+            return super.getWriteFlusher();
+        }
+
+        @Override
+        protected void onIncompleteFlush()
+        {
+            // This means that the decrypted endpoint write method was called and not
+            // all data could be wrapped. So either we need to write some encrypted data,
+            // OR if we are handshaking we need to read some encrypted data OR
+            // if neither then we should just try the flush again.
+            boolean flush = false;
+            synchronized (DecryptedEndPoint.this)
+            {
+                if (DEBUG)
+                    LOG.debug("onIncompleteFlush {}", getEndPoint());
+                // If we have pending output data,
+                if (BufferUtil.hasContent(_encryptedOutput))
+                {
+                    // write it
+                    _cannotAcceptMoreAppDataToFlush = true;
+                    getEndPoint().write(_writeCallback, _encryptedOutput);
+                }
+                // If we are handshaking and need to read,
+                else if (_sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP)
+                {
+                    // check if we are actually read blocked in order to write
+                    _flushRequiresFillToProgress = true;
+                    SslConnection.this.fillInterested();
+                }
+                else
+                {
+                    flush = true;
+                }
+            }
+            if (flush)
+            {
+                // If the output is closed,
+                if (isOutputShutdown())
+                {
+                    // don't bother writing, just notify of close
+                    getWriteFlusher().onClose();
+                }
+                // Else,
+                else
+                {
+                    // try to flush what is pending
+                    getWriteFlusher().completeWrite();
+                }
+            }
+        }
+
+        @Override
+        protected boolean needsFill() throws IOException
+        {
+            // This means that the decrypted data consumer has called the fillInterested
+            // method on the DecryptedEndPoint, so we have to work out if there is
+            // decrypted data to be filled or what callbacks to setup to be told when there
+            // might be more encrypted data available to attempt another call to fill
+
+            synchronized (DecryptedEndPoint.this)
+            {
+                // Do we already have some app data, then app can fill now so return true
+                if (BufferUtil.hasContent(_decryptedInput))
+                    return true;
+
+                // If we have no encrypted data to decrypt OR we have some, but it is not enough
+                if (BufferUtil.isEmpty(_encryptedInput) || _underFlown)
+                {
+                    // We are not ready to read data
+
+                    // Are we actually write blocked?
+                    if (_fillRequiresFlushToProgress)
+                    {
+                        // we must be blocked trying to write before we can read
+
+                        // Do we have data to write
+                        if (BufferUtil.hasContent(_encryptedOutput))
+                        {
+                            // write it
+                            _cannotAcceptMoreAppDataToFlush = true;
+                            getEndPoint().write(_writeCallback, _encryptedOutput);
+                        }
+                        else
+                        {
+                            // we have already written the net data
+                            // pretend we are readable so the wrap is done by next readable callback
+                            _fillRequiresFlushToProgress = false;
+                            return true;
+                        }
+                    }
+                    else
+                    {
+                        // Normal readable callback
+                        // Get called back on onfillable when then is more data to fill
+                        SslConnection.this.fillInterested();
+                    }
+
+                    return false;
+                }
+                else
+                {
+                    // We are ready to read data
+                    return true;
+                }
+            }
+        }
+
+        @Override
+        public void setConnection(Connection connection)
+        {
+            if (connection instanceof AbstractConnection)
+            {
+                AbstractConnection a = (AbstractConnection)connection;
+                if (a.getInputBufferSize()<_sslEngine.getSession().getApplicationBufferSize())
+                    a.setInputBufferSize(_sslEngine.getSession().getApplicationBufferSize());
+            }
+            super.setConnection(connection);
+        }
+
+        public SslConnection getSslConnection()
+        {
+            return SslConnection.this;
+        }
+
+        @Override
+        public synchronized int fill(ByteBuffer buffer) throws IOException
+        {
+            if (DEBUG)
+                LOG.debug("{} fill enter", SslConnection.this);
+            try
+            {
+                // Do we already have some decrypted data?
+                if (BufferUtil.hasContent(_decryptedInput))
+                    return BufferUtil.append(buffer,_decryptedInput);
+
+                // We will need a network buffer
+                if (_encryptedInput == null)
+                    _encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
+                else
+                    BufferUtil.compact(_encryptedInput);
+
+                // We also need an app buffer, but can use the passed buffer if it is big enough
+                ByteBuffer app_in;
+                if (BufferUtil.space(buffer) > _sslEngine.getSession().getApplicationBufferSize())
+                    app_in = buffer;
+                else if (_decryptedInput == null)
+                    app_in = _decryptedInput = _bufferPool.acquire(_sslEngine.getSession().getApplicationBufferSize(), _decryptedDirectBuffers);
+                else
+                    app_in = _decryptedInput;
+
+                // loop filling and unwrapping until we have something
+                while (true)
+                {
+                    // Let's try reading some encrypted data... even if we have some already.
+                    int net_filled = getEndPoint().fill(_encryptedInput);
+                    if (DEBUG)
+                        LOG.debug("{} filled {} encrypted bytes", SslConnection.this, net_filled);
+
+                    decryption: while (true)
+                    {
+                        // Let's unwrap even if we have no net data because in that
+                        // case we want to fall through to the handshake handling
+                        int pos = BufferUtil.flipToFill(app_in);
+                        SSLEngineResult unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
+                        BufferUtil.flipToFlush(app_in, pos);
+                        if (DEBUG)
+                            LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
+
+                        HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
+                        HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
+                        Status unwrapResultStatus = unwrapResult.getStatus();
+
+                        _underFlown = unwrapResultStatus == Status.BUFFER_UNDERFLOW;
+
+                        if (_underFlown)
+                        {
+                            if (net_filled < 0)
+                                closeInbound();
+                            if (net_filled <= 0)
+                                return net_filled;
+                        }
+
+                        switch (unwrapResultStatus)
+                        {
+                            case CLOSED:
+                            {
+                                switch (handshakeStatus)
+                                {
+                                    case NOT_HANDSHAKING:
+                                    {
+                                        // We were not handshaking, so just tell the app we are closed
+                                        return -1;
+                                    }
+                                    case NEED_TASK:
+                                    {
+                                        _sslEngine.getDelegatedTask().run();
+                                        continue;
+                                    }
+                                    case NEED_WRAP:
+                                    {
+                                        // We need to send some handshake data (probably the close handshake).
+                                        // We return -1 so that the application can drive the close by flushing
+                                        // or shutting down the output.
+                                        return -1;
+                                    }
+                                    default:
+                                    {
+                                        throw new IllegalStateException();
+                                    }
+                                }
+                            }
+                            case BUFFER_UNDERFLOW:
+                            case OK:
+                            {
+                                if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
+                                {
+                                    _handshaken = true;
+                                    if (DEBUG)
+                                        LOG.debug("{} {} handshake completed", SslConnection.this,
+                                                _sslEngine.getUseClientMode() ? "client-side" : "resumed session server-side");
+                                }
+
+                                // Check whether renegotiation is allowed
+                                if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
+                                {
+                                    if (DEBUG)
+                                        LOG.debug("{} renegotiation denied", SslConnection.this);
+                                    closeInbound();
+                                    return -1;
+                                }
+
+                                // If bytes were produced, don't bother with the handshake status;
+                                // pass the decrypted data to the application, which will perform
+                                // another call to fill() or flush().
+                                if (unwrapResult.bytesProduced() > 0)
+                                {
+                                    if (app_in == buffer)
+                                        return unwrapResult.bytesProduced();
+                                    return BufferUtil.append(buffer,_decryptedInput);
+                                }
+
+                                switch (handshakeStatus)
+                                {
+                                    case NOT_HANDSHAKING:
+                                    {
+                                        if (_underFlown)
+                                            break decryption;
+                                        continue;
+                                    }
+                                    case NEED_TASK:
+                                    {
+                                        _sslEngine.getDelegatedTask().run();
+                                        continue;
+                                    }
+                                    case NEED_WRAP:
+                                    {
+                                        // If we are called from flush()
+                                        // return to let it do the wrapping.
+                                        if (buffer == __FLUSH_CALLED_FILL)
+                                            return 0;
+
+                                        _fillRequiresFlushToProgress = true;
+                                        flush(__FILL_CALLED_FLUSH);
+                                        if (BufferUtil.isEmpty(_encryptedOutput))
+                                        {
+                                            // The flush wrote all the encrypted bytes so continue to fill
+                                            _fillRequiresFlushToProgress = false;
+                                            continue;
+                                        }
+                                        else
+                                        {
+                                            // The flush did not complete, return from fill()
+                                            // and let the write completion mechanism to kick in.
+                                            return 0;
+                                        }
+                                    }
+                                    case NEED_UNWRAP:
+                                    {
+                                        if (_underFlown)
+                                            break decryption;
+                                        continue;
+                                    }
+                                    default:
+                                    {
+                                        throw new IllegalStateException();
+                                    }
+                                }
+                            }
+                            default:
+                            {
+                                throw new IllegalStateException();
+                            }
+                        }
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                getEndPoint().close();
+                throw e;
+            }
+            finally
+            {
+                // If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
+                if (_flushRequiresFillToProgress)
+                {
+                    _flushRequiresFillToProgress = false;
+                    getExecutor().execute(_runCompletWrite);
+                }
+
+                if (_encryptedInput != null && !_encryptedInput.hasRemaining())
+                {
+                    _bufferPool.release(_encryptedInput);
+                    _encryptedInput = null;
+                }
+                if (_decryptedInput != null && !_decryptedInput.hasRemaining())
+                {
+                    _bufferPool.release(_decryptedInput);
+                    _decryptedInput = null;
+                }
+                if (DEBUG)
+                    LOG.debug("{} fill exit", SslConnection.this);
+            }
+        }
+
+        private void closeInbound()
+        {
+            try
+            {
+                _sslEngine.closeInbound();
+            }
+            catch (SSLException x)
+            {
+                LOG.ignore(x);
+            }
+        }
+
+        @Override
+        public synchronized boolean flush(ByteBuffer... appOuts) throws IOException
+        {
+            // The contract for flush does not require that all appOuts bytes are written
+            // or even that any appOut bytes are written!  If the connection is write block
+            // or busy handshaking, then zero bytes may be taken from appOuts and this method
+            // will return 0 (even if some handshake bytes were flushed and filled).
+            // it is the applications responsibility to call flush again - either in a busy loop
+            // or better yet by using EndPoint#write to do the flushing.
+
+            if (DEBUG)
+                LOG.debug("{} flush enter {}", SslConnection.this, Arrays.toString(appOuts));
+            int consumed=0;
+            try
+            {
+                if (_cannotAcceptMoreAppDataToFlush)
+                {
+                    if (_sslEngine.isOutboundDone())
+                        throw new EofException(new ClosedChannelException());
+                    return false;
+                }
+
+                // We will need a network buffer
+                if (_encryptedOutput == null)
+                    _encryptedOutput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
+
+                while (true)
+                {
+                    // We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer
+                    BufferUtil.compact(_encryptedOutput);
+                    int pos = BufferUtil.flipToFill(_encryptedOutput);
+                    SSLEngineResult wrapResult = _sslEngine.wrap(appOuts, _encryptedOutput);
+                    if (DEBUG)
+                        LOG.debug("{} wrap {}", SslConnection.this, wrapResult);
+                    BufferUtil.flipToFlush(_encryptedOutput, pos);
+                    if (wrapResult.bytesConsumed()>0)
+                        consumed+=wrapResult.bytesConsumed();
+
+                    boolean allConsumed=true;
+                    // clear empty buffers to prevent position creeping up the buffer
+                    for (ByteBuffer b : appOuts)
+                    {
+                        if (BufferUtil.isEmpty(b))
+                            BufferUtil.clear(b);
+                        else
+                            allConsumed=false;
+                    }
+
+                    Status wrapResultStatus = wrapResult.getStatus();
+
+                    // and deal with the results returned from the sslEngineWrap
+                    switch (wrapResultStatus)
+                    {
+                        case CLOSED:
+                            // The SSL engine has close, but there may be close handshake that needs to be written
+                            if (BufferUtil.hasContent(_encryptedOutput))
+                            {
+                                _cannotAcceptMoreAppDataToFlush = true;
+                                getEndPoint().flush(_encryptedOutput);
+                                getEndPoint().shutdownOutput();
+                                // If we failed to flush the close handshake then we will just pretend that
+                                // the write has progressed normally and let a subsequent call to flush
+                                // (or WriteFlusher#onIncompleteFlushed) to finish writing the close handshake.
+                                // The caller will find out about the close on a subsequent flush or fill.
+                                if (BufferUtil.hasContent(_encryptedOutput))
+                                    return false;
+                            }
+                            // otherwise we have written, and the caller will close the underlying connection
+                            else
+                            {
+                                getEndPoint().shutdownOutput();
+                            }
+                            return allConsumed;
+
+                        case BUFFER_UNDERFLOW:
+                            throw new IllegalStateException();
+
+                        default:
+                            if (DEBUG)
+                                LOG.debug("{} {} {}", this, wrapResultStatus, BufferUtil.toDetailString(_encryptedOutput));
+
+                            if (wrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED && !_handshaken)
+                            {
+                                _handshaken = true;
+                                if (DEBUG)
+                                    LOG.debug("{} {} handshake completed", SslConnection.this, "server-side");
+                            }
+
+                            HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
+
+                            // Check whether renegotiation is allowed
+                            if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
+                            {
+                                if (DEBUG)
+                                    LOG.debug("{} renegotiation denied", SslConnection.this);
+                                shutdownOutput();
+                                return allConsumed;
+                            }
+
+                            // if we have net bytes, let's try to flush them
+                            if (BufferUtil.hasContent(_encryptedOutput))
+                                getEndPoint().flush(_encryptedOutput);
+
+                            // But we also might have more to do for the handshaking state.
+                            switch (handshakeStatus)
+                            {
+                                case NOT_HANDSHAKING:
+                                    // Return with the number of bytes consumed (which may be 0)
+                                    return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
+
+                                case NEED_TASK:
+                                    // run the task and continue
+                                    _sslEngine.getDelegatedTask().run();
+                                    continue;
+
+                                case NEED_WRAP:
+                                    // Hey we just wrapped! Oh well who knows what the sslEngine is thinking, so continue and we will wrap again
+                                    continue;
+
+                                case NEED_UNWRAP:
+                                    // Ah we need to fill some data so we can write.
+                                    // So if we were not called from fill and the app is not reading anyway
+                                    if (appOuts[0]!=__FILL_CALLED_FLUSH && !getFillInterest().isInterested())
+                                    {
+                                        // Tell the onFillable method that there might be a write to complete
+                                        _flushRequiresFillToProgress = true;
+                                        fill(__FLUSH_CALLED_FILL);
+                                        // Check if after the fill() we need to wrap again
+                                        if (handshakeStatus == HandshakeStatus.NEED_WRAP)
+                                            continue;
+                                    }
+                                    return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
+
+                                case FINISHED:
+                                    throw new IllegalStateException();
+                            }
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                getEndPoint().close();
+                throw e;
+            }
+            finally
+            {
+                if (DEBUG)
+                    LOG.debug("{} flush exit, consumed {}", SslConnection.this, consumed);
+                releaseEncryptedOutputBuffer();
+            }
+        }
+
+        private void releaseEncryptedOutputBuffer()
+        {
+            if (!Thread.holdsLock(DecryptedEndPoint.this))
+                throw new IllegalStateException();
+            if (_encryptedOutput != null && !_encryptedOutput.hasRemaining())
+            {
+                _bufferPool.release(_encryptedOutput);
+                _encryptedOutput = null;
+            }
+        }
+
+        @Override
+        public void shutdownOutput()
+        {
+            boolean ishut = isInputShutdown();
+            boolean oshut = isOutputShutdown();
+            if (DEBUG)
+                LOG.debug("{} shutdownOutput: oshut={}, ishut={}", SslConnection.this, oshut, ishut);
+            if (ishut)
+            {
+                // Aggressively close, since inbound close alert has already been processed
+                // and the TLS specification allows to close the connection directly, which
+                // is what most other implementations expect: a FIN rather than a TLS close
+                // reply. If a TLS close reply is sent, most implementations send a RST.
+                getEndPoint().close();
+            }
+            else if (!oshut)
+            {
+                try
+                {
+                    _sslEngine.closeOutbound();
+                    flush(BufferUtil.EMPTY_BUFFER); // Send close handshake
+                    SslConnection.this.fillInterested(); // seek reply FIN or RST or close handshake
+                }
+                catch (Exception e)
+                {
+                    LOG.ignore(e);
+                    getEndPoint().close();
+                }
+            }
+        }
+
+        @Override
+        public boolean isOutputShutdown()
+        {
+            return _sslEngine.isOutboundDone() || getEndPoint().isOutputShutdown();
+        }
+
+        @Override
+        public void close()
+        {
+            super.close();
+            // First send the TLS Close Alert, then the FIN
+            shutdownOutput();
+            getEndPoint().close();
+        }
+
+        @Override
+        public boolean isOpen()
+        {
+            return getEndPoint().isOpen();
+        }
+
+        @Override
+        public Object getTransport()
+        {
+            return getEndPoint();
+        }
+
+        @Override
+        public boolean isInputShutdown()
+        {
+            return _sslEngine.isInboundDone();
+        }
+
+        @Override
+        public String toString()
+        {
+            return super.toString()+"->"+getEndPoint().toString();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ssl/package-info.java b/lib/jetty/org/eclipse/jetty/io/ssl/package-info.java
new file mode 100644 (file)
index 0000000..f8676c3
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty IO : Core SSL Support
+ */
+package org.eclipse.jetty.io.ssl;
+
diff --git a/lib/jetty/org/eclipse/jetty/security/AbstractUserAuthentication.java b/lib/jetty/org/eclipse/jetty/security/AbstractUserAuthentication.java
new file mode 100644 (file)
index 0000000..bc049fd
--- /dev/null
@@ -0,0 +1,98 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.Serializable;
+import java.util.Set;
+
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.UserIdentity.Scope;
+
+/**
+ * AbstractUserAuthentication
+ *
+ *
+ * Base class for representing an authenticated user.
+ */
+public abstract class AbstractUserAuthentication implements User, Serializable
+{
+    private static final long serialVersionUID = -6290411814232723403L;
+    protected String _method;
+    protected transient UserIdentity _userIdentity;
+    
+    
+    
+    public AbstractUserAuthentication(String method, UserIdentity userIdentity)
+    {
+        _method = method;
+        _userIdentity = userIdentity;
+    }
+    
+
+    @Override
+    public String getAuthMethod()
+    {
+        return _method;
+    }
+
+    @Override
+    public UserIdentity getUserIdentity()
+    {
+        return _userIdentity;
+    }
+
+    @Override
+    public boolean isUserInRole(Scope scope, String role)
+    {
+        String roleToTest = null;
+        if (scope!=null && scope.getRoleRefMap()!=null)
+            roleToTest=scope.getRoleRefMap().get(role);
+        if (roleToTest==null)
+            roleToTest=role;
+        //Servlet Spec 3.1 pg 125 if testing special role **
+        if ("**".equals(roleToTest.trim()))
+        {
+            //if ** is NOT a declared role name, the we return true 
+            //as the user is authenticated. If ** HAS been declared as a
+            //role name, then we have to check if the user has that role
+            if (!declaredRolesContains("**"))
+                return true;
+            else
+                return _userIdentity.isUserInRole(role, scope);
+        }
+      
+        return _userIdentity.isUserInRole(role, scope);
+    }
+
+    public boolean declaredRolesContains(String roleName)
+    {
+        SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+        if (security==null)
+            return false;
+        
+        if (security instanceof ConstraintAware)
+        {
+            Set<String> declaredRoles = ((ConstraintAware)security).getRoles();
+            return (declaredRoles != null) && declaredRoles.contains(roleName);
+        }
+        
+        return false;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/Authenticator.java b/lib/jetty/org/eclipse/jetty/security/Authenticator.java
new file mode 100644 (file)
index 0000000..e36a3b3
--- /dev/null
@@ -0,0 +1,138 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.Server;
+
+/**
+ * Authenticator Interface
+ * <p>
+ * An Authenticator is responsible for checking requests and sending
+ * response challenges in order to authenticate a request.
+ * Various types of {@link Authentication} are returned in order to
+ * signal the next step in authentication.
+ *
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public interface Authenticator
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Configure the Authenticator
+     * @param configuration
+     */
+    void setConfiguration(AuthConfiguration configuration);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The name of the authentication method
+     */
+    String getAuthMethod();
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Called prior to validateRequest. The authenticator can
+     * manipulate the request to update it with information that
+     * can be inspected prior to validateRequest being called.
+     * The primary purpose of this method is to satisfy the Servlet
+     * Spec 3.1 section 13.6.3 on handling Form authentication
+     * where the http method of the original request causing authentication
+     * is not the same as the http method resulting from the redirect
+     * after authentication.
+     * @param request
+     */
+    void prepareRequest(ServletRequest request);
+    
+
+    /* ------------------------------------------------------------ */
+    /** Validate a request
+     * @param request The request
+     * @param response The response
+     * @param mandatory True if authentication is mandatory.
+     * @return An Authentication.  If Authentication is successful, this will be a {@link org.eclipse.jetty.server.Authentication.User}. If a response has
+     * been sent by the Authenticator (which can be done for both successful and unsuccessful authentications), then the result will
+     * implement {@link org.eclipse.jetty.server.Authentication.ResponseSent}.  If Authentication is not manditory, then a
+     * {@link org.eclipse.jetty.server.Authentication.Deferred} may be returned.
+     *
+     * @throws ServerAuthException
+     */
+    Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param request
+     * @param response
+     * @param mandatory
+     * @param validatedUser
+     * @return true if response is secure
+     * @throws ServerAuthException
+     */
+    boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser) throws ServerAuthException;
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Authenticator Configuration
+     */
+    interface AuthConfiguration
+    {
+        String getAuthMethod();
+        String getRealmName();
+
+        /** Get a SecurityHandler init parameter
+         * @see SecurityHandler#getInitParameter(String)
+         * @param param parameter name
+         * @return Parameter value or null
+         */
+        String getInitParameter(String param);
+
+        /* ------------------------------------------------------------ */
+        /** Get a SecurityHandler init parameter names
+         * @see SecurityHandler#getInitParameterNames()
+         * @return Set of parameter names
+         */
+        Set<String> getInitParameterNames();
+
+        LoginService getLoginService();
+        IdentityService getIdentityService();
+        boolean isSessionRenewedOnAuthentication();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Authenticator Factory
+     */
+    interface Factory
+    {
+        Authenticator getAuthenticator(Server server, ServletContext context, AuthConfiguration configuration, IdentityService identityService, LoginService loginService);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/ConstraintAware.java b/lib/jetty/org/eclipse/jetty/security/ConstraintAware.java
new file mode 100644 (file)
index 0000000..5cf1348
--- /dev/null
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public interface ConstraintAware
+{
+    List<ConstraintMapping> getConstraintMappings();
+    Set<String> getRoles();
+    
+    /* ------------------------------------------------------------ */
+    /** Set Constraint Mappings and roles.
+     * Can only be called during initialization.
+     * @param constraintMappings
+     * @param roles
+     */
+    void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles);
+    
+    /* ------------------------------------------------------------ */
+    /** Add a Constraint Mapping.
+     * May be called for running webapplication as an annotated servlet is instantiated.
+     * @param mapping
+     */
+    void addConstraintMapping(ConstraintMapping mapping);
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Add a Role definition.
+     * May be called on running webapplication as an annotated servlet is instantiated.
+     * @param role
+     */
+    void addRole(String role);
+    
+    /**
+     * See Servlet Spec 31, sec 13.8.4, pg 145
+     * When true, requests with http methods not explicitly covered either by inclusion or omissions
+     * in constraints, will have access denied.
+     * @param deny
+     */
+    void setDenyUncoveredHttpMethods(boolean deny);
+    
+    boolean isDenyUncoveredHttpMethods();
+    
+    /**
+     * See Servlet Spec 31, sec 13.8.4, pg 145
+     * Container must check if there are urls with uncovered http methods
+     */
+    boolean checkPathsWithUncoveredHttpMethods();
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/ConstraintMapping.java b/lib/jetty/org/eclipse/jetty/security/ConstraintMapping.java
new file mode 100644 (file)
index 0000000..dd99c5b
--- /dev/null
@@ -0,0 +1,100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import org.eclipse.jetty.util.security.Constraint;
+
+public class ConstraintMapping
+{
+    String _method;
+    String[] _methodOmissions;
+
+    String _pathSpec;
+
+    Constraint _constraint;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the constraint.
+     */
+    public Constraint getConstraint()
+    {
+        return _constraint;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param constraint The constraint to set.
+     */
+    public void setConstraint(Constraint constraint)
+    {
+        this._constraint = constraint;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the method.
+     */
+    public String getMethod()
+    {
+        return _method;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param method The method to set.
+     */
+    public void setMethod(String method)
+    {
+        this._method = method;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the pathSpec.
+     */
+    public String getPathSpec()
+    {
+        return _pathSpec;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpec The pathSpec to set.
+     */
+    public void setPathSpec(String pathSpec)
+    {
+        this._pathSpec = pathSpec;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param omissions The http-method-omission
+     */
+    public void setMethodOmissions(String[] omissions)
+    {
+        _methodOmissions = omissions;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String[] getMethodOmissions()
+    {
+        return _methodOmissions;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/ConstraintSecurityHandler.java b/lib/jetty/org/eclipse/jetty/security/ConstraintSecurityHandler.java
new file mode 100644 (file)
index 0000000..201618d
--- /dev/null
@@ -0,0 +1,928 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import javax.servlet.HttpConstraintElement;
+import javax.servlet.HttpMethodConstraintElement;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
+import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+
+/* ------------------------------------------------------------ */
+/**
+ * ConstraintSecurityHandler
+ * 
+ * Handler to enforce SecurityConstraints. This implementation is servlet spec
+ * 3.1 compliant and pre-computes the constraint combinations for runtime
+ * efficiency.
+ *
+ */
+public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware
+{
+    private static final Logger LOG = Log.getLogger(SecurityHandler.class); //use same as SecurityHandler
+    
+    private static final String OMISSION_SUFFIX = ".omission";
+    private static final String ALL_METHODS = "*";
+    private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<>();
+    private final Set<String> _roles = new CopyOnWriteArraySet<>();
+    private final PathMap<Map<String, RoleInfo>> _constraintMap = new PathMap<>();
+    private boolean _denyUncoveredMethods = false;
+
+
+    /* ------------------------------------------------------------ */
+    public static Constraint createConstraint()
+    {
+        return new Constraint();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param constraint
+     */
+    public static Constraint createConstraint(Constraint constraint)
+    {
+        try
+        {
+            return (Constraint)constraint.clone();
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new IllegalStateException (e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a security constraint
+     * 
+     * @param name
+     * @param authenticate
+     * @param roles
+     * @param dataConstraint
+     */
+    public static Constraint createConstraint (String name, boolean authenticate, String[] roles, int dataConstraint)
+    {
+        Constraint constraint = createConstraint();
+        if (name != null)
+            constraint.setName(name);
+        constraint.setAuthenticate(authenticate);
+        constraint.setRoles(roles);
+        constraint.setDataConstraint(dataConstraint);
+        return constraint;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name
+     * @param element
+     */
+    public static Constraint createConstraint (String name, HttpConstraintElement element)
+    {
+        return createConstraint(name, element.getRolesAllowed(), element.getEmptyRoleSemantic(), element.getTransportGuarantee());     
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name
+     * @param rolesAllowed
+     * @param permitOrDeny
+     * @param transport
+     */
+    public static Constraint createConstraint (String name, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
+    {
+        Constraint constraint = createConstraint();
+        
+        if (rolesAllowed == null || rolesAllowed.length==0)
+        {           
+            if (permitOrDeny.equals(EmptyRoleSemantic.DENY))
+            {
+                //Equivalent to <auth-constraint> with no roles
+                constraint.setName(name+"-Deny");
+                constraint.setAuthenticate(true);
+            }
+            else
+            {
+                //Equivalent to no <auth-constraint>
+                constraint.setName(name+"-Permit");
+                constraint.setAuthenticate(false);
+            }
+        }
+        else
+        {
+            //Equivalent to <auth-constraint> with list of <security-role-name>s
+            constraint.setAuthenticate(true);
+            constraint.setRoles(rolesAllowed);
+            constraint.setName(name+"-RolesAllowed");           
+        } 
+
+        //Equivalent to //<user-data-constraint><transport-guarantee>CONFIDENTIAL</transport-guarantee></user-data-constraint>
+        constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE));
+        return constraint; 
+    }
+    
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpec
+     * @param constraintMappings
+     */
+    public static List<ConstraintMapping> getConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
+    {
+        if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
+            return Collections.emptyList();
+        
+        List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
+        for (ConstraintMapping mapping:constraintMappings)
+        {
+            if (pathSpec.equals(mapping.getPathSpec()))
+            {
+               mappings.add(mapping);
+            }
+        }
+        return mappings;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Take out of the constraint mappings those that match the 
+     * given path.
+     * 
+     * @param pathSpec
+     * @param constraintMappings a new list minus the matching constraints
+     */
+    public static List<ConstraintMapping> removeConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
+    {
+        if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
+            return Collections.emptyList();
+        
+        List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
+        for (ConstraintMapping mapping:constraintMappings)
+        {
+            //Remove the matching mappings by only copying in non-matching mappings
+            if (!pathSpec.equals(mapping.getPathSpec()))
+            {
+               mappings.add(mapping);
+            }
+        }
+        return mappings;
+    }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Generate Constraints and ContraintMappings for the given url pattern and ServletSecurityElement
+     * 
+     * @param name
+     * @param pathSpec
+     * @param securityElement
+     * @return
+     */
+    public static List<ConstraintMapping> createConstraintsWithMappingsForPath (String name, String pathSpec, ServletSecurityElement securityElement)
+    {
+        List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
+
+        //Create a constraint that will describe the default case (ie if not overridden by specific HttpMethodConstraints)
+        Constraint httpConstraint = null;
+        ConstraintMapping httpConstraintMapping = null;
+        
+        if (securityElement.getEmptyRoleSemantic() != EmptyRoleSemantic.PERMIT ||
+            securityElement.getRolesAllowed().length != 0 ||
+            securityElement.getTransportGuarantee() != TransportGuarantee.NONE)
+        {
+            httpConstraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
+
+            //Create a mapping for the pathSpec for the default case
+            httpConstraintMapping = new ConstraintMapping();
+            httpConstraintMapping.setPathSpec(pathSpec);
+            httpConstraintMapping.setConstraint(httpConstraint); 
+            mappings.add(httpConstraintMapping);
+        }
+        
+
+        //See Spec 13.4.1.2 p127
+        List<String> methodOmissions = new ArrayList<String>();
+        
+        //make constraint mappings for this url for each of the HttpMethodConstraintElements
+        Collection<HttpMethodConstraintElement> methodConstraintElements = securityElement.getHttpMethodConstraints();
+        if (methodConstraintElements != null)
+        {
+            for (HttpMethodConstraintElement methodConstraintElement:methodConstraintElements)
+            {
+                //Make a Constraint that captures the <auth-constraint> and <user-data-constraint> elements supplied for the HttpMethodConstraintElement
+                Constraint methodConstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraintElement);
+                ConstraintMapping mapping = new ConstraintMapping();
+                mapping.setConstraint(methodConstraint);
+                mapping.setPathSpec(pathSpec);
+                if (methodConstraintElement.getMethodName() != null)
+                {
+                    mapping.setMethod(methodConstraintElement.getMethodName());
+                    //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
+                    methodOmissions.add(methodConstraintElement.getMethodName());
+                }
+                mappings.add(mapping);
+            }
+        }
+        //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
+        //UNLESS the default constraint contains all default values. In that case, we won't add it. See Servlet Spec 3.1 pg 129
+        if (methodOmissions.size() > 0  && httpConstraintMapping != null)
+            httpConstraintMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
+     
+        return mappings;
+    }
+    
+    
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the constraintMappings.
+     */
+    @Override
+    public List<ConstraintMapping> getConstraintMappings()
+    {
+        return _constraintMappings;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Set<String> getRoles()
+    {
+        return _roles;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Process the constraints following the combining rules in Servlet 3.0 EA
+     * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
+     *
+     * @param constraintMappings
+     *            The constraintMappings to set, from which the set of known roles
+     *            is determined.
+     */
+    public void setConstraintMappings(List<ConstraintMapping> constraintMappings)
+    {
+        setConstraintMappings(constraintMappings,null);
+    }
+
+    /**
+     * Process the constraints following the combining rules in Servlet 3.0 EA
+     * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
+     *
+     * @param constraintMappings
+     *            The constraintMappings to set as array, from which the set of known roles
+     *            is determined.  Needed to retain API compatibility for 7.x
+     */
+    public void setConstraintMappings( ConstraintMapping[] constraintMappings )
+    {
+        setConstraintMappings( Arrays.asList(constraintMappings), null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Process the constraints following the combining rules in Servlet 3.0 EA
+     * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
+     *
+     * @param constraintMappings
+     *            The constraintMappings to set.
+     * @param roles The known roles (or null to determine them from the mappings)
+     */
+    @Override
+    public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles)
+    {
+        _constraintMappings.clear();
+        _constraintMappings.addAll(constraintMappings);
+
+        if (roles==null)
+        {
+            roles = new HashSet<>();
+            for (ConstraintMapping cm : constraintMappings)
+            {
+                String[] cmr = cm.getConstraint().getRoles();
+                if (cmr!=null)
+                {
+                    for (String r : cmr)
+                        if (!ALL_METHODS.equals(r))
+                            roles.add(r);
+                }
+            }
+        }
+        setRoles(roles);
+        
+        if (isStarted())
+        {
+            for (ConstraintMapping mapping : _constraintMappings)
+            {
+                processConstraintMapping(mapping);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the known roles.
+     * This may be overridden by a subsequent call to {@link #setConstraintMappings(ConstraintMapping[])} or
+     * {@link #setConstraintMappings(List, Set)}.
+     * @param roles The known roles (or null to determine them from the mappings)
+     */
+    public void setRoles(Set<String> roles)
+    {
+        _roles.clear();
+        _roles.addAll(roles);
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.ConstraintAware#addConstraintMapping(org.eclipse.jetty.security.ConstraintMapping)
+     */
+    @Override
+    public void addConstraintMapping(ConstraintMapping mapping)
+    {
+        _constraintMappings.add(mapping);
+        if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null)
+        {
+            //allow for lazy role naming: if a role is named in a security constraint, try and
+            //add it to the list of declared roles (ie as if it was declared with a security-role
+            for (String role :  mapping.getConstraint().getRoles())
+            {
+                if ("*".equals(role) || "**".equals(role))
+                    continue;
+                addRole(role);
+            }
+        }
+
+        if (isStarted())
+        {
+            processConstraintMapping(mapping);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.ConstraintAware#addRole(java.lang.String)
+     */
+    @Override
+    public void addRole(String role)
+    {
+        //add to list of declared roles
+        boolean modified = _roles.add(role);
+        if (isStarted() && modified)
+        {
+            // Add the new role to currently defined any role role infos
+            for (Map<String,RoleInfo> map : _constraintMap.values())
+            {
+                for (RoleInfo info : map.values())
+                {
+                    if (info.isAnyRole())
+                        info.addRole(role);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.SecurityHandler#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        _constraintMap.clear();
+        if (_constraintMappings!=null)
+        {
+            for (ConstraintMapping mapping : _constraintMappings)
+            {
+                processConstraintMapping(mapping);
+            }
+        }
+        
+        //Servlet Spec 3.1 pg 147 sec 13.8.4.2 log paths for which there are uncovered http methods
+        checkPathsWithUncoveredHttpMethods();        
+       
+        super.doStart();
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        _constraintMap.clear();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Create and combine the constraint with the existing processed
+     * constraints.
+     * 
+     * @param mapping
+     */
+    protected void processConstraintMapping(ConstraintMapping mapping)
+    {
+        Map<String, RoleInfo> mappings = _constraintMap.get(mapping.getPathSpec());
+        if (mappings == null)
+        {
+            mappings = new HashMap<String,RoleInfo>();
+            _constraintMap.put(mapping.getPathSpec(),mappings);
+        }
+        RoleInfo allMethodsRoleInfo = mappings.get(ALL_METHODS);
+        if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
+            return;
+
+        if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0)
+        {
+            processConstraintMappingWithMethodOmissions(mapping, mappings);
+            return;
+        }
+
+        String httpMethod = mapping.getMethod();
+        if (httpMethod==null)
+            httpMethod=ALL_METHODS;
+        RoleInfo roleInfo = mappings.get(httpMethod);
+        if (roleInfo == null)
+        {
+            roleInfo = new RoleInfo();
+            mappings.put(httpMethod,roleInfo);
+            if (allMethodsRoleInfo != null)
+            {
+                roleInfo.combine(allMethodsRoleInfo);
+            }
+        }
+        if (roleInfo.isForbidden())
+            return;
+
+        //add in info from the constraint
+        configureRoleInfo(roleInfo, mapping);
+        
+        if (roleInfo.isForbidden())
+        {
+            if (httpMethod.equals(ALL_METHODS))
+            {
+                mappings.clear();
+                mappings.put(ALL_METHODS,roleInfo);
+            }
+        }
+        else
+        {
+            //combine with any entry that covers all methods
+            if (httpMethod == null)
+            {
+                for (Map.Entry<String, RoleInfo> entry : mappings.entrySet())
+                {
+                    if (entry.getKey() != null)
+                    {
+                        RoleInfo specific = entry.getValue();
+                        specific.combine(roleInfo);
+                    }
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Constraints that name method omissions are dealt with differently.
+     * We create an entry in the mappings with key "&lt;method&gt;.omission". This entry
+     * is only ever combined with other omissions for the same method to produce a
+     * consolidated RoleInfo. Then, when we wish to find the relevant constraints for
+     *  a given Request (in prepareConstraintInfo()), we consult 3 types of entries in 
+     * the mappings: an entry that names the method of the Request specifically, an
+     * entry that names constraints that apply to all methods, entries of the form
+     * &lt;method&gt;.omission, where the method of the Request is not named in the omission.
+     * @param mapping
+     * @param mappings
+     */
+    protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map<String, RoleInfo> mappings)
+    {
+        String[] omissions = mapping.getMethodOmissions();
+        StringBuilder sb = new StringBuilder();
+        for (int i=0; i<omissions.length; i++)
+        {
+            if (i > 0)
+                sb.append(".");
+            sb.append(omissions[i]);
+        }
+        sb.append(OMISSION_SUFFIX);
+        RoleInfo ri = new RoleInfo();
+        mappings.put(sb.toString(), ri);
+        configureRoleInfo(ri, mapping);
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Initialize or update the RoleInfo from the constraint
+     * @param ri
+     * @param mapping
+     */
+    protected void configureRoleInfo (RoleInfo ri, ConstraintMapping mapping)
+    { 
+        Constraint constraint = mapping.getConstraint();
+        boolean forbidden = constraint.isForbidden();
+        ri.setForbidden(forbidden);
+        
+        //set up the data constraint (NOTE: must be done after setForbidden, as it nulls out the data constraint
+        //which we need in order to do combining of omissions in prepareConstraintInfo
+        UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint());
+        ri.setUserDataConstraint(userDataConstraint);
+
+        //if forbidden, no point setting up roles
+        if (!ri.isForbidden())
+        {
+            //add in the roles
+            boolean checked = mapping.getConstraint().getAuthenticate();
+            ri.setChecked(checked);
+
+            if (ri.isChecked())
+            {
+                if (mapping.getConstraint().isAnyRole())
+                {
+                    // * means matches any defined role
+                    for (String role : _roles)
+                        ri.addRole(role);
+                    ri.setAnyRole(true);
+                }
+                else if (mapping.getConstraint().isAnyAuth())
+                {
+                    //being authenticated is sufficient, not necessary to check roles
+                    ri.setAnyAuth(true);
+                }
+                else
+                {   
+                    //user must be in one of the named roles
+                    String[] newRoles = mapping.getConstraint().getRoles();
+                     for (String role : newRoles)
+                     {
+                         //check role has been defined
+                         if (!_roles.contains(role))
+                             throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
+                        ri.addRole(role);
+                     }
+                 }
+             }
+         }
+     }
+
+   
+    /* ------------------------------------------------------------ */
+    /** 
+     * Find constraints that apply to the given path.
+     * In order to do this, we consult 3 different types of information stored in the mappings for each path - each mapping
+     * represents a merged set of user data constraints, roles etc -:
+     * <ol>
+     * <li>A mapping of an exact method name </li>
+     * <li>A mapping with key * that matches every method name</li>
+     * <li>Mappings with keys of the form "&lt;method&gt;.&lt;method&gt;.&lt;method&gt;.omission" that indicates it will match every method name EXCEPT those given</li>
+     * </ol>
+     * 
+     * @see org.eclipse.jetty.security.SecurityHandler#prepareConstraintInfo(java.lang.String, org.eclipse.jetty.server.Request)
+     */
+    @Override
+    protected RoleInfo prepareConstraintInfo(String pathInContext, Request request)
+    {
+        Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.match(pathInContext);
+
+        if (mappings != null)
+        {
+            String httpMethod = request.getMethod();
+            RoleInfo roleInfo = mappings.get(httpMethod);
+            if (roleInfo == null)
+            {
+                //No specific http-method names matched
+                List<RoleInfo> applicableConstraints = new ArrayList<RoleInfo>();
+
+                //Get info for constraint that matches all methods if it exists
+                RoleInfo all = mappings.get(ALL_METHODS);
+                if (all != null)
+                    applicableConstraints.add(all);
+          
+                
+                //Get info for constraints that name method omissions where target method name is not omitted
+                //(ie matches because target method is not omitted, hence considered covered by the constraint)
+                for (Entry<String, RoleInfo> entry: mappings.entrySet())
+                {
+                    if (entry.getKey() != null && entry.getKey().endsWith(OMISSION_SUFFIX) && ! entry.getKey().contains(httpMethod))
+                        applicableConstraints.add(entry.getValue());
+                }
+                
+                if (applicableConstraints.size() == 0 && isDenyUncoveredHttpMethods())
+                {
+                    roleInfo = new RoleInfo();
+                    roleInfo.setForbidden(true);
+                }
+                else if (applicableConstraints.size() == 1)
+                    roleInfo = applicableConstraints.get(0);
+                else
+                {
+                    roleInfo = new RoleInfo();
+                    roleInfo.setUserDataConstraint(UserDataConstraint.None);
+                    
+                    for (RoleInfo r:applicableConstraints)
+                        roleInfo.combine(r);
+                }
+
+            }
+           
+            return roleInfo;
+        }
+
+        return null;
+    }
+
+    @Override
+    protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo roleInfo) throws IOException
+    {
+        if (roleInfo == null)
+            return true;
+
+        if (roleInfo.isForbidden())
+            return false;
+
+        UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint();
+        if (dataConstraint == null || dataConstraint == UserDataConstraint.None)
+            return true;
+
+        HttpConfiguration httpConfig = HttpChannel.getCurrentHttpChannel().getHttpConfiguration();
+
+        if (dataConstraint == UserDataConstraint.Confidential || dataConstraint == UserDataConstraint.Integral)
+        {
+            if (request.isSecure())
+                return true;
+
+            if (httpConfig.getSecurePort() > 0)
+            {
+                String scheme = httpConfig.getSecureScheme();
+                int port = httpConfig.getSecurePort();
+                String url = ("https".equalsIgnoreCase(scheme) && port==443)
+                    ? "https://"+request.getServerName()+request.getRequestURI()
+                    : scheme + "://" + request.getServerName() + ":" + port + request.getRequestURI();                    
+                if (request.getQueryString() != null)
+                    url += "?" + request.getQueryString();
+                response.setContentLength(0);
+                response.sendRedirect(url);
+            }
+            else
+                response.sendError(HttpStatus.FORBIDDEN_403,"!Secure");
+
+            request.setHandled(true);
+            return false;
+        }
+        else
+        {
+            throw new IllegalArgumentException("Invalid dataConstraint value: " + dataConstraint);
+        }
+
+    }
+
+    @Override
+    protected boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo)
+    {
+        return constraintInfo != null && ((RoleInfo)constraintInfo).isChecked();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.security.SecurityHandler#checkWebResourcePermissions(java.lang.String, org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object, org.eclipse.jetty.server.UserIdentity)
+     */
+    @Override
+    protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity)
+            throws IOException
+    {
+        if (constraintInfo == null)
+        {
+            return true;
+        }
+        RoleInfo roleInfo = (RoleInfo)constraintInfo;
+
+        if (!roleInfo.isChecked())
+        {
+            return true;
+        }
+
+        //handle ** role constraint
+        if (roleInfo.isAnyAuth() &&  request.getUserPrincipal() != null)
+        {
+            return true;
+        }
+        
+        //check if user is any of the allowed roles
+        boolean isUserInRole = false;
+        for (String role : roleInfo.getRoles())
+        {
+            if (userIdentity.isUserInRole(role, null))
+            {
+                isUserInRole = true;
+                break;
+            }
+        }
+        
+        //handle * role constraint
+        if (roleInfo.isAnyRole() && request.getUserPrincipal() != null && isUserInRole)
+        {
+            return true;
+        }
+
+        //normal role check
+        if (isUserInRole)
+        {
+            return true;
+        }
+       
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out,String indent) throws IOException
+    {
+        // TODO these should all be beans
+        dumpBeans(out,indent,
+                Collections.singleton(getLoginService()),
+                Collections.singleton(getIdentityService()),
+                Collections.singleton(getAuthenticator()),
+                Collections.singleton(_roles),
+                _constraintMap.entrySet());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.security.ConstraintAware#setDenyUncoveredHttpMethods(boolean)
+     */
+    @Override
+    public void setDenyUncoveredHttpMethods(boolean deny)
+    {
+        _denyUncoveredMethods = deny;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isDenyUncoveredHttpMethods()
+    {
+        return _denyUncoveredMethods;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Servlet spec 3.1 pg. 147.
+     */
+    @Override
+    public boolean checkPathsWithUncoveredHttpMethods()
+    {
+        Set<String> paths = getPathsWithUncoveredHttpMethods();
+        if (paths != null && !paths.isEmpty())
+        {
+            for (String p:paths)
+                LOG.warn("{} has uncovered http methods for path: {}",ContextHandler.getCurrentContext(), p);
+            if (LOG.isDebugEnabled())
+                LOG.debug(new Throwable());
+            return true;
+        }
+        return false; 
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Servlet spec 3.1 pg. 147.
+     * The container must check all the combined security constraint
+     * information and log any methods that are not protected and the
+     * urls at which they are not protected
+     * 
+     * @return list of paths for which there are uncovered methods
+     */
+    public Set<String> getPathsWithUncoveredHttpMethods ()
+    {
+        //if automatically denying uncovered methods, there are no uncovered methods
+        if (_denyUncoveredMethods)
+            return Collections.emptySet();
+        
+        Set<String> uncoveredPaths = new HashSet<String>();
+        
+        for (String path:_constraintMap.keySet())
+        {
+            Map<String, RoleInfo> methodMappings = _constraintMap.get(path);
+            //Each key is either:
+            // : an exact method name
+            // : * which means that the constraint applies to every method
+            // : a name of the form <method>.<method>.<method>.omission, which means it applies to every method EXCEPT those named
+            if (methodMappings.get(ALL_METHODS) != null)
+                continue; //can't be any uncovered methods for this url path
+          
+            boolean hasOmissions = omissionsExist(path, methodMappings);
+            
+            for (String method:methodMappings.keySet())
+            {
+                if (method.endsWith(OMISSION_SUFFIX))
+                {
+                    Set<String> omittedMethods = getOmittedMethods(method);
+                    for (String m:omittedMethods)
+                    {
+                        if (!methodMappings.containsKey(m))
+                            uncoveredPaths.add(path);
+                    }
+                }
+                else
+                {
+                    //an exact method name
+                    if (!hasOmissions)
+                        //a http-method does not have http-method-omission to cover the other method names
+                        uncoveredPaths.add(path);
+                }
+                
+            }
+        }
+        return uncoveredPaths;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Check if any http method omissions exist in the list of method
+     * to auth info mappings.
+     * 
+     * @param path
+     * @param methodMappings
+     * @return
+     */
+    protected boolean omissionsExist (String path, Map<String, RoleInfo> methodMappings)
+    {
+        if (methodMappings == null)
+            return false;
+        boolean hasOmissions = false;
+        for (String m:methodMappings.keySet())
+        {
+            if (m.endsWith(OMISSION_SUFFIX))
+                hasOmissions = true;
+        }
+        return hasOmissions;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Given a string of the form &lt;method&gt;.&lt;method&gt;.omission
+     * split out the individual method names.
+     * 
+     * @param omission
+     * @return
+     */
+    protected Set<String> getOmittedMethods (String omission)
+    {
+        if (omission == null || !omission.endsWith(OMISSION_SUFFIX))
+            return Collections.emptySet();
+        
+        String[] strings = omission.split("\\.");
+        Set<String> methods = new HashSet<String>();
+        for (int i=0;i<strings.length-1;i++)
+            methods.add(strings[i]);
+        return methods;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/CrossContextPsuedoSession.java b/lib/jetty/org/eclipse/jetty/security/CrossContextPsuedoSession.java
new file mode 100644 (file)
index 0000000..e2de9f7
--- /dev/null
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public interface CrossContextPsuedoSession<T>
+{
+
+    T fetch(HttpServletRequest request);
+
+    void store(T data, HttpServletResponse response);
+
+    void clear(HttpServletRequest request);
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java b/lib/jetty/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java
new file mode 100644 (file)
index 0000000..534a6d4
--- /dev/null
@@ -0,0 +1,96 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import javax.servlet.ServletContext;
+
+import org.eclipse.jetty.security.Authenticator.AuthConfiguration;
+import org.eclipse.jetty.security.authentication.BasicAuthenticator;
+import org.eclipse.jetty.security.authentication.ClientCertAuthenticator;
+import org.eclipse.jetty.security.authentication.DigestAuthenticator;
+import org.eclipse.jetty.security.authentication.FormAuthenticator;
+import org.eclipse.jetty.security.authentication.SpnegoAuthenticator;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.security.Constraint;
+
+/* ------------------------------------------------------------ */
+/**
+ * The Default Authenticator Factory.
+ * Uses the {@link AuthConfiguration#getAuthMethod()} to select an {@link Authenticator} from: <ul>
+ * <li>{@link org.eclipse.jetty.security.authentication.BasicAuthenticator}</li>
+ * <li>{@link org.eclipse.jetty.security.authentication.DigestAuthenticator}</li>
+ * <li>{@link org.eclipse.jetty.security.authentication.FormAuthenticator}</li>
+ * <li>{@link org.eclipse.jetty.security.authentication.ClientCertAuthenticator}</li>
+ * </ul>
+ * All authenticators derived from {@link org.eclipse.jetty.security.authentication.LoginAuthenticator} are 
+ * wrapped with a {@link org.eclipse.jetty.security.authentication.DeferredAuthentication}
+ * instance, which is used if authentication is not mandatory.
+ * 
+ * The Authentications from the {@link org.eclipse.jetty.security.authentication.FormAuthenticator} are always wrapped in a 
+ * {@link org.eclipse.jetty.security.authentication.SessionAuthentication}
+ * <p>
+ * If a {@link LoginService} has not been set on this factory, then
+ * the service is selected by searching the {@link Server#getBeans(Class)} results for
+ * a service that matches the realm name, else the first LoginService found is used.
+ *
+ */
+public class DefaultAuthenticatorFactory implements Authenticator.Factory
+{
+    LoginService _loginService;
+    
+    public Authenticator getAuthenticator(Server server, ServletContext context, AuthConfiguration configuration, IdentityService identityService, LoginService loginService)
+    {
+        String auth=configuration.getAuthMethod();
+        Authenticator authenticator=null;
+        
+        if (auth==null || Constraint.__BASIC_AUTH.equalsIgnoreCase(auth))
+            authenticator=new BasicAuthenticator();
+        else if (Constraint.__DIGEST_AUTH.equalsIgnoreCase(auth))
+            authenticator=new DigestAuthenticator();
+        else if (Constraint.__FORM_AUTH.equalsIgnoreCase(auth))
+            authenticator=new FormAuthenticator();
+        else if ( Constraint.__SPNEGO_AUTH.equalsIgnoreCase(auth) )
+            authenticator = new SpnegoAuthenticator();
+        else if ( Constraint.__NEGOTIATE_AUTH.equalsIgnoreCase(auth) ) // see Bug #377076
+            authenticator = new SpnegoAuthenticator(Constraint.__NEGOTIATE_AUTH);
+        if (Constraint.__CERT_AUTH.equalsIgnoreCase(auth)||Constraint.__CERT_AUTH2.equalsIgnoreCase(auth))
+            authenticator=new ClientCertAuthenticator();
+        
+        return authenticator;
+    }
+   
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the loginService
+     */
+    public LoginService getLoginService()
+    {
+        return _loginService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param loginService the loginService to set
+     */
+    public void setLoginService(LoginService loginService)
+    {
+        _loginService = loginService;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/DefaultIdentityService.java b/lib/jetty/org/eclipse/jetty/security/DefaultIdentityService.java
new file mode 100644 (file)
index 0000000..c8ae0c2
--- /dev/null
@@ -0,0 +1,90 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Default Identity Service implementation.
+ * This service handles only role reference maps passed in an
+ * associated {@link org.eclipse.jetty.server.UserIdentity.Scope}.  If there are roles
+ * refs present, then associate will wrap the UserIdentity with one
+ * that uses the role references in the
+ * {@link org.eclipse.jetty.server.UserIdentity#isUserInRole(String, org.eclipse.jetty.server.UserIdentity.Scope)}
+ * implementation. All other operations are effectively noops.
+ *
+ */
+public class DefaultIdentityService implements IdentityService
+{
+    /* ------------------------------------------------------------ */
+    public DefaultIdentityService()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * If there are roles refs present in the scope, then wrap the UserIdentity
+     * with one that uses the role references in the {@link UserIdentity#isUserInRole(String, org.eclipse.jetty.server.UserIdentity.Scope)}
+     */
+    public Object associate(UserIdentity user)
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void disassociate(Object previous)
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public Object setRunAs(UserIdentity user, RunAsToken token)
+    {
+        return token;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void unsetRunAs(Object lastToken)
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public RunAsToken newRunAsToken(String runAsName)
+    {
+        return new RoleRunAsToken(runAsName);
+    }
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity getSystemUserIdentity()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity newUserIdentity(final Subject subject, final Principal userPrincipal, final String[] roles)
+    {
+        return new DefaultUserIdentity(subject,userPrincipal,roles);
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/DefaultUserIdentity.java b/lib/jetty/org/eclipse/jetty/security/DefaultUserIdentity.java
new file mode 100644 (file)
index 0000000..65e083d
--- /dev/null
@@ -0,0 +1,81 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * The default implementation of UserIdentity.
+ *
+ */
+public class DefaultUserIdentity implements UserIdentity
+{
+    private final Subject _subject;
+    private final Principal _userPrincipal;
+    private final String[] _roles;
+
+    public DefaultUserIdentity(Subject subject, Principal userPrincipal, String[] roles)
+    {
+        _subject=subject;
+        _userPrincipal=userPrincipal;
+        _roles=roles;
+    }
+
+    public Subject getSubject()
+    {
+        return _subject;
+    }
+
+    public Principal getUserPrincipal()
+    {
+        return _userPrincipal;
+    }
+
+    public boolean isUserInRole(String role, Scope scope)
+    {
+        //Servlet Spec 3.1, pg 125
+        if ("*".equals(role))
+            return false;
+        
+        String roleToTest = null;
+        if (scope!=null && scope.getRoleRefMap()!=null)
+            roleToTest=scope.getRoleRefMap().get(role);
+
+        //Servlet Spec 3.1, pg 125
+        if (roleToTest == null)
+            roleToTest = role;
+       
+        for (String r :_roles)
+            if (r.equals(roleToTest))
+                return true;
+        return false;
+    }
+
+    @Override
+    public String toString()
+    {
+        return DefaultUserIdentity.class.getSimpleName()+"('"+_userPrincipal+"')";
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java b/lib/jetty/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java
new file mode 100644 (file)
index 0000000..8499a60
--- /dev/null
@@ -0,0 +1,99 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @version $Rev: 4660 $ $Date: 2009-02-25 17:29:53 +0100 (Wed, 25 Feb 2009) $
+ */
+public class HashCrossContextPsuedoSession<T> implements CrossContextPsuedoSession<T>
+{
+    private final String _cookieName;
+
+    private final String _cookiePath;
+
+    private final Random _random = new SecureRandom();
+
+    private final Map<String, T> _data = new HashMap<String, T>();
+
+    public HashCrossContextPsuedoSession(String cookieName, String cookiePath)
+    {
+        this._cookieName = cookieName;
+        this._cookiePath = cookiePath == null ? "/" : cookiePath;
+    }
+
+    public T fetch(HttpServletRequest request)
+    {
+        Cookie[] cookies = request.getCookies();
+        if (cookies == null)
+            return null;
+        
+        for (Cookie cookie : cookies)
+        {
+            if (_cookieName.equals(cookie.getName()))
+            {
+                String key = cookie.getValue();
+                return _data.get(key);
+            }
+        }
+        return null;
+    }
+
+    public void store(T datum, HttpServletResponse response)
+    {
+        String key;
+
+        synchronized (_data)
+        {
+            // Create new ID
+            while (true)
+            {
+                key = Long.toString(Math.abs(_random.nextLong()), 30 + (int) (System.currentTimeMillis() % 7));
+                if (!_data.containsKey(key)) break;
+            }
+
+            _data.put(key, datum);
+        }
+
+        Cookie cookie = new Cookie(_cookieName, key);
+        cookie.setPath(_cookiePath);
+        response.addCookie(cookie);
+    }
+
+    public void clear(HttpServletRequest request)
+    {
+        for (Cookie cookie : request.getCookies())
+        {
+            if (_cookieName.equals(cookie.getName()))
+            {
+                String key = cookie.getValue();
+                _data.remove(key);
+                break;
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/HashLoginService.java b/lib/jetty/org/eclipse/jetty/security/HashLoginService.java
new file mode 100644 (file)
index 0000000..335aabd
--- /dev/null
@@ -0,0 +1,184 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.security.PropertyUserStore.UserListener;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Credential;
+
+/* ------------------------------------------------------------ */
+/**
+ * Properties User Realm.
+ * 
+ * An implementation of UserRealm that stores users and roles in-memory in HashMaps.
+ * <P>
+ * Typically these maps are populated by calling the load() method or passing a properties resource to the constructor. The format of the properties file is:
+ * 
+ * <PRE>
+ *  username: password [,rolename ...]
+ * </PRE>
+ * 
+ * Passwords may be clear text, obfuscated or checksummed. The class com.eclipse.Util.Password should be used to generate obfuscated passwords or password
+ * checksums.
+ * 
+ * If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:.
+ */
+public class HashLoginService extends MappedLoginService implements UserListener
+{
+    private static final Logger LOG = Log.getLogger(HashLoginService.class);
+
+    private PropertyUserStore _propertyUserStore;
+    private String _config;
+    private Resource _configResource;
+    private Scanner _scanner;
+    private int _refreshInterval = 0;// default is not to reload
+
+    /* ------------------------------------------------------------ */
+    public HashLoginService()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public HashLoginService(String name)
+    {
+        setName(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    public HashLoginService(String name, String config)
+    {
+        setName(name);
+        setConfig(config);
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getConfig()
+    {
+        return _config;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void getConfig(String config)
+    {
+        _config = config;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Resource getConfigResource()
+    {
+        return _configResource;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Load realm users from properties file. The property file maps usernames to password specs followed by an optional comma separated list of role names.
+     * 
+     * @param config
+     *            Filename or url of user properties file.
+     */
+    public void setConfig(String config)
+    {
+        _config = config;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRefreshInterval(int msec)
+    {
+        _refreshInterval = msec;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getRefreshInterval()
+    {
+        return _refreshInterval;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected UserIdentity loadUser(String username)
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void loadUsers() throws IOException
+    {
+        // TODO: Consider refactoring MappedLoginService to not have to override with unused methods
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        
+        if (_propertyUserStore == null)
+        {
+            if(LOG.isDebugEnabled())
+                LOG.debug("doStart: Starting new PropertyUserStore. PropertiesFile: " + _config + " refreshInterval: " + _refreshInterval);
+            
+            _propertyUserStore = new PropertyUserStore();
+            _propertyUserStore.setRefreshInterval(_refreshInterval);
+            _propertyUserStore.setConfig(_config);
+            _propertyUserStore.registerUserListener(this);
+            _propertyUserStore.start();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        if (_scanner != null)
+            _scanner.stop();
+        _scanner = null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void update(String userName, Credential credential, String[] roleArray)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("update: " + userName + " Roles: " + roleArray.length);
+        putUser(userName,credential,roleArray);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void remove(String userName)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("remove: " + userName);
+        removeUser(userName);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/IdentityService.java b/lib/jetty/org/eclipse/jetty/security/IdentityService.java
new file mode 100644 (file)
index 0000000..99094c1
--- /dev/null
@@ -0,0 +1,95 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+
+/* ------------------------------------------------------------ */
+/**
+ * Associates UserIdentities from with threads and UserIdentity.Contexts.
+ *
+ */
+public interface IdentityService
+{
+    final static String[] NO_ROLES = new String[]{};
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Associate a user identity with the current thread.
+     * This is called with as a thread enters the
+     * {@link SecurityHandler#handle(String, Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
+     * method and then again with a null argument as that call exits.
+     * @param user The current user or null for no user to associated.
+     * @return an object representing the previous associated state
+     */
+    Object associate(UserIdentity user);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Disassociate the user identity from the current thread
+     * and restore previous identity.
+     * @param previous The opaque object returned from a call to {@link IdentityService#associate(UserIdentity)}
+     */
+    void disassociate(Object previous);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Associate a runas Token with the current user and thread.
+     * @param user The UserIdentity
+     * @param token The runAsToken to associate.
+     * @return The previous runAsToken or null.
+     */
+    Object setRunAs(UserIdentity user, RunAsToken token);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Disassociate the current runAsToken from the thread
+     * and reassociate the previous token.
+     * @param token RUNAS returned from previous associateRunAs call
+     */
+    void unsetRunAs(Object token);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new UserIdentity for use with this identity service.
+     * The UserIdentity should be immutable and able to be cached.
+     *
+     * @param subject Subject to include in UserIdentity
+     * @param userPrincipal Principal to include in UserIdentity.  This will be returned from getUserPrincipal calls
+     * @param roles set of roles to include in UserIdentity.
+     * @return A new immutable UserIdententity
+     */
+    UserIdentity newUserIdentity(Subject subject, Principal userPrincipal, String[] roles);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new RunAsToken from a runAsName (normally a role).
+     * @param runAsName Normally a role name
+     * @return A new immutable RunAsToken
+     */
+    RunAsToken newRunAsToken(String runAsName);
+
+    /* ------------------------------------------------------------ */
+    UserIdentity getSystemUserIdentity();
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/JDBCLoginService.java b/lib/jetty/org/eclipse/jetty/security/JDBCLoginService.java
new file mode 100644 (file)
index 0000000..30b0a83
--- /dev/null
@@ -0,0 +1,297 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Credential;
+
+/* ------------------------------------------------------------ */
+/**
+ * HashMapped User Realm with JDBC as data source. JDBCLoginService extends
+ * HashULoginService and adds a method to fetch user information from database.
+ * The login() method checks the inherited Map for the user. If the user is not
+ * found, it will fetch details from the database and populate the inherited
+ * Map. It then calls the superclass login() method to perform the actual
+ * authentication. Periodically (controlled by configuration parameter),
+ * internal hashes are cleared. Caching can be disabled by setting cache refresh
+ * interval to zero. Uses one database connection that is initialized at
+ * startup. Reconnect on failures. authenticate() is 'synchronized'.
+ * 
+ * An example properties file for configuration is in
+ * $JETTY_HOME/etc/jdbcRealm.properties
+ * 
+ * @version $Id: JDBCLoginService.java 4792 2009-03-18 21:55:52Z gregw $
+ * 
+ * 
+ * 
+ * 
+ */
+
+public class JDBCLoginService extends MappedLoginService
+{
+    private static final Logger LOG = Log.getLogger(JDBCLoginService.class);
+
+    protected String _config;
+    protected String _jdbcDriver;
+    protected String _url;
+    protected String _userName;
+    protected String _password;
+    protected String _userTableKey;
+    protected String _userTablePasswordField;
+    protected String _roleTableRoleField;
+    protected int _cacheTime;
+    protected long _lastHashPurge;
+    protected Connection _con;
+    protected String _userSql;
+    protected String _roleSql;
+
+
+    /* ------------------------------------------------------------ */
+    public JDBCLoginService()
+        throws IOException
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    public JDBCLoginService(String name)
+        throws IOException
+    {
+        setName(name);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public JDBCLoginService(String name, String config)
+        throws IOException
+    {
+        setName(name);
+        setConfig(config);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public JDBCLoginService(String name, IdentityService identityService, String config)
+        throws IOException
+    {
+        setName(name);
+        setIdentityService(identityService);
+        setConfig(config);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.MappedLoginService#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        Properties properties = new Properties();
+        Resource resource = Resource.newResource(_config);
+        try (InputStream in = resource.getInputStream())
+        {
+            properties.load(in);
+        }
+        _jdbcDriver = properties.getProperty("jdbcdriver");
+        _url = properties.getProperty("url");
+        _userName = properties.getProperty("username");
+        _password = properties.getProperty("password");
+        String _userTable = properties.getProperty("usertable");
+        _userTableKey = properties.getProperty("usertablekey");
+        String _userTableUserField = properties.getProperty("usertableuserfield");
+        _userTablePasswordField = properties.getProperty("usertablepasswordfield");
+        String _roleTable = properties.getProperty("roletable");
+        String _roleTableKey = properties.getProperty("roletablekey");
+        _roleTableRoleField = properties.getProperty("roletablerolefield");
+        String _userRoleTable = properties.getProperty("userroletable");
+        String _userRoleTableUserKey = properties.getProperty("userroletableuserkey");
+        String _userRoleTableRoleKey = properties.getProperty("userroletablerolekey");
+        _cacheTime = new Integer(properties.getProperty("cachetime"));
+
+        if (_jdbcDriver == null || _jdbcDriver.equals("")
+            || _url == null
+            || _url.equals("")
+            || _userName == null
+            || _userName.equals("")
+            || _password == null
+            || _cacheTime < 0)
+        {
+            LOG.warn("UserRealm " + getName() + " has not been properly configured");
+        }
+        _cacheTime *= 1000;
+        _lastHashPurge = 0;
+        _userSql = "select " + _userTableKey + "," + _userTablePasswordField + " from " + _userTable + " where " + _userTableUserField + " = ?";
+        _roleSql = "select r." + _roleTableRoleField
+                   + " from "
+                   + _roleTable
+                   + " r, "
+                   + _userRoleTable
+                   + " u where u."
+                   + _userRoleTableUserKey
+                   + " = ?"
+                   + " and r."
+                   + _roleTableKey
+                   + " = u."
+                   + _userRoleTableRoleKey;
+        
+        Loader.loadClass(this.getClass(), _jdbcDriver).newInstance();
+        super.doStart();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public String getConfig()
+    {
+        return _config;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Load JDBC connection configuration from properties file.
+     * 
+     * @param config Filename or url of user properties file.
+     */
+    public void setConfig(String config)
+    {        
+        if (isRunning())
+            throw new IllegalStateException("Running");
+        _config=config;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * (re)Connect to database with parameters setup by loadConfig()
+     */
+    public void connectDatabase()
+    {
+        try
+        {
+            Class.forName(_jdbcDriver);
+            _con = DriverManager.getConnection(_url, _userName, _password);
+        }
+        catch (SQLException e)
+        {
+            LOG.warn("UserRealm " + getName() + " could not connect to database; will try later", e);
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.warn("UserRealm " + getName() + " could not connect to database; will try later", e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public UserIdentity login(String username, Object credentials)
+    {
+        long now = System.currentTimeMillis();
+        if (now - _lastHashPurge > _cacheTime || _cacheTime == 0)
+        {
+            _users.clear();
+            _lastHashPurge = now;
+            closeConnection();
+        }
+        
+        return super.login(username,credentials);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void loadUsers()
+    {   
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected UserIdentity loadUser(String username)
+    {
+        try
+        {
+            if (null == _con) 
+                connectDatabase();
+
+            if (null == _con) 
+                throw new SQLException("Can't connect to database");
+
+            try (PreparedStatement stat1 = _con.prepareStatement(_userSql))
+            {
+                stat1.setObject(1, username);
+                try (ResultSet rs1 = stat1.executeQuery())
+                {
+                    if (rs1.next())
+                    {
+                        int key = rs1.getInt(_userTableKey);
+                        String credentials = rs1.getString(_userTablePasswordField);
+                        List<String> roles = new ArrayList<String>();
+
+                        try (PreparedStatement stat2 = _con.prepareStatement(_roleSql))
+                        {
+                            stat2.setInt(1, key);
+                            try (ResultSet rs2 = stat2.executeQuery())
+                            {
+                                while (rs2.next())
+                                    roles.add(rs2.getString(_roleTableRoleField));
+                            }
+                        }
+                        return putUser(username, credentials, roles.toArray(new String[roles.size()]));
+                    }
+                }
+            }
+        }
+        catch (SQLException e)
+        {
+            LOG.warn("UserRealm " + getName() + " could not load user information from database", e);
+            closeConnection();
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected UserIdentity putUser (String username, String credentials, String[] roles)
+    {
+        return putUser(username, Credential.getCredential(credentials),roles);
+    }
+    
+
+    /**
+     * Close an existing connection
+     */
+    private void closeConnection ()
+    {
+        if (_con != null)
+        {
+            if (LOG.isDebugEnabled()) LOG.debug("Closing db connection for JDBCUserRealm");
+            try { _con.close(); }catch (Exception e) {LOG.ignore(e);}
+        }
+        _con = null;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/LoginService.java b/lib/jetty/org/eclipse/jetty/security/LoginService.java
new file mode 100644 (file)
index 0000000..1e64141
--- /dev/null
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Login Service Interface.
+ * <p>
+ * The Login service provides an abstract mechanism for an {@link Authenticator}
+ * to check credentials and to create a {@link UserIdentity} using the 
+ * set {@link IdentityService}.
+ */
+public interface LoginService
+{
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Get the name of the login service (aka Realm name)
+     */
+    String getName();
+    
+    /* ------------------------------------------------------------ */
+    /** Login a user.
+     * @param username The user name
+     * @param credentials The users credentials
+     * @return A UserIdentity if the credentials matched, otherwise null
+     */
+    UserIdentity login(String username,Object credentials);
+    
+    /* ------------------------------------------------------------ */
+    /** Validate a user identity.
+     * Validate that a UserIdentity previously created by a call 
+     * to {@link #login(String, Object)} is still valid.
+     * @param user The user to validate
+     * @return true if authentication has not been revoked for the user.
+     */
+    boolean validate(UserIdentity user);
+    
+    /* ------------------------------------------------------------ */
+    /** Get the IdentityService associated with this Login Service.
+     * @return the IdentityService associated with this Login Service.
+     */
+    IdentityService getIdentityService();
+    
+    /* ------------------------------------------------------------ */
+    /** Set the IdentityService associated with this Login Service.
+     * @param service the IdentityService associated with this Login Service.
+     */
+    void setIdentityService(IdentityService service);
+    
+    void logout(UserIdentity user);
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/MappedLoginService.java b/lib/jetty/org/eclipse/jetty/security/MappedLoginService.java
new file mode 100644 (file)
index 0000000..480131d
--- /dev/null
@@ -0,0 +1,343 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Credential;
+
+
+
+/* ------------------------------------------------------------ */
+/**
+ * A login service that keeps UserIdentities in a concurrent map
+ * either as the source or a cache of the users.
+ *
+ */
+public abstract class MappedLoginService extends AbstractLifeCycle implements LoginService
+{
+    private static final Logger LOG = Log.getLogger(MappedLoginService.class);
+
+    protected IdentityService _identityService=new DefaultIdentityService();
+    protected String _name;
+    protected final ConcurrentMap<String, UserIdentity> _users=new ConcurrentHashMap<String, UserIdentity>();
+
+    /* ------------------------------------------------------------ */
+    protected MappedLoginService()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the name.
+     * @return the name
+     */
+    public String getName()
+    {
+        return _name;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the identityService.
+     * @return the identityService
+     */
+    public IdentityService getIdentityService()
+    {
+        return _identityService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the users.
+     * @return the users
+     */
+    public ConcurrentMap<String, UserIdentity> getUsers()
+    {
+        return _users;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the identityService.
+     * @param identityService the identityService to set
+     */
+    public void setIdentityService(IdentityService identityService)
+    {
+        if (isRunning())
+            throw new IllegalStateException("Running");
+        _identityService = identityService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the name.
+     * @param name the name to set
+     */
+    public void setName(String name)
+    {
+        if (isRunning())
+            throw new IllegalStateException("Running");
+        _name = name;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the users.
+     * @param users the users to set
+     */
+    public void setUsers(Map<String, UserIdentity> users)
+    {
+        if (isRunning())
+            throw new IllegalStateException("Running");
+        _users.clear();
+        _users.putAll(users);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        loadUsers();
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void logout(UserIdentity identity)
+    {
+        LOG.debug("logout {}",identity);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return this.getClass().getSimpleName()+"["+_name+"]";
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Put user into realm.
+     * Called by implementations to put the user data loaded from
+     * file/db etc into the user structure.
+     * @param userName User name
+     * @param info a UserIdentity instance, or a String password or Credential instance
+     * @return User instance
+     */
+    protected synchronized UserIdentity putUser(String userName, Object info)
+    {
+        final UserIdentity identity;
+        if (info instanceof UserIdentity)
+            identity=(UserIdentity)info;
+        else
+        {
+            Credential credential = (info instanceof Credential)?(Credential)info:Credential.getCredential(info.toString());
+
+            Principal userPrincipal = new KnownUser(userName,credential);
+            Subject subject = new Subject();
+            subject.getPrincipals().add(userPrincipal);
+            subject.getPrivateCredentials().add(credential);
+            subject.setReadOnly();
+            identity=_identityService.newUserIdentity(subject,userPrincipal,IdentityService.NO_ROLES);
+        }
+
+        _users.put(userName,identity);
+        return identity;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Put user into realm.
+     * @param userName The user to add
+     * @param credential The users Credentials
+     * @param roles The users roles
+     * @return UserIdentity
+     */
+    public synchronized UserIdentity putUser(String userName, Credential credential, String[] roles)
+    {
+        Principal userPrincipal = new KnownUser(userName,credential);
+        Subject subject = new Subject();
+        subject.getPrincipals().add(userPrincipal);
+        subject.getPrivateCredentials().add(credential);
+
+        if (roles!=null)
+            for (String role : roles)
+                subject.getPrincipals().add(new RolePrincipal(role));
+
+        subject.setReadOnly();
+        UserIdentity identity=_identityService.newUserIdentity(subject,userPrincipal,roles);
+        _users.put(userName,identity);
+        return identity;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void removeUser(String username)
+    {
+        _users.remove(username);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.LoginService#login(java.lang.String, java.lang.Object)
+     */
+    public UserIdentity login(String username, Object credentials)
+    {
+        if (username == null)
+            return null;
+        
+        UserIdentity user = _users.get(username);
+
+        if (user==null)
+            user = loadUser(username);
+
+        if (user!=null)
+        {
+            UserPrincipal principal = (UserPrincipal)user.getUserPrincipal();
+            if (principal.authenticate(credentials))
+                return user;
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean validate(UserIdentity user)
+    {
+        if (_users.containsKey(user.getUserPrincipal().getName()))
+            return true;
+
+        if (loadUser(user.getUserPrincipal().getName())!=null)
+            return true;
+
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected abstract UserIdentity loadUser(String username);
+
+    /* ------------------------------------------------------------ */
+    protected abstract void loadUsers() throws IOException;
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public interface UserPrincipal extends Principal,Serializable
+    {
+        boolean authenticate(Object credentials);
+        public boolean isAuthenticated();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class RolePrincipal implements Principal,Serializable
+    {
+        private static final long serialVersionUID = 2998397924051854402L;
+        private final String _roleName;
+        public RolePrincipal(String name)
+        {
+            _roleName=name;
+        }
+        public String getName()
+        {
+            return _roleName;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class Anonymous implements UserPrincipal,Serializable
+    {
+        private static final long serialVersionUID = 1097640442553284845L;
+
+        public boolean isAuthenticated()
+        {
+            return false;
+        }
+
+        public String getName()
+        {
+            return "Anonymous";
+        }
+
+        public boolean authenticate(Object credentials)
+        {
+            return false;
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class KnownUser implements UserPrincipal,Serializable
+    {
+        private static final long serialVersionUID = -6226920753748399662L;
+        private final String _name;
+        private final Credential _credential;
+
+        /* -------------------------------------------------------- */
+        public KnownUser(String name,Credential credential)
+        {
+            _name=name;
+            _credential=credential;
+        }
+
+        /* -------------------------------------------------------- */
+        public boolean authenticate(Object credentials)
+        {
+            return _credential!=null && _credential.check(credentials);
+        }
+
+        /* ------------------------------------------------------------ */
+        public String getName()
+        {
+            return _name;
+        }
+
+        /* -------------------------------------------------------- */
+        public boolean isAuthenticated()
+        {
+            return true;
+        }
+
+        /* -------------------------------------------------------- */
+        @Override
+        public String toString()
+        {
+            return _name;
+        }
+    }
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/security/PropertyUserStore.java b/lib/jetty/org/eclipse/jetty/security/PropertyUserStore.java
new file mode 100644 (file)
index 0000000..7a8b5e7
--- /dev/null
@@ -0,0 +1,356 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.security.MappedLoginService.KnownUser;
+import org.eclipse.jetty.security.MappedLoginService.RolePrincipal;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.Scanner.BulkListener;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * PropertyUserStore
+ *
+ * This class monitors a property file of the format mentioned below and notifies registered listeners of the changes to the the given file.
+ *
+ * <PRE>
+ *  username: password [,rolename ...]
+ * </PRE>
+ *
+ * Passwords may be clear text, obfuscated or checksummed. The class com.eclipse.Util.Password should be used to generate obfuscated passwords or password
+ * checksums.
+ *
+ * If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:.
+ */
+public class PropertyUserStore extends AbstractLifeCycle
+{
+    private static final Logger LOG = Log.getLogger(PropertyUserStore.class);
+
+    private String _config;
+    private Resource _configResource;
+    private Scanner _scanner;
+    private int _refreshInterval = 0;// default is not to reload
+
+    private IdentityService _identityService = new DefaultIdentityService();
+    private boolean _firstLoad = true; // true if first load, false from that point on
+    private final List<String> _knownUsers = new ArrayList<String>();
+    private final Map<String, UserIdentity> _knownUserIdentities = new HashMap<String, UserIdentity>();
+    private List<UserListener> _listeners;
+
+    /* ------------------------------------------------------------ */
+    public String getConfig()
+    {
+        return _config;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setConfig(String config)
+    {
+        _config = config;
+    }
+
+    /* ------------------------------------------------------------ */
+        public UserIdentity getUserIdentity(String userName)
+        {
+            return _knownUserIdentities.get(userName);
+        }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * returns the resource associated with the configured properties file, creating it if necessary
+     */
+    public Resource getConfigResource() throws IOException
+    {
+        if (_configResource == null)
+        {
+            _configResource = Resource.newResource(_config);
+        }
+
+        return _configResource;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * sets the refresh interval (in seconds)
+     */
+    public void setRefreshInterval(int msec)
+    {
+        _refreshInterval = msec;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * refresh interval in seconds for how often the properties file should be checked for changes
+     */
+    public int getRefreshInterval()
+    {
+        return _refreshInterval;
+    }
+
+    /* ------------------------------------------------------------ */
+    private void loadUsers() throws IOException
+    {
+        if (_config == null)
+            return;
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Load " + this + " from " + _config);
+        Properties properties = new Properties();
+        if (getConfigResource().exists())
+            properties.load(getConfigResource().getInputStream());
+        Set<String> known = new HashSet<String>();
+
+        for (Map.Entry<Object, Object> entry : properties.entrySet())
+        {
+            String username = ((String)entry.getKey()).trim();
+            String credentials = ((String)entry.getValue()).trim();
+            String roles = null;
+            int c = credentials.indexOf(',');
+            if (c > 0)
+            {
+                roles = credentials.substring(c + 1).trim();
+                credentials = credentials.substring(0,c).trim();
+            }
+
+            if (username != null && username.length() > 0 && credentials != null && credentials.length() > 0)
+            {
+                String[] roleArray = IdentityService.NO_ROLES;
+                if (roles != null && roles.length() > 0)
+                {
+                    roleArray = roles.split(",");
+                }
+                known.add(username);
+                Credential credential = Credential.getCredential(credentials);
+
+                Principal userPrincipal = new KnownUser(username,credential);
+                Subject subject = new Subject();
+                subject.getPrincipals().add(userPrincipal);
+                subject.getPrivateCredentials().add(credential);
+
+                if (roles != null)
+                {
+                    for (String role : roleArray)
+                    {
+                        subject.getPrincipals().add(new RolePrincipal(role));
+                    }
+                }
+
+                subject.setReadOnly();
+
+                _knownUserIdentities.put(username,_identityService.newUserIdentity(subject,userPrincipal,roleArray));
+                notifyUpdate(username,credential,roleArray);
+            }
+        }
+
+        synchronized (_knownUsers)
+        {
+            /*
+             * if its not the initial load then we want to process removed users
+             */
+            if (!_firstLoad)
+            {
+                Iterator<String> users = _knownUsers.iterator();
+                while (users.hasNext())
+                {
+                    String user = users.next();
+                    if (!known.contains(user))
+                    {
+                        _knownUserIdentities.remove(user);
+                        notifyRemove(user);
+                    }
+                }
+            }
+
+            /*
+             * reset the tracked _users list to the known users we just processed
+             */
+
+            _knownUsers.clear();
+            _knownUsers.addAll(known);
+
+        }
+
+        /*
+         * set initial load to false as there should be no more initial loads
+         */
+        _firstLoad = false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Depending on the value of the refresh interval, this method will either start up a scanner thread that will monitor the properties file for changes after
+     * it has initially loaded it. Otherwise the users will be loaded and there will be no active monitoring thread so changes will not be detected.
+     *
+     *
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+
+        if (getRefreshInterval() > 0)
+        {
+            _scanner = new Scanner();
+            _scanner.setScanInterval(getRefreshInterval());
+            List<File> dirList = new ArrayList<File>(1);
+            dirList.add(getConfigResource().getFile().getParentFile());
+            _scanner.setScanDirs(dirList);
+            _scanner.setFilenameFilter(new FilenameFilter()
+            {
+                public boolean accept(File dir, String name)
+                {
+                    File f = new File(dir,name);
+                    try
+                    {
+                        if (f.compareTo(getConfigResource().getFile()) == 0)
+                        {
+                            return true;
+                        }
+                    }
+                    catch (IOException e)
+                    {
+                        return false;
+                    }
+
+                    return false;
+                }
+
+            });
+
+            _scanner.addListener(new BulkListener()
+            {
+                public void filesChanged(List<String> filenames) throws Exception
+                {
+                    if (filenames == null)
+                        return;
+                    if (filenames.isEmpty())
+                        return;
+                    if (filenames.size() == 1)
+                    {
+                        Resource r = Resource.newResource(filenames.get(0));
+                        if (r.getFile().equals(_configResource.getFile()))
+                            loadUsers();
+                    }
+                }
+
+                public String toString()
+                {
+                    return "PropertyUserStore$Scanner";
+                }
+
+            });
+
+            _scanner.setReportExistingFilesOnStartup(true);
+            _scanner.setRecursive(false);
+            _scanner.start();
+        }
+        else
+        {
+            loadUsers();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+     */
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        if (_scanner != null)
+            _scanner.stop();
+        _scanner = null;
+    }
+
+    /**
+     * Notifies the registered listeners of potential updates to a user
+     *
+     * @param username
+     * @param credential
+     * @param roleArray
+     */
+    private void notifyUpdate(String username, Credential credential, String[] roleArray)
+    {
+        if (_listeners != null)
+        {
+            for (Iterator<UserListener> i = _listeners.iterator(); i.hasNext();)
+            {
+                i.next().update(username,credential,roleArray);
+            }
+        }
+    }
+
+    /**
+     * notifies the registered listeners that a user has been removed.
+     *
+     * @param username
+     */
+    private void notifyRemove(String username)
+    {
+        if (_listeners != null)
+        {
+            for (Iterator<UserListener> i = _listeners.iterator(); i.hasNext();)
+            {
+                i.next().remove(username);
+            }
+        }
+    }
+
+    /**
+     * registers a listener to be notified of the contents of the property file
+     */
+    public void registerUserListener(UserListener listener)
+    {
+        if (_listeners == null)
+        {
+            _listeners = new ArrayList<UserListener>();
+        }
+        _listeners.add(listener);
+    }
+
+    /**
+     * UserListener
+     */
+    public interface UserListener
+    {
+        public void update(String username, Credential credential, String[] roleArray);
+
+        public void remove(String username);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/RoleInfo.java b/lib/jetty/org/eclipse/jetty/security/RoleInfo.java
new file mode 100644 (file)
index 0000000..55f1ae2
--- /dev/null
@@ -0,0 +1,162 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * RoleInfo
+ * 
+ * Badly named class that holds the role and user data constraint info for a
+ * path/http method combination, extracted and combined from security
+ * constraints.
+ * 
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class RoleInfo
+{
+    private boolean _isAnyAuth;
+    private boolean _isAnyRole;
+    private boolean _checked;
+    private boolean _forbidden;
+    private UserDataConstraint _userDataConstraint;
+
+    /**
+     * List of permitted roles
+     */
+    private final Set<String> _roles = new CopyOnWriteArraySet<String>();
+
+    public RoleInfo()
+    {    
+    }
+    
+    public boolean isChecked()
+    {
+        return _checked;
+    }
+
+    public void setChecked(boolean checked)
+    {
+        this._checked = checked;
+        if (!checked)
+        {
+            _forbidden=false;
+            _roles.clear();
+            _isAnyRole=false;
+            _isAnyAuth=false;
+        }
+    }
+
+    public boolean isForbidden()
+    {
+        return _forbidden;
+    }
+
+    public void setForbidden(boolean forbidden)
+    {
+        this._forbidden = forbidden;
+        if (forbidden)
+        {
+            _checked = true;
+            _userDataConstraint = null;
+            _isAnyRole=false;
+            _isAnyAuth=false;
+            _roles.clear();
+        }
+    }
+
+    public boolean isAnyRole()
+    {
+        return _isAnyRole;
+    }
+
+    public void setAnyRole(boolean anyRole)
+    {
+        this._isAnyRole=anyRole;
+        if (anyRole)
+            _checked = true;
+    }
+    
+    public boolean isAnyAuth ()
+    {
+        return _isAnyAuth;
+    }
+    
+    public void setAnyAuth(boolean anyAuth)
+    {
+        this._isAnyAuth=anyAuth;
+        if (anyAuth)
+            _checked = true;
+    }
+
+    public UserDataConstraint getUserDataConstraint()
+    {
+        return _userDataConstraint;
+    }
+
+    public void setUserDataConstraint(UserDataConstraint userDataConstraint)
+    {
+        if (userDataConstraint == null) throw new NullPointerException("Null UserDataConstraint");
+        if (this._userDataConstraint == null)
+        {
+           
+            this._userDataConstraint = userDataConstraint;
+        }
+        else
+        {
+            this._userDataConstraint = this._userDataConstraint.combine(userDataConstraint);
+        }
+    }
+
+    public Set<String> getRoles()
+    {
+        return _roles;
+    }
+    
+    public void addRole(String role)
+    {
+        _roles.add(role);
+    }
+
+    public void combine(RoleInfo other)
+    {
+        if (other._forbidden)
+            setForbidden(true);
+        else if (!other._checked) // TODO is this the right way around???
+            setChecked(true);
+        else if (other._isAnyRole)
+            setAnyRole(true);
+        else if (other._isAnyAuth)
+            setAnyAuth(true);
+        else if (!_isAnyRole)
+        {
+            for (String r : other._roles)
+                _roles.add(r);
+        }
+        
+        setUserDataConstraint(other._userDataConstraint);
+    }
+    
+    @Override
+    public String toString()
+    {
+        return "{RoleInfo"+(_forbidden?",F":"")+(_checked?",C":"")+(_isAnyRole?",*":_roles)+(_userDataConstraint!=null?","+_userDataConstraint:"")+"}";
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/RoleRunAsToken.java b/lib/jetty/org/eclipse/jetty/security/RoleRunAsToken.java
new file mode 100644 (file)
index 0000000..13a7ea7
--- /dev/null
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+
+
+/**
+ * @version $Rev: 4701 $ $Date: 2009-03-03 13:01:26 +0100 (Tue, 03 Mar 2009) $
+ */
+public class RoleRunAsToken implements RunAsToken
+{
+    private final String _runAsRole;
+
+    public RoleRunAsToken(String runAsRole)
+    {
+        this._runAsRole = runAsRole;
+    }
+
+    public String getRunAsRole()
+    {
+        return _runAsRole;
+    }
+
+    public String toString()
+    {
+        return "RoleRunAsToken("+_runAsRole+")";
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/RunAsToken.java b/lib/jetty/org/eclipse/jetty/security/RunAsToken.java
new file mode 100644 (file)
index 0000000..639c972
--- /dev/null
@@ -0,0 +1,27 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+/**
+ * marker interface for run-as-role tokens
+ * @version $Rev: 4701 $ $Date: 2009-03-03 13:01:26 +0100 (Tue, 03 Mar 2009) $
+ */
+public interface RunAsToken
+{
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/SecurityHandler.java b/lib/jetty/org/eclipse/jetty/security/SecurityHandler.java
new file mode 100644 (file)
index 0000000..a6e108e
--- /dev/null
@@ -0,0 +1,708 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.security.authentication.DeferredAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Abstract SecurityHandler.
+ * Select and apply an {@link Authenticator} to a request.
+ * <p>
+ * The Authenticator may either be directly set on the handler
+ * or will be create during {@link #start()} with a call to
+ * either the default or set AuthenticatorFactory.
+ * <p>
+ * SecurityHandler has a set of initparameters that are used by the
+ * Authentication.Configuration. At startup, any context init parameters
+ * that start with "org.eclipse.jetty.security." that do not have
+ * values in the SecurityHandler init parameters, are copied.
+ *
+ */
+public abstract class SecurityHandler extends HandlerWrapper implements Authenticator.AuthConfiguration
+{
+    private static final Logger LOG = Log.getLogger(SecurityHandler.class);
+
+    /* ------------------------------------------------------------ */
+    private boolean _checkWelcomeFiles = false;
+    private Authenticator _authenticator;
+    private Authenticator.Factory _authenticatorFactory=new DefaultAuthenticatorFactory();
+    private String _realmName;
+    private String _authMethod;
+    private final Map<String,String> _initParameters=new HashMap<String,String>();
+    private LoginService _loginService;
+    private IdentityService _identityService;
+    private boolean _renewSession=true;
+
+    /* ------------------------------------------------------------ */
+    protected SecurityHandler()
+    {
+        addBean(_authenticatorFactory);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the identityService.
+     * @return the identityService
+     */
+    @Override
+    public IdentityService getIdentityService()
+    {
+        return _identityService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the identityService.
+     * @param identityService the identityService to set
+     */
+    public void setIdentityService(IdentityService identityService)
+    {
+        if (isStarted())
+            throw new IllegalStateException("Started");
+        updateBean(_identityService,identityService);
+        _identityService = identityService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the loginService.
+     * @return the loginService
+     */
+    @Override
+    public LoginService getLoginService()
+    {
+        return _loginService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the loginService.
+     * @param loginService the loginService to set
+     */
+    public void setLoginService(LoginService loginService)
+    {
+        if (isStarted())
+            throw new IllegalStateException("Started");
+        updateBean(_loginService,loginService);
+        _loginService = loginService;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public Authenticator getAuthenticator()
+    {
+        return _authenticator;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the authenticator.
+     * @param authenticator
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public void setAuthenticator(Authenticator authenticator)
+    {
+        if (isStarted())
+            throw new IllegalStateException("Started");
+        updateBean(_authenticator,authenticator);
+        _authenticator = authenticator;
+        if (_authenticator!=null)
+            _authMethod=_authenticator.getAuthMethod();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the authenticatorFactory
+     */
+    public Authenticator.Factory getAuthenticatorFactory()
+    {
+        return _authenticatorFactory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param authenticatorFactory the authenticatorFactory to set
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public void setAuthenticatorFactory(Authenticator.Factory authenticatorFactory)
+    {
+        if (isRunning())
+            throw new IllegalStateException("running");
+        updateBean(_authenticatorFactory,authenticatorFactory);
+        _authenticatorFactory = authenticatorFactory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the realmName
+     */
+    @Override
+    public String getRealmName()
+    {
+        return _realmName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param realmName the realmName to set
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public void setRealmName(String realmName)
+    {
+        if (isRunning())
+            throw new IllegalStateException("running");
+        _realmName = realmName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the authMethod
+     */
+    @Override
+    public String getAuthMethod()
+    {
+        return _authMethod;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param authMethod the authMethod to set
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public void setAuthMethod(String authMethod)
+    {
+        if (isRunning())
+            throw new IllegalStateException("running");
+        _authMethod = authMethod;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if forwards to welcome files are authenticated
+     */
+    public boolean isCheckWelcomeFiles()
+    {
+        return _checkWelcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param authenticateWelcomeFiles True if forwards to welcome files are
+     *                authenticated
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public void setCheckWelcomeFiles(boolean authenticateWelcomeFiles)
+    {
+        if (isRunning())
+            throw new IllegalStateException("running");
+        _checkWelcomeFiles = authenticateWelcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getInitParameter(String key)
+    {
+        return _initParameters.get(key);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Set<String> getInitParameterNames()
+    {
+        return _initParameters.keySet();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set an initialization parameter.
+     * @param key
+     * @param value
+     * @return previous value
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public String setInitParameter(String key, String value)
+    {
+        if (isRunning())
+            throw new IllegalStateException("running");
+        return _initParameters.put(key,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected LoginService findLoginService() throws Exception
+    {
+        Collection<LoginService> list = getServer().getBeans(LoginService.class);
+        LoginService service = null;
+        String realm=getRealmName();
+        if (realm!=null)
+        {
+            for (LoginService s : list)
+                if (s.getName()!=null && s.getName().equals(realm))
+                {
+                    service=s;
+                    break;
+                }
+        }
+        else if (list.size()==1)
+            service =  list.iterator().next();
+        
+        return service;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected IdentityService findIdentityService()
+    {
+        return getServer().getBean(IdentityService.class);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    @Override
+    protected void doStart()
+        throws Exception
+    {
+        // copy security init parameters
+        ContextHandler.Context context =ContextHandler.getCurrentContext();
+        if (context!=null)
+        {
+            Enumeration<String> names=context.getInitParameterNames();
+            while (names!=null && names.hasMoreElements())
+            {
+                String name =names.nextElement();
+                if (name.startsWith("org.eclipse.jetty.security.") &&
+                        getInitParameter(name)==null)
+                    setInitParameter(name,context.getInitParameter(name));
+            }
+            
+            //register a session listener to handle securing sessions when authentication is performed
+            context.getContextHandler().addEventListener(new HttpSessionListener()
+            {
+                @Override
+                public void sessionDestroyed(HttpSessionEvent se)
+                {
+                }
+
+                @Override
+                public void sessionCreated(HttpSessionEvent se)
+                {                    
+                    //if current request is authenticated, then as we have just created the session, mark it as secure, as it has not yet been returned to a user
+                    HttpChannel<?> channel = HttpChannel.getCurrentHttpChannel();              
+                    
+                    if (channel == null)
+                        return;
+                    Request request = channel.getRequest();
+                    if (request == null)
+                        return;
+                    
+                    if (request.isSecure())
+                    {
+                        se.getSession().setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+                    }
+                }
+            });
+        }
+
+        // complicated resolution of login and identity service to handle
+        // many different ways these can be constructed and injected.
+
+        if (_loginService==null)
+        {
+            setLoginService(findLoginService());
+            if (_loginService!=null)
+                unmanage(_loginService);
+        }
+        
+        if (_identityService==null)
+        {
+            if (_loginService!=null)
+                setIdentityService(_loginService.getIdentityService());
+
+            if (_identityService==null)
+                setIdentityService(findIdentityService());
+
+            if (_identityService==null)
+            {
+                if (_realmName!=null)
+                { 
+                    setIdentityService(new DefaultIdentityService());
+                    manage(_identityService);
+                }
+            }
+            else
+                unmanage(_identityService);
+        }
+
+        if (_loginService!=null)
+        {
+            if (_loginService.getIdentityService()==null)
+                _loginService.setIdentityService(_identityService);
+            else if (_loginService.getIdentityService()!=_identityService)
+                throw new IllegalStateException("LoginService has different IdentityService to "+this);
+        }
+
+        Authenticator.Factory authenticatorFactory = getAuthenticatorFactory();
+        if (_authenticator==null && authenticatorFactory!=null && _identityService!=null)
+            setAuthenticator(authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService));
+
+        if (_authenticator!=null)
+            _authenticator.setConfiguration(this);
+        else if (_realmName!=null)
+        {
+            LOG.warn("No Authenticator for "+this);
+            throw new IllegalStateException("No Authenticator");
+        }
+
+        super.doStart();
+    }
+
+    @Override
+    /* ------------------------------------------------------------ */
+    protected void doStop() throws Exception
+    {
+        //if we discovered the services (rather than had them explicitly configured), remove them.
+        if (!isManaged(_identityService))
+        {
+            removeBean(_identityService);
+            _identityService = null;   
+        }
+        
+        if (!isManaged(_loginService))
+        {
+            removeBean(_loginService);
+            _loginService=null;
+        }
+        
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    protected boolean checkSecurity(Request request)
+    {
+        switch(request.getDispatcherType())
+        {
+            case REQUEST:
+            case ASYNC:
+                return true;
+            case FORWARD:
+                if (isCheckWelcomeFiles() && request.getAttribute("org.eclipse.jetty.server.welcome") != null)
+                {
+                    request.removeAttribute("org.eclipse.jetty.server.welcome");
+                    return true;
+                }
+                return false;
+            default:
+                return false;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
+     */
+    @Override
+    public boolean isSessionRenewedOnAuthentication()
+    {
+        return _renewSession;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set renew the session on Authentication.
+     * <p>
+     * If set to true, then on authentication, the session associated with a reqeuest is invalidated and replaced with a new session.
+     * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
+     */
+    public void setSessionRenewedOnAuthentication(boolean renew)
+    {
+        _renewSession=renew;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(java.lang.String,
+     *      javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        final Response base_response = baseRequest.getResponse();
+        final Handler handler=getHandler();
+
+        if (handler==null)
+            return;
+
+        final Authenticator authenticator = _authenticator;
+
+        if (checkSecurity(baseRequest))
+        {
+            //See Servlet Spec 3.1 sec 13.6.3
+            if (authenticator != null)
+                authenticator.prepareRequest(baseRequest);
+            
+            RoleInfo roleInfo = prepareConstraintInfo(pathInContext, baseRequest);
+
+            // Check data constraints
+            if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, roleInfo))
+            {
+                if (!baseRequest.isHandled())
+                {
+                    response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                    baseRequest.setHandled(true);
+                }
+                return;
+            }
+
+            // is Auth mandatory?
+            boolean isAuthMandatory =
+                isAuthMandatory(baseRequest, base_response, roleInfo);
+
+            if (isAuthMandatory && authenticator==null)
+            {
+                LOG.warn("No authenticator for: "+roleInfo);
+                if (!baseRequest.isHandled())
+                {
+                    response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                    baseRequest.setHandled(true);
+                }
+                return;
+            }
+
+            // check authentication
+            Object previousIdentity = null;
+            try
+            {
+                Authentication authentication = baseRequest.getAuthentication();
+                if (authentication==null || authentication==Authentication.NOT_CHECKED)
+                    authentication=authenticator==null?Authentication.UNAUTHENTICATED:authenticator.validateRequest(request, response, isAuthMandatory);
+
+                if (authentication instanceof Authentication.Wrapped)
+                {
+                    request=((Authentication.Wrapped)authentication).getHttpServletRequest();
+                    response=((Authentication.Wrapped)authentication).getHttpServletResponse();
+                }
+
+                if (authentication instanceof Authentication.ResponseSent)
+                {
+                    baseRequest.setHandled(true);
+                }
+                else if (authentication instanceof Authentication.User)
+                {
+                    Authentication.User userAuth = (Authentication.User)authentication;
+                    baseRequest.setAuthentication(authentication);
+                    if (_identityService!=null)
+                        previousIdentity = _identityService.associate(userAuth.getUserIdentity());
+
+                    if (isAuthMandatory)
+                    {
+                        boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, roleInfo, userAuth.getUserIdentity());
+                        if (!authorized)
+                        {
+                            response.sendError(HttpServletResponse.SC_FORBIDDEN, "!role");
+                            baseRequest.setHandled(true);
+                            return;
+                        }
+                    }
+
+                    handler.handle(pathInContext, baseRequest, request, response);
+                    if (authenticator!=null)
+                        authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
+                }
+                else if (authentication instanceof Authentication.Deferred)
+                {
+                    DeferredAuthentication deferred= (DeferredAuthentication)authentication;
+                    baseRequest.setAuthentication(authentication);
+
+                    try
+                    {
+                        handler.handle(pathInContext, baseRequest, request, response);
+                    }
+                    finally
+                    {
+                        previousIdentity = deferred.getPreviousAssociation();
+                    }
+
+                    if (authenticator!=null)
+                    {
+                        Authentication auth=baseRequest.getAuthentication();
+                        if (auth instanceof Authentication.User)
+                        {
+                            Authentication.User userAuth = (Authentication.User)auth;
+                            authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
+                        }
+                        else
+                            authenticator.secureResponse(request, response, isAuthMandatory, null);
+                    }
+                }
+                else
+                {
+                    baseRequest.setAuthentication(authentication);
+                    if (_identityService!=null)
+                        previousIdentity = _identityService.associate(null);
+                    handler.handle(pathInContext, baseRequest, request, response);
+                    if (authenticator!=null)
+                        authenticator.secureResponse(request, response, isAuthMandatory, null);
+                }
+            }
+            catch (ServerAuthException e)
+            {
+                // jaspi 3.8.3 send HTTP 500 internal server error, with message
+                // from AuthException
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
+            }
+            finally
+            {
+                if (_identityService!=null)
+                    _identityService.disassociate(previousIdentity);
+            }
+        }
+        else
+            handler.handle(pathInContext, baseRequest, request, response);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static SecurityHandler getCurrentSecurityHandler()
+    {
+        Context context = ContextHandler.getCurrentContext();
+        if (context==null)
+            return null;
+
+        return context.getContextHandler().getChildHandlerByClass(SecurityHandler.class);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void logout(Authentication.User user)
+    {
+        LOG.debug("logout {}",user);
+        LoginService login_service=getLoginService();
+        if (login_service!=null)
+        {
+            login_service.logout(user.getUserIdentity());
+        }
+
+        IdentityService identity_service=getIdentityService();
+        if (identity_service!=null)
+        {
+            // TODO recover previous from threadlocal (or similar)
+            Object previous=null;
+            identity_service.disassociate(previous);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected abstract RoleInfo prepareConstraintInfo(String pathInContext, Request request);
+
+    /* ------------------------------------------------------------ */
+    protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo constraintInfo) throws IOException;
+
+    /* ------------------------------------------------------------ */
+    protected abstract boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo);
+
+    /* ------------------------------------------------------------ */
+    protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo,
+                                                           UserIdentity userIdentity) throws IOException;
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public class NotChecked implements Principal
+    {
+        @Override
+        public String getName()
+        {
+            return null;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "NOT CHECKED";
+        }
+
+        public SecurityHandler getSecurityHandler()
+        {
+            return SecurityHandler.this;
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static final Principal __NO_USER = new Principal()
+    {
+        @Override
+        public String getName()
+        {
+            return null;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "No User";
+        }
+    };
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Nobody user. The Nobody UserPrincipal is used to indicate a partial state
+     * of authentication. A request with a Nobody UserPrincipal will be allowed
+     * past all authentication constraints - but will not be considered an
+     * authenticated request. It can be used by Authenticators such as
+     * FormAuthenticator to allow access to logon and error pages within an
+     * authenticated URI tree.
+     */
+    public static final Principal __NOBODY = new Principal()
+    {
+        @Override
+        public String getName()
+        {
+            return "Nobody";
+        }
+
+        @Override
+        public String toString()
+        {
+            return getName();
+        }
+    };
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/ServerAuthException.java b/lib/jetty/org/eclipse/jetty/security/ServerAuthException.java
new file mode 100644 (file)
index 0000000..85f532f
--- /dev/null
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.GeneralSecurityException;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public class ServerAuthException extends GeneralSecurityException
+{
+
+    public ServerAuthException()
+    {
+    }
+
+    public ServerAuthException(String s)
+    {
+        super(s);
+    }
+
+    public ServerAuthException(String s, Throwable throwable)
+    {
+        super(s, throwable);
+    }
+
+    public ServerAuthException(Throwable throwable)
+    {
+        super(throwable);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/SpnegoLoginService.java b/lib/jetty/org/eclipse/jetty/security/SpnegoLoginService.java
new file mode 100644 (file)
index 0000000..ba160f0
--- /dev/null
@@ -0,0 +1,191 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.Properties;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+
+public class SpnegoLoginService extends AbstractLifeCycle implements LoginService
+{
+    private static final Logger LOG = Log.getLogger(SpnegoLoginService.class);
+
+    protected IdentityService _identityService;// = new LdapIdentityService();
+    protected String _name;
+    private String _config;
+
+    private String _targetName;
+
+    public SpnegoLoginService()
+    {
+
+    }
+
+    public SpnegoLoginService( String name )
+    {
+        setName(name);
+    }
+
+    public SpnegoLoginService( String name, String config )
+    {
+        setName(name);
+        setConfig(config);
+    }
+
+    @Override
+    public String getName()
+    {
+        return _name;
+    }
+
+    public void setName(String name)
+    {
+        if (isRunning())
+        {
+            throw new IllegalStateException("Running");
+        }
+
+        _name = name;
+    }
+
+    public String getConfig()
+    {
+        return _config;
+    }
+
+    public void setConfig( String config )
+    {
+        if (isRunning())
+        {
+            throw new IllegalStateException("Running");
+        }
+
+        _config = config;
+    }
+
+
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        Properties properties = new Properties();
+        Resource resource = Resource.newResource(_config);
+        properties.load(resource.getInputStream());
+
+        _targetName = properties.getProperty("targetName");
+
+        LOG.debug("Target Name {}", _targetName);
+
+        super.doStart();
+    }
+
+    /**
+     * username will be null since the credentials will contain all the relevant info
+     */
+    @Override
+    public UserIdentity login(String username, Object credentials)
+    {
+        String encodedAuthToken = (String)credentials;
+
+        byte[] authToken = B64Code.decode(encodedAuthToken);
+
+        GSSManager manager = GSSManager.getInstance();
+        try
+        {
+            Oid krb5Oid = new Oid("1.3.6.1.5.5.2"); // http://java.sun.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html
+            GSSName gssName = manager.createName(_targetName,null);
+            GSSCredential serverCreds = manager.createCredential(gssName,GSSCredential.INDEFINITE_LIFETIME,krb5Oid,GSSCredential.ACCEPT_ONLY);
+            GSSContext gContext = manager.createContext(serverCreds);
+
+            if (gContext == null)
+            {
+                LOG.debug("SpnegoUserRealm: failed to establish GSSContext");
+            }
+            else
+            {
+                while (!gContext.isEstablished())
+                {
+                    authToken = gContext.acceptSecContext(authToken,0,authToken.length);
+                }
+                if (gContext.isEstablished())
+                {
+                    String clientName = gContext.getSrcName().toString();
+                    String role = clientName.substring(clientName.indexOf('@') + 1);
+
+                    LOG.debug("SpnegoUserRealm: established a security context");
+                    LOG.debug("Client Principal is: " + gContext.getSrcName());
+                    LOG.debug("Server Principal is: " + gContext.getTargName());
+                    LOG.debug("Client Default Role: " + role);
+
+                    SpnegoUserPrincipal user = new SpnegoUserPrincipal(clientName,authToken);
+
+                    Subject subject = new Subject();
+                    subject.getPrincipals().add(user);
+
+                    return _identityService.newUserIdentity(subject,user, new String[]{role});
+                }
+            }
+
+        }
+        catch (GSSException gsse)
+        {
+            LOG.warn(gsse);
+        }
+
+        return null;
+    }
+
+    @Override
+    public boolean validate(UserIdentity user)
+    {
+        return false;
+    }
+
+    @Override
+    public IdentityService getIdentityService()
+    {
+        return _identityService;
+    }
+
+    @Override
+    public void setIdentityService(IdentityService service)
+    {
+        _identityService = service;
+    }
+
+    @Override
+    public void logout(UserIdentity user) 
+    {
+        // TODO Auto-generated method stub
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/SpnegoUserIdentity.java b/lib/jetty/org/eclipse/jetty/security/SpnegoUserIdentity.java
new file mode 100644 (file)
index 0000000..13cf0bb
--- /dev/null
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+import java.util.List;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+public class SpnegoUserIdentity implements UserIdentity
+{
+    private Subject _subject;
+    private Principal _principal;
+    private List<String> _roles;
+
+    public SpnegoUserIdentity( Subject subject, Principal principal, List<String> roles )
+    {
+        _subject = subject;
+        _principal = principal;
+        _roles = roles;
+    }
+
+
+    public Subject getSubject()
+    {
+        return _subject;
+    }
+
+    public Principal getUserPrincipal()
+    {
+        return _principal;
+    }
+
+    public boolean isUserInRole(String role, Scope scope)
+    {
+        return _roles.contains(role);
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/SpnegoUserPrincipal.java b/lib/jetty/org/eclipse/jetty/security/SpnegoUserPrincipal.java
new file mode 100644 (file)
index 0000000..3fe9445
--- /dev/null
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import org.eclipse.jetty.util.B64Code;
+
+public class SpnegoUserPrincipal implements Principal
+{
+    private final String _name;
+    private byte[] _token;
+    private String _encodedToken;
+
+    public SpnegoUserPrincipal( String name, String encodedToken )
+    {
+        _name = name;
+        _encodedToken = encodedToken;
+    }
+
+    public SpnegoUserPrincipal( String name, byte[] token )
+    {
+        _name = name;
+        _token = token;
+    }
+
+    public String getName()
+    {
+        return _name;
+    }
+
+    public byte[] getToken()
+    {
+        if ( _token == null )
+        {
+            _token = B64Code.decode(_encodedToken);
+        }
+        return _token;
+    }
+
+    public String getEncodedToken()
+    {
+        if ( _encodedToken == null )
+        {
+            _encodedToken = new String(B64Code.encode(_token,true));
+        }
+        return _encodedToken;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/UserAuthentication.java b/lib/jetty/org/eclipse/jetty/security/UserAuthentication.java
new file mode 100644 (file)
index 0000000..9174a06
--- /dev/null
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class UserAuthentication extends AbstractUserAuthentication
+{
+   
+    public UserAuthentication(String method, UserIdentity userIdentity)
+    {
+        super(method, userIdentity);
+    }
+
+    
+    @Override
+    public String toString()
+    {
+        return "{User,"+getAuthMethod()+","+_userIdentity+"}";
+    }
+
+    public void logout()
+    {
+        SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+        if (security!=null)
+            security.logout(this);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/UserDataConstraint.java b/lib/jetty/org/eclipse/jetty/security/UserDataConstraint.java
new file mode 100644 (file)
index 0000000..c288e1d
--- /dev/null
@@ -0,0 +1,40 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public enum UserDataConstraint
+{
+    None, Integral, Confidential;
+
+    public static UserDataConstraint get(int dataConstraint)
+    {
+        if (dataConstraint < -1 || dataConstraint > 2) throw new IllegalArgumentException("Expected -1, 0, 1, or 2, not: " + dataConstraint);
+        if (dataConstraint == -1) return None;
+        return values()[dataConstraint];
+    }
+
+    public UserDataConstraint combine(UserDataConstraint other)
+    {
+        if (this.compareTo(other) < 0) return this;
+        return other;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/BasicAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/BasicAuthenticator.java
new file mode 100644 (file)
index 0000000..caa784f
--- /dev/null
@@ -0,0 +1,121 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.security.Constraint;
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class BasicAuthenticator extends LoginAuthenticator
+{
+    /* ------------------------------------------------------------ */
+    public BasicAuthenticator()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.Authenticator#getAuthMethod()
+     */
+    @Override
+    public String getAuthMethod()
+    {
+        return Constraint.__BASIC_AUTH;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.Authenticator#validateRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse, boolean)
+     */
+    @Override
+    public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+    {
+        HttpServletRequest request = (HttpServletRequest)req;
+        HttpServletResponse response = (HttpServletResponse)res;
+        String credentials = request.getHeader(HttpHeader.AUTHORIZATION.asString());
+
+        try
+        {
+            if (!mandatory)
+                return new DeferredAuthentication(this);
+
+            if (credentials != null)
+            {
+                int space=credentials.indexOf(' ');
+                if (space>0)
+                {
+                    String method=credentials.substring(0,space);
+                    if ("basic".equalsIgnoreCase(method))
+                    {
+                        credentials = credentials.substring(space+1);
+                        credentials = B64Code.decode(credentials, StandardCharsets.ISO_8859_1);
+                        int i = credentials.indexOf(':');
+                        if (i>0)
+                        {
+                            String username = credentials.substring(0,i);
+                            String password = credentials.substring(i+1);
+
+                            UserIdentity user = login (username, password, request);
+                            if (user!=null)
+                            {
+                                return new UserAuthentication(getAuthMethod(),user);
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (DeferredAuthentication.isDeferred(response))
+                return Authentication.UNAUTHENTICATED;
+
+            response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "basic realm=\"" + _loginService.getName() + '"');
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return Authentication.SEND_CONTINUE;
+        }
+        catch (IOException e)
+        {
+            throw new ServerAuthException(e);
+        }
+    }
+
+    @Override
+    public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java
new file mode 100644 (file)
index 0000000..9694cf1
--- /dev/null
@@ -0,0 +1,371 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.Principal;
+import java.security.cert.CRL;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.security.CertificateUtils;
+import org.eclipse.jetty.util.security.CertificateValidator;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Password;
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class ClientCertAuthenticator extends LoginAuthenticator
+{
+    /** String name of keystore password property. */
+    private static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";
+
+    /** Truststore path */
+    private String _trustStorePath;
+    /** Truststore provider name */
+    private String _trustStoreProvider;
+    /** Truststore type */
+    private String _trustStoreType = "JKS";
+    /** Truststore password */
+    private transient Password _trustStorePassword;
+
+    /** Set to true if SSL certificate validation is required */
+    private boolean _validateCerts;
+    /** Path to file that contains Certificate Revocation List */
+    private String _crlPath;
+    /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
+    private int _maxCertPathLength = -1;
+    /** CRL Distribution Points (CRLDP) support */
+    private boolean _enableCRLDP = false;
+    /** On-Line Certificate Status Protocol (OCSP) support */
+    private boolean _enableOCSP = false;
+    /** Location of OCSP Responder */
+    private String _ocspResponderURL;
+
+    public ClientCertAuthenticator()
+    {
+        super();
+    }
+
+    @Override
+    public String getAuthMethod()
+    {
+        return Constraint.__CERT_AUTH;
+    }
+
+    
+
+    /**
+     * @return Authentication for request
+     * @throws ServerAuthException
+     */
+    @Override
+    public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+    {
+        if (!mandatory)
+            return new DeferredAuthentication(this);
+
+        HttpServletRequest request = (HttpServletRequest)req;
+        HttpServletResponse response = (HttpServletResponse)res;
+        X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
+
+        try
+        {
+            // Need certificates.
+            if (certs != null && certs.length > 0)
+            {
+
+                if (_validateCerts)
+                {
+                    KeyStore trustStore = getKeyStore(null,
+                            _trustStorePath, _trustStoreType, _trustStoreProvider,
+                            _trustStorePassword == null ? null :_trustStorePassword.toString());
+                    Collection<? extends CRL> crls = loadCRL(_crlPath);
+                    CertificateValidator validator = new CertificateValidator(trustStore, crls);
+                    validator.validate(certs);
+                }
+
+                for (X509Certificate cert: certs)
+                {
+                    if (cert==null)
+                        continue;
+
+                    Principal principal = cert.getSubjectDN();
+                    if (principal == null) principal = cert.getIssuerDN();
+                    final String username = principal == null ? "clientcert" : principal.getName();
+
+                    final char[] credential = B64Code.encode(cert.getSignature());
+
+                    UserIdentity user = login(username, credential, req);
+                    if (user!=null)
+                    {
+                        return new UserAuthentication(getAuthMethod(),user);
+                    }
+                }
+            }
+
+            if (!DeferredAuthentication.isDeferred(response))
+            {
+                response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                return Authentication.SEND_FAILURE;
+            }
+
+            return Authentication.UNAUTHENTICATED;
+        }
+        catch (Exception e)
+        {
+            throw new ServerAuthException(e.getMessage());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Loads keystore using an input stream or a file path in the same
+     * order of precedence.
+     *
+     * Required for integrations to be able to override the mechanism
+     * used to load a keystore in order to provide their own implementation.
+     *
+     * @param storeStream keystore input stream
+     * @param storePath path of keystore file
+     * @param storeType keystore type
+     * @param storeProvider keystore provider
+     * @param storePassword keystore password
+     * @return created keystore
+     * @throws Exception
+     */
+    protected KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
+    {
+        return CertificateUtils.getKeyStore(storeStream, storePath, storeType, storeProvider, storePassword);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Loads certificate revocation list (CRL) from a file.
+     *
+     * Required for integrations to be able to override the mechanism used to
+     * load CRL in order to provide their own implementation.
+     *
+     * @param crlPath path of certificate revocation list file
+     * @return a (possibly empty) collection view of java.security.cert.CRL objects initialized with the data from the
+     *         input stream.
+     * @throws Exception
+     */
+    protected Collection<? extends CRL> loadCRL(String crlPath) throws Exception
+    {
+        return CertificateUtils.loadCRL(crlPath);
+    }
+
+    @Override
+    public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if SSL certificate has to be validated
+     */
+    public boolean isValidateCerts()
+    {
+        return _validateCerts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param validateCerts
+     *            true if SSL certificates have to be validated
+     */
+    public void setValidateCerts(boolean validateCerts)
+    {
+        _validateCerts = validateCerts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The file name or URL of the trust store location
+     */
+    public String getTrustStore()
+    {
+        return _trustStorePath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param trustStorePath
+     *            The file name or URL of the trust store location
+     */
+    public void setTrustStore(String trustStorePath)
+    {
+        _trustStorePath = trustStorePath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The provider of the trust store
+     */
+    public String getTrustStoreProvider()
+    {
+        return _trustStoreProvider;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param trustStoreProvider
+     *            The provider of the trust store
+     */
+    public void setTrustStoreProvider(String trustStoreProvider)
+    {
+        _trustStoreProvider = trustStoreProvider;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The type of the trust store (default "JKS")
+     */
+    public String getTrustStoreType()
+    {
+        return _trustStoreType;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param trustStoreType
+     *            The type of the trust store (default "JKS")
+     */
+    public void setTrustStoreType(String trustStoreType)
+    {
+        _trustStoreType = trustStoreType;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param password
+     *            The password for the trust store
+     */
+    public void setTrustStorePassword(String password)
+    {
+        _trustStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the crlPath.
+     * @return the crlPath
+     */
+    public String getCrlPath()
+    {
+        return _crlPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the crlPath.
+     * @param crlPath the crlPath to set
+     */
+    public void setCrlPath(String crlPath)
+    {
+        _crlPath = crlPath;
+    }
+
+    /**
+     * @return Maximum number of intermediate certificates in
+     * the certification path (-1 for unlimited)
+     */
+    public int getMaxCertPathLength()
+    {
+        return _maxCertPathLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maxCertPathLength
+     *            maximum number of intermediate certificates in
+     *            the certification path (-1 for unlimited)
+     */
+    public void setMaxCertPathLength(int maxCertPathLength)
+    {
+        _maxCertPathLength = maxCertPathLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if CRL Distribution Points support is enabled
+     */
+    public boolean isEnableCRLDP()
+    {
+        return _enableCRLDP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Enables CRL Distribution Points Support
+     * @param enableCRLDP true - turn on, false - turns off
+     */
+    public void setEnableCRLDP(boolean enableCRLDP)
+    {
+        _enableCRLDP = enableCRLDP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if On-Line Certificate Status Protocol support is enabled
+     */
+    public boolean isEnableOCSP()
+    {
+        return _enableOCSP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Enables On-Line Certificate Status Protocol support
+     * @param enableOCSP true - turn on, false - turn off
+     */
+    public void setEnableOCSP(boolean enableOCSP)
+    {
+        _enableOCSP = enableOCSP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Location of the OCSP Responder
+     */
+    public String getOcspResponderURL()
+    {
+        return _ocspResponderURL;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the location of the OCSP Responder.
+     * @param ocspResponderURL location of the OCSP Responder
+     */
+    public void setOcspResponderURL(String ocspResponderURL)
+    {
+        _ocspResponderURL = ocspResponderURL;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/DeferredAuthentication.java b/lib/jetty/org/eclipse/jetty/security/authentication/DeferredAuthentication.java
new file mode 100644 (file)
index 0000000..df0f7d9
--- /dev/null
@@ -0,0 +1,395 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Locale;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.WriteListener;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class DeferredAuthentication implements Authentication.Deferred
+{
+    private static final Logger LOG = Log.getLogger(DeferredAuthentication.class);
+    protected final LoginAuthenticator _authenticator;
+    private Object _previousAssociation;
+
+    /* ------------------------------------------------------------ */
+    public DeferredAuthentication(LoginAuthenticator authenticator)
+    {
+        if (authenticator == null)
+            throw new NullPointerException("No Authenticator");
+        this._authenticator = authenticator;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.Authentication.Deferred#authenticate(ServletRequest)
+     */
+    @Override
+    public Authentication authenticate(ServletRequest request)
+    {
+        try
+        {
+            Authentication authentication = _authenticator.validateRequest(request,__deferredResponse,true);
+
+            if (authentication!=null && (authentication instanceof Authentication.User) && !(authentication instanceof Authentication.ResponseSent))
+            {
+                LoginService login_service= _authenticator.getLoginService();
+                IdentityService identity_service=login_service.getIdentityService();
+                
+                if (identity_service!=null)
+                    _previousAssociation=identity_service.associate(((Authentication.User)authentication).getUserIdentity());
+                
+                return authentication;
+            }
+        }
+        catch (ServerAuthException e)
+        {
+            LOG.debug(e);
+        }
+
+        return this;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.Authentication.Deferred#authenticate(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+     */
+    @Override
+    public Authentication authenticate(ServletRequest request, ServletResponse response)
+    {
+        try
+        {
+            LoginService login_service= _authenticator.getLoginService();
+            IdentityService identity_service=login_service.getIdentityService();
+            
+            Authentication authentication = _authenticator.validateRequest(request,response,true);
+            if (authentication instanceof Authentication.User && identity_service!=null)
+                _previousAssociation=identity_service.associate(((Authentication.User)authentication).getUserIdentity());
+            return authentication;
+        }
+        catch (ServerAuthException e)
+        {
+            LOG.debug(e);
+        }
+        return this;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.Authentication.Deferred#login(String, Object, ServletRequest)
+     */
+    @Override
+    public Authentication login(String username, Object password, ServletRequest request)
+    {
+        if (username == null)
+            return null;
+        
+        UserIdentity identity = _authenticator.login(username, password, request);
+        if (identity != null)
+        {
+            IdentityService identity_service = _authenticator.getLoginService().getIdentityService();
+            UserAuthentication authentication = new UserAuthentication("API",identity);
+            if (identity_service != null)
+                _previousAssociation=identity_service.associate(identity);
+            return authentication;
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Object getPreviousAssociation()
+    {
+        return _previousAssociation;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param response
+     * @return true if this response is from a deferred call to {@link #authenticate(ServletRequest)}
+     */
+    public static boolean isDeferred(HttpServletResponse response)
+    {
+        return response==__deferredResponse;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    final static HttpServletResponse __deferredResponse = new HttpServletResponse()
+    {
+        @Override
+        public void addCookie(Cookie cookie)
+        {
+        }
+
+        @Override
+        public void addDateHeader(String name, long date)
+        {
+        }
+
+        @Override
+        public void addHeader(String name, String value)
+        {
+        }
+
+        @Override
+        public void addIntHeader(String name, int value)
+        {
+        }
+
+        @Override
+        public boolean containsHeader(String name)
+        {
+            return false;
+        }
+
+        @Override
+        public String encodeRedirectURL(String url)
+        {
+            return null;
+        }
+
+        @Override
+        public String encodeRedirectUrl(String url)
+        {
+            return null;
+        }
+
+        @Override
+        public String encodeURL(String url)
+        {
+            return null;
+        }
+
+        @Override
+        public String encodeUrl(String url)
+        {
+            return null;
+        }
+
+        @Override
+        public void sendError(int sc) throws IOException
+        {
+        }
+
+        @Override
+        public void sendError(int sc, String msg) throws IOException
+        {
+        }
+
+        @Override
+        public void sendRedirect(String location) throws IOException
+        {
+        }
+
+        @Override
+        public void setDateHeader(String name, long date)
+        {
+        }
+
+        @Override
+        public void setHeader(String name, String value)
+        {
+        }
+
+        @Override
+        public void setIntHeader(String name, int value)
+        {
+        }
+
+        @Override
+        public void setStatus(int sc)
+        {
+        }
+
+        @Override
+        public void setStatus(int sc, String sm)
+        {
+        }
+
+        @Override
+        public void flushBuffer() throws IOException
+        {
+        }
+
+        @Override
+        public int getBufferSize()
+        {
+            return 1024;
+        }
+
+        @Override
+        public String getCharacterEncoding()
+        {
+            return null;
+        }
+
+        @Override
+        public String getContentType()
+        {
+            return null;
+        }
+
+        @Override
+        public Locale getLocale()
+        {
+            return null;
+        }
+
+        @Override
+        public ServletOutputStream getOutputStream() throws IOException
+        {
+            return __nullOut;
+        }
+
+        @Override
+        public PrintWriter getWriter() throws IOException
+        {
+            return IO.getNullPrintWriter();
+        }
+
+        @Override
+        public boolean isCommitted()
+        {
+            return true;
+        }
+
+        @Override
+        public void reset()
+        {
+        }
+
+        @Override
+        public void resetBuffer()
+        {
+        }
+
+        @Override
+        public void setBufferSize(int size)
+        {
+        }
+
+        @Override
+        public void setCharacterEncoding(String charset)
+        {
+        }
+
+        @Override
+        public void setContentLength(int len)
+        {
+        }
+        
+        public void setContentLengthLong(long len)
+        {
+           
+        }
+
+        @Override
+        public void setContentType(String type)
+        {
+        }
+
+        @Override
+        public void setLocale(Locale loc)
+        {
+        }
+
+        @Override
+       public Collection<String> getHeaderNames()
+       {
+           return Collections.emptyList();
+       }
+
+       @Override
+       public String getHeader(String arg0)
+       {
+           return null;
+       }
+
+       @Override
+       public Collection<String> getHeaders(String arg0)
+       {
+            return Collections.emptyList();
+       }
+
+       @Override
+       public int getStatus()
+       {
+           return 0;
+       }
+
+
+    };
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static ServletOutputStream __nullOut = new ServletOutputStream()
+    {
+        @Override
+        public void write(int b) throws IOException
+        {
+        }
+        
+        @Override
+        public void print(String s) throws IOException
+        {
+        }
+        
+        @Override
+        public void println(String s) throws IOException
+        {
+        }
+
+     
+        @Override
+        public void setWriteListener(WriteListener writeListener)
+        {
+            
+        }
+
+        @Override
+        public boolean isReady()
+        {
+            return false;
+        }
+    };
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/DigestAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/DigestAuthenticator.java
new file mode 100644 (file)
index 0000000..13537e6
--- /dev/null
@@ -0,0 +1,421 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.BitSet;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ *
+ * The nonce max age in ms can be set with the {@link SecurityHandler#setInitParameter(String, String)}
+ * using the name "maxNonceAge".  The nonce max count can be set with {@link SecurityHandler#setInitParameter(String, String)}
+ * using the name "maxNonceCount".  When the age or count is exceeded, the nonce is considered stale.
+ */
+public class DigestAuthenticator extends LoginAuthenticator
+{
+    private static final Logger LOG = Log.getLogger(DigestAuthenticator.class);
+    SecureRandom _random = new SecureRandom();
+    private long _maxNonceAgeMs = 60*1000;
+    private int _maxNC=1024;
+    private ConcurrentMap<String, Nonce> _nonceMap = new ConcurrentHashMap<String, Nonce>();
+    private Queue<Nonce> _nonceQueue = new ConcurrentLinkedQueue<Nonce>();
+    private static class Nonce
+    {
+        final String _nonce;
+        final long _ts;
+        final BitSet _seen; 
+
+        public Nonce(String nonce, long ts, int size)
+        {
+            _nonce=nonce;
+            _ts=ts;
+            _seen = new BitSet(size);
+        }
+
+        public boolean seen(int count)
+        {
+            synchronized (this)
+            {
+                if (count>=_seen.size())
+                    return true;
+                boolean s=_seen.get(count);
+                _seen.set(count);
+                return s;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public DigestAuthenticator()
+    {
+        super();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
+     */
+    @Override
+    public void setConfiguration(AuthConfiguration configuration)
+    {
+        super.setConfiguration(configuration);
+
+        String mna=configuration.getInitParameter("maxNonceAge");
+        if (mna!=null)
+        {
+            _maxNonceAgeMs=Long.valueOf(mna);
+        }
+        String mnc=configuration.getInitParameter("maxNonceCount");
+        if (mnc!=null)
+        {
+            _maxNC=Integer.valueOf(mnc);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getMaxNonceCount()
+    {
+        return _maxNC;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMaxNonceCount(int maxNC)
+    {
+        _maxNC = maxNC;
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getMaxNonceAge()
+    {
+        return _maxNonceAgeMs;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void setMaxNonceAge(long maxNonceAgeInMillis)
+    {
+        _maxNonceAgeMs = maxNonceAgeInMillis;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getAuthMethod()
+    {
+        return Constraint.__DIGEST_AUTH;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+    
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+    {
+        if (!mandatory)
+            return new DeferredAuthentication(this);
+
+        HttpServletRequest request = (HttpServletRequest)req;
+        HttpServletResponse response = (HttpServletResponse)res;
+        String credentials = request.getHeader(HttpHeader.AUTHORIZATION.asString());
+
+        try
+        {
+            boolean stale = false;
+            if (credentials != null)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Credentials: " + credentials);
+                QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(credentials, "=, ", true, false);
+                final Digest digest = new Digest(request.getMethod());
+                String last = null;
+                String name = null;
+
+                while (tokenizer.hasMoreTokens())
+                {
+                    String tok = tokenizer.nextToken();
+                    char c = (tok.length() == 1) ? tok.charAt(0) : '\0';
+
+                    switch (c)
+                    {
+                        case '=':
+                            name = last;
+                            last = tok;
+                            break;
+                        case ',':
+                            name = null;
+                            break;
+                        case ' ':
+                            break;
+
+                        default:
+                            last = tok;
+                            if (name != null)
+                            {
+                                if ("username".equalsIgnoreCase(name))
+                                    digest.username = tok;
+                                else if ("realm".equalsIgnoreCase(name))
+                                    digest.realm = tok;
+                                else if ("nonce".equalsIgnoreCase(name))
+                                    digest.nonce = tok;
+                                else if ("nc".equalsIgnoreCase(name))
+                                    digest.nc = tok;
+                                else if ("cnonce".equalsIgnoreCase(name))
+                                    digest.cnonce = tok;
+                                else if ("qop".equalsIgnoreCase(name))
+                                    digest.qop = tok;
+                                else if ("uri".equalsIgnoreCase(name))
+                                    digest.uri = tok;
+                                else if ("response".equalsIgnoreCase(name))
+                                    digest.response = tok;
+                                name=null;
+                            }
+                    }
+                }
+
+                int n = checkNonce(digest,(Request)request);
+
+                if (n > 0)
+                {
+                    //UserIdentity user = _loginService.login(digest.username,digest);
+                    UserIdentity user = login(digest.username, digest, req);
+                    if (user!=null)
+                    {
+                        return new UserAuthentication(getAuthMethod(),user);
+                    }
+                }
+                else if (n == 0)
+                    stale = true;
+
+            }
+
+            if (!DeferredAuthentication.isDeferred(response))
+            {
+                String domain = request.getContextPath();
+                if (domain == null)
+                    domain = "/";
+                response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "Digest realm=\"" + _loginService.getName()
+                        + "\", domain=\""
+                        + domain
+                        + "\", nonce=\""
+                        + newNonce((Request)request)
+                        + "\", algorithm=MD5, qop=\"auth\","
+                        + " stale=" + stale);
+                response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+
+                return Authentication.SEND_CONTINUE;
+            }
+
+            return Authentication.UNAUTHENTICATED;
+        }
+        catch (IOException e)
+        {
+            throw new ServerAuthException(e);
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    public String newNonce(Request request)
+    {
+        Nonce nonce;
+
+        do
+        {
+            byte[] nounce = new byte[24];
+            _random.nextBytes(nounce);
+
+            nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp(),_maxNC);
+        }
+        while (_nonceMap.putIfAbsent(nonce._nonce,nonce)!=null);
+        _nonceQueue.add(nonce);
+
+        return nonce._nonce;
+    }
+
+    /**
+     * @param nstring nonce to check
+     * @param request
+     * @return -1 for a bad nonce, 0 for a stale none, 1 for a good nonce
+     */
+    /* ------------------------------------------------------------ */
+    private int checkNonce(Digest digest, Request request)
+    {
+        // firstly let's expire old nonces
+        long expired = request.getTimeStamp()-_maxNonceAgeMs;
+        Nonce nonce=_nonceQueue.peek();
+        while (nonce!=null && nonce._ts<expired)
+        {
+            _nonceQueue.remove(nonce);
+            _nonceMap.remove(nonce._nonce);
+            nonce=_nonceQueue.peek();
+        }
+
+        // Now check the requested nonce
+        try
+        {
+            nonce = _nonceMap.get(digest.nonce);
+            if (nonce==null)
+                return 0;
+
+            long count = Long.parseLong(digest.nc,16);
+            if (count>=_maxNC)
+                return 0;
+            
+            if (nonce.seen((int)count))
+                return -1;
+
+            return 1;
+        }
+        catch (Exception e)
+        {
+            LOG.ignore(e);
+        }
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class Digest extends Credential
+    {
+        private static final long serialVersionUID = -2484639019549527724L;
+        final String method;
+        String username = "";
+        String realm = "";
+        String nonce = "";
+        String nc = "";
+        String cnonce = "";
+        String qop = "";
+        String uri = "";
+        String response = "";
+
+        /* ------------------------------------------------------------ */
+        Digest(String m)
+        {
+            method = m;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public boolean check(Object credentials)
+        {
+            if (credentials instanceof char[])
+                credentials=new String((char[])credentials);
+            String password = (credentials instanceof String) ? (String) credentials : credentials.toString();
+
+            try
+            {
+                MessageDigest md = MessageDigest.getInstance("MD5");
+                byte[] ha1;
+                if (credentials instanceof Credential.MD5)
+                {
+                    // Credentials are already a MD5 digest - assume it's in
+                    // form user:realm:password (we have no way to know since
+                    // it's a digest, alright?)
+                    ha1 = ((Credential.MD5) credentials).getDigest();
+                }
+                else
+                {
+                    // calc A1 digest
+                    md.update(username.getBytes(StandardCharsets.ISO_8859_1));
+                    md.update((byte) ':');
+                    md.update(realm.getBytes(StandardCharsets.ISO_8859_1));
+                    md.update((byte) ':');
+                    md.update(password.getBytes(StandardCharsets.ISO_8859_1));
+                    ha1 = md.digest();
+                }
+                // calc A2 digest
+                md.reset();
+                md.update(method.getBytes(StandardCharsets.ISO_8859_1));
+                md.update((byte) ':');
+                md.update(uri.getBytes(StandardCharsets.ISO_8859_1));
+                byte[] ha2 = md.digest();
+
+                // calc digest
+                // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":"
+                // nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" H(A2) )
+                // <">
+                // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2)
+                // ) > <">
+
+                md.update(TypeUtil.toString(ha1, 16).getBytes(StandardCharsets.ISO_8859_1));
+                md.update((byte) ':');
+                md.update(nonce.getBytes(StandardCharsets.ISO_8859_1));
+                md.update((byte) ':');
+                md.update(nc.getBytes(StandardCharsets.ISO_8859_1));
+                md.update((byte) ':');
+                md.update(cnonce.getBytes(StandardCharsets.ISO_8859_1));
+                md.update((byte) ':');
+                md.update(qop.getBytes(StandardCharsets.ISO_8859_1));
+                md.update((byte) ':');
+                md.update(TypeUtil.toString(ha2, 16).getBytes(StandardCharsets.ISO_8859_1));
+                byte[] digest = md.digest();
+
+                // check digest
+                return (TypeUtil.toString(digest, 16).equalsIgnoreCase(response));
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+
+            return false;
+        }
+
+        @Override
+        public String toString()
+        {
+            return username + "," + response;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/FormAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/FormAuthenticator.java
new file mode 100644 (file)
index 0000000..dcfea41
--- /dev/null
@@ -0,0 +1,564 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Locale;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+
+/**
+ * FORM Authenticator.
+ *
+ * <p>This authenticator implements form authentication will use dispatchers to
+ * the login page if the {@link #__FORM_DISPATCH} init parameter is set to true.
+ * Otherwise it will redirect.</p>
+ *
+ * <p>The form authenticator redirects unauthenticated requests to a log page
+ * which should use a form to gather username/password from the user and send them
+ * to the /j_security_check URI within the context.  FormAuthentication uses
+ * {@link SessionAuthentication} to wrap Authentication results so that they
+ * are  associated with the session.</p>
+ *
+ *
+ */
+public class FormAuthenticator extends LoginAuthenticator
+{
+    private static final Logger LOG = Log.getLogger(FormAuthenticator.class);
+
+    public final static String __FORM_LOGIN_PAGE="org.eclipse.jetty.security.form_login_page";
+    public final static String __FORM_ERROR_PAGE="org.eclipse.jetty.security.form_error_page";
+    public final static String __FORM_DISPATCH="org.eclipse.jetty.security.dispatch";
+    public final static String __J_URI = "org.eclipse.jetty.security.form_URI";
+    public final static String __J_POST = "org.eclipse.jetty.security.form_POST";
+    public final static String __J_METHOD = "org.eclipse.jetty.security.form_METHOD";
+    public final static String __J_SECURITY_CHECK = "/j_security_check";
+    public final static String __J_USERNAME = "j_username";
+    public final static String __J_PASSWORD = "j_password";
+
+    private String _formErrorPage;
+    private String _formErrorPath;
+    private String _formLoginPage;
+    private String _formLoginPath;
+    private boolean _dispatch;
+    private boolean _alwaysSaveUri;
+
+    public FormAuthenticator()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public FormAuthenticator(String login,String error,boolean dispatch)
+    {
+        this();
+        if (login!=null)
+            setLoginPage(login);
+        if (error!=null)
+            setErrorPage(error);
+        _dispatch=dispatch;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * If true, uris that cause a redirect to a login page will always
+     * be remembered. If false, only the first uri that leads to a login
+     * page redirect is remembered.
+     * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=379909
+     * @param alwaysSave
+     */
+    public void setAlwaysSaveUri (boolean alwaysSave)
+    {
+        _alwaysSaveUri = alwaysSave;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public boolean getAlwaysSaveUri ()
+    {
+        return _alwaysSaveUri;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
+     */
+    @Override
+    public void setConfiguration(AuthConfiguration configuration)
+    {
+        super.setConfiguration(configuration);
+        String login=configuration.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE);
+        if (login!=null)
+            setLoginPage(login);
+        String error=configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
+        if (error!=null)
+            setErrorPage(error);
+        String dispatch=configuration.getInitParameter(FormAuthenticator.__FORM_DISPATCH);
+        _dispatch = dispatch==null?_dispatch:Boolean.valueOf(dispatch);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getAuthMethod()
+    {
+        return Constraint.__FORM_AUTH;
+    }
+
+    /* ------------------------------------------------------------ */
+    private void setLoginPage(String path)
+    {
+        if (!path.startsWith("/"))
+        {
+            LOG.warn("form-login-page must start with /");
+            path = "/" + path;
+        }
+        _formLoginPage = path;
+        _formLoginPath = path;
+        if (_formLoginPath.indexOf('?') > 0)
+            _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
+    }
+
+    /* ------------------------------------------------------------ */
+    private void setErrorPage(String path)
+    {
+        if (path == null || path.trim().length() == 0)
+        {
+            _formErrorPath = null;
+            _formErrorPage = null;
+        }
+        else
+        {
+            if (!path.startsWith("/"))
+            {
+                LOG.warn("form-error-page must start with /");
+                path = "/" + path;
+            }
+            _formErrorPage = path;
+            _formErrorPath = path;
+
+            if (_formErrorPath.indexOf('?') > 0)
+                _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public UserIdentity login(String username, Object password, ServletRequest request)
+    {
+        
+        UserIdentity user = super.login(username,password,request);
+        if (user!=null)
+        {
+            HttpSession session = ((HttpServletRequest)request).getSession(true);
+            Authentication cached=new SessionAuthentication(getAuthMethod(),user,password);
+            session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached);
+        }
+        return user;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void prepareRequest(ServletRequest request)
+    {
+        //if this is a request resulting from a redirect after auth is complete
+        //(ie its from a redirect to the original request uri) then due to 
+        //browser handling of 302 redirects, the method may not be the same as
+        //that of the original request. Replace the method and original post
+        //params (if it was a post).
+        //
+        //See Servlet Spec 3.1 sec 13.6.3
+        HttpServletRequest httpRequest = (HttpServletRequest)request;
+        HttpSession session = httpRequest.getSession(false);
+        if (session == null || session.getAttribute(SessionAuthentication.__J_AUTHENTICATED) == null)
+            return; //not authenticated yet
+        
+        String juri = (String)session.getAttribute(__J_URI);
+        if (juri == null || juri.length() == 0)
+            return; //no original uri saved
+        
+        String method = (String)session.getAttribute(__J_METHOD);
+        if (method == null || method.length() == 0)
+            return; //didn't save original request method
+       
+        StringBuffer buf = httpRequest.getRequestURL();
+        if (httpRequest.getQueryString() != null)
+            buf.append("?").append(httpRequest.getQueryString());
+        
+        if (!juri.equals(buf.toString()))
+            return; //this request is not for the same url as the original
+        
+        //restore the original request's method on this request
+        if (LOG.isDebugEnabled()) LOG.debug("Restoring original method {} for {} with method {}", method, juri,httpRequest.getMethod());
+        Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+        HttpMethod m = HttpMethod.fromString(method);
+        base_request.setMethod(m,m.asString());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+    {
+        HttpServletRequest request = (HttpServletRequest)req;
+        HttpServletResponse response = (HttpServletResponse)res;
+        String uri = request.getRequestURI();
+        if (uri==null)
+            uri=URIUtil.SLASH;
+
+        mandatory|=isJSecurityCheck(uri);
+        if (!mandatory)
+            return new DeferredAuthentication(this);
+
+        if (isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo())) &&!DeferredAuthentication.isDeferred(response))
+            return new DeferredAuthentication(this);
+
+        HttpSession session = request.getSession(true);
+
+        try
+        {
+            // Handle a request for authentication.
+            if (isJSecurityCheck(uri))
+            {
+                final String username = request.getParameter(__J_USERNAME);
+                final String password = request.getParameter(__J_PASSWORD);
+
+                UserIdentity user = login(username, password, request);
+                LOG.debug("jsecuritycheck {} {}",username,user);
+                session = request.getSession(true);
+                if (user!=null)
+                {                    
+                    // Redirect to original request
+                    String nuri;
+                    FormAuthentication form_auth;
+                    synchronized(session)
+                    {
+                        nuri = (String) session.getAttribute(__J_URI);
+
+                        if (nuri == null || nuri.length() == 0)
+                        {
+                            nuri = request.getContextPath();
+                            if (nuri.length() == 0)
+                                nuri = URIUtil.SLASH;
+                        }
+                        form_auth = new FormAuthentication(getAuthMethod(),user);
+                    }
+                    LOG.debug("authenticated {}->{}",form_auth,nuri);
+
+                    response.setContentLength(0);
+                    Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+                    Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                    int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+                    base_response.sendRedirect(redirectCode, response.encodeRedirectURL(nuri));
+                    return form_auth;
+                }
+
+                // not authenticated
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
+                if (_formErrorPage == null)
+                {
+                    LOG.debug("auth failed {}->403",username);
+                    if (response != null)
+                        response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                }
+                else if (_dispatch)
+                {
+                    LOG.debug("auth failed {}=={}",username,_formErrorPage);
+                    RequestDispatcher dispatcher = request.getRequestDispatcher(_formErrorPage);
+                    response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
+                    response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
+                    dispatcher.forward(new FormRequest(request), new FormResponse(response));
+                }
+                else
+                {
+                    LOG.debug("auth failed {}->{}",username,_formErrorPage);
+                    Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+                    Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                    int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+                    base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
+                }
+
+                return Authentication.SEND_FAILURE;
+            }
+
+            // Look for cached authentication
+            Authentication authentication = (Authentication) session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
+            if (authentication != null)
+            {
+                // Has authentication been revoked?
+                if (authentication instanceof Authentication.User &&
+                    _loginService!=null &&
+                    !_loginService.validate(((Authentication.User)authentication).getUserIdentity()))
+                {
+                    LOG.debug("auth revoked {}",authentication);
+                    session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
+                }
+                else
+                {
+                    synchronized (session)
+                    {
+                        String j_uri=(String)session.getAttribute(__J_URI);
+                        if (j_uri!=null)
+                        {
+                            //check if the request is for the same url as the original and restore
+                            //params if it was a post
+                            LOG.debug("auth retry {}->{}",authentication,j_uri);
+                            StringBuffer buf = request.getRequestURL();
+                            if (request.getQueryString() != null)
+                                buf.append("?").append(request.getQueryString());
+
+                            if (j_uri.equals(buf.toString()))
+                            {
+                                MultiMap<String> j_post = (MultiMap<String>)session.getAttribute(__J_POST);
+                                if (j_post!=null)
+                                {
+                                    LOG.debug("auth rePOST {}->{}",authentication,j_uri);
+                                    Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                                    base_request.setContentParameters(j_post);
+                                }
+                                session.removeAttribute(__J_URI);
+                                session.removeAttribute(__J_METHOD);
+                                session.removeAttribute(__J_POST);
+                            }
+                        }
+                    }
+                    LOG.debug("auth {}",authentication);
+                    return authentication;
+                }
+            }
+
+            // if we can't send challenge
+            if (DeferredAuthentication.isDeferred(response))
+            {
+                LOG.debug("auth deferred {}",session.getId());
+                return Authentication.UNAUTHENTICATED;
+            }
+
+            // remember the current URI
+            synchronized (session)
+            {
+                // But only if it is not set already, or we save every uri that leads to a login form redirect
+                if (session.getAttribute(__J_URI)==null || _alwaysSaveUri)
+                {
+                    StringBuffer buf = request.getRequestURL();
+                    if (request.getQueryString() != null)
+                        buf.append("?").append(request.getQueryString());
+                    session.setAttribute(__J_URI, buf.toString());
+                    session.setAttribute(__J_METHOD, request.getMethod());
+
+                    if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod()))
+                    {
+                        Request base_request = (req instanceof Request)?(Request)req:HttpChannel.getCurrentHttpChannel().getRequest();
+                        MultiMap<String> formParameters = new MultiMap<>();
+                        base_request.extractFormParameters(formParameters);
+                        session.setAttribute(__J_POST, formParameters);
+                    }
+                }
+            }
+
+            // send the the challenge
+            if (_dispatch)
+            {
+                LOG.debug("challenge {}=={}",session.getId(),_formLoginPage);
+                RequestDispatcher dispatcher = request.getRequestDispatcher(_formLoginPage);
+                response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
+                response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
+                dispatcher.forward(new FormRequest(request), new FormResponse(response));
+            }
+            else
+            {
+                LOG.debug("challenge {}->{}",session.getId(),_formLoginPage);
+                Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+                Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+                base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formLoginPage)));
+            }
+            return Authentication.SEND_CONTINUE;
+        }
+        catch (IOException | ServletException e)
+        {
+            throw new ServerAuthException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isJSecurityCheck(String uri)
+    {
+        int jsc = uri.indexOf(__J_SECURITY_CHECK);
+
+        if (jsc<0)
+            return false;
+        int e=jsc+__J_SECURITY_CHECK.length();
+        if (e==uri.length())
+            return true;
+        char c = uri.charAt(e);
+        return c==';'||c=='#'||c=='/'||c=='?';
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isLoginOrErrorPage(String pathInContext)
+    {
+        return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected static class FormRequest extends HttpServletRequestWrapper
+    {
+        public FormRequest(HttpServletRequest request)
+        {
+            super(request);
+        }
+
+        @Override
+        public long getDateHeader(String name)
+        {
+            if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+                return -1;
+            return super.getDateHeader(name);
+        }
+
+        @Override
+        public String getHeader(String name)
+        {
+            if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+                return null;
+            return super.getHeader(name);
+        }
+
+        @Override
+        public Enumeration<String> getHeaderNames()
+        {
+            return Collections.enumeration(Collections.list(super.getHeaderNames()));
+        }
+
+        @Override
+        public Enumeration<String> getHeaders(String name)
+        {
+            if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+                return Collections.<String>enumeration(Collections.<String>emptyList());
+            return super.getHeaders(name);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected static class FormResponse extends HttpServletResponseWrapper
+    {
+        public FormResponse(HttpServletResponse response)
+        {
+            super(response);
+        }
+
+        @Override
+        public void addDateHeader(String name, long date)
+        {
+            if (notIgnored(name))
+                super.addDateHeader(name,date);
+        }
+
+        @Override
+        public void addHeader(String name, String value)
+        {
+            if (notIgnored(name))
+                super.addHeader(name,value);
+        }
+
+        @Override
+        public void setDateHeader(String name, long date)
+        {
+            if (notIgnored(name))
+                super.setDateHeader(name,date);
+        }
+
+        @Override
+        public void setHeader(String name, String value)
+        {
+            if (notIgnored(name))
+                super.setHeader(name,value);
+        }
+
+        private boolean notIgnored(String name)
+        {
+            if (HttpHeader.CACHE_CONTROL.is(name) ||
+                HttpHeader.PRAGMA.is(name) ||
+                HttpHeader.ETAG.is(name) ||
+                HttpHeader.EXPIRES.is(name) ||
+                HttpHeader.LAST_MODIFIED.is(name) ||
+                HttpHeader.AGE.is(name))
+                return false;
+            return true;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** This Authentication represents a just completed Form authentication.
+     * Subsequent requests from the same user are authenticated by the presents
+     * of a {@link SessionAuthentication} instance in their session.
+     */
+    public static class FormAuthentication extends UserAuthentication implements Authentication.ResponseSent
+    {
+        public FormAuthentication(String method, UserIdentity userIdentity)
+        {
+            super(method,userIdentity);
+        }
+
+        @Override
+        public String toString()
+        {
+            return "Form"+super.toString();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/LoginAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/LoginAuthenticator.java
new file mode 100644 (file)
index 0000000..9a1940d
--- /dev/null
@@ -0,0 +1,133 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.security.Authenticator;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class LoginAuthenticator implements Authenticator
+{
+    private static final Logger LOG = Log.getLogger(LoginAuthenticator.class);
+
+    protected LoginService _loginService;
+    protected IdentityService _identityService;
+    private boolean _renewSession;
+    
+    
+    /* ------------------------------------------------------------ */
+    protected LoginAuthenticator()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void prepareRequest(ServletRequest request)
+    {
+        //empty implementation as the default
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity login(String username, Object password, ServletRequest request)
+    {
+        UserIdentity user = _loginService.login(username,password);
+        if (user!=null)
+        {
+            renewSession((HttpServletRequest)request, (request instanceof Request? ((Request)request).getResponse() : null));
+            return user;
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setConfiguration(AuthConfiguration configuration)
+    {
+        _loginService=configuration.getLoginService();
+        if (_loginService==null)
+            throw new IllegalStateException("No LoginService for "+this+" in "+configuration);
+        _identityService=configuration.getIdentityService();
+        if (_identityService==null)
+            throw new IllegalStateException("No IdentityService for "+this+" in "+configuration);
+        _renewSession=configuration.isSessionRenewedOnAuthentication();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public LoginService getLoginService()
+    {
+        return _loginService;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Change the session id.
+     * The session is changed to a new instance with a new ID if and only if:<ul>
+     * <li>A session exists.
+     * <li>The {@link AuthConfiguration#isSessionRenewedOnAuthentication()} returns true.
+     * <li>The session ID has been given to unauthenticated responses
+     * </ul>
+     * @param request
+     * @param response
+     * @return The new session.
+     */
+    protected HttpSession renewSession(HttpServletRequest request, HttpServletResponse response)
+    {
+        HttpSession httpSession = request.getSession(false);
+
+        if (_renewSession && httpSession!=null)
+        {
+            synchronized (httpSession)
+            {
+                //if we should renew sessions, and there is an existing session that may have been seen by non-authenticated users
+                //(indicated by SESSION_SECURED not being set on the session) then we should change id
+                if (httpSession.getAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED)!=Boolean.TRUE)
+                {
+                    if (httpSession instanceof AbstractSession)
+                    {
+                        AbstractSession abstractSession = (AbstractSession)httpSession;
+                        String oldId = abstractSession.getId();
+                        abstractSession.renewId(request);
+                        abstractSession.setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+                        if (abstractSession.isIdChanged() && response != null && (response instanceof Response))
+                            ((Response)response).addCookie(abstractSession.getSessionManager().getSessionCookie(abstractSession, request.getContextPath(), request.isSecure()));
+                        LOG.debug("renew {}->{}",oldId,abstractSession.getId());
+                    }
+                    else
+                        LOG.warn("Unable to renew session "+httpSession);
+                    
+                    return httpSession;
+                }
+            }
+        }
+        return httpSession;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallback.java b/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallback.java
new file mode 100644 (file)
index 0000000..e123b22
--- /dev/null
@@ -0,0 +1,55 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+
+/**
+ * This is similar to the jaspi PasswordValidationCallback but includes user
+ * principal and group info as well.
+ *
+ * @version $Rev: 4792 $ $Date: 2009-03-18 22:55:52 +0100 (Wed, 18 Mar 2009) $
+ */
+public interface LoginCallback
+{
+    public Subject getSubject();
+
+    public String getUserName();
+
+    public Object getCredential();
+
+    public boolean isSuccess();
+
+    public void setSuccess(boolean success);
+
+    public Principal getUserPrincipal();
+
+    public void setUserPrincipal(Principal userPrincipal);
+
+    public String[] getRoles();
+
+    public void setRoles(String[] roles);
+
+    public void clearPassword();
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java b/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java
new file mode 100644 (file)
index 0000000..5939caf
--- /dev/null
@@ -0,0 +1,109 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.security.IdentityService;
+
+/**
+ * This is similar to the jaspi PasswordValidationCallback but includes user
+ * principal and group info as well.
+ *
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class LoginCallbackImpl implements LoginCallback
+{
+    // initial data
+    private final Subject subject;
+
+    private final String userName;
+
+    private Object credential;
+
+    private boolean success;
+
+    private Principal userPrincipal;
+
+    private String[] roles = IdentityService.NO_ROLES;
+
+    //TODO could use Credential instance instead of Object if Basic/Form create a Password object
+    public LoginCallbackImpl (Subject subject, String userName, Object credential)
+    {
+        this.subject = subject;
+        this.userName = userName;
+        this.credential = credential;
+    }
+
+    public Subject getSubject()
+    {
+        return subject;
+    }
+
+    public String getUserName()
+    {
+        return userName;
+    }
+
+    public Object getCredential()
+    {
+        return credential;
+    }
+
+    public boolean isSuccess()
+    {
+        return success;
+    }
+
+    public void setSuccess(boolean success)
+    {
+        this.success = success;
+    }
+
+    public Principal getUserPrincipal()
+    {
+        return userPrincipal;
+    }
+
+    public void setUserPrincipal(Principal userPrincipal)
+    {
+        this.userPrincipal = userPrincipal;
+    }
+
+    public String[] getRoles()
+    {
+        return roles;
+    }
+
+    public void setRoles(String[] groups)
+    {
+        this.roles = groups;
+    }
+
+    public void clearPassword()
+    {
+        if (credential != null)
+        {
+            credential = null;
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/SessionAuthentication.java b/lib/jetty/org/eclipse/jetty/security/authentication/SessionAuthentication.java
new file mode 100644 (file)
index 0000000..e3be584
--- /dev/null
@@ -0,0 +1,131 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionEvent;
+
+import org.eclipse.jetty.security.AbstractUserAuthentication;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class SessionAuthentication extends AbstractUserAuthentication implements Serializable, HttpSessionActivationListener, HttpSessionBindingListener
+{
+    private static final Logger LOG = Log.getLogger(SessionAuthentication.class);
+
+    private static final long serialVersionUID = -4643200685888258706L;
+
+
+
+    public final static String __J_AUTHENTICATED="org.eclipse.jetty.security.UserIdentity";
+
+    private final String _name;
+    private final Object _credentials;
+    private transient HttpSession _session;
+
+    public SessionAuthentication(String method, UserIdentity userIdentity, Object credentials)
+    {
+        super(method, userIdentity);
+        _name=userIdentity.getUserPrincipal().getName();
+        _credentials=credentials;
+    }
+
+
+    private void readObject(ObjectInputStream stream)
+        throws IOException, ClassNotFoundException
+    {
+        stream.defaultReadObject();
+
+        SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+        if (security==null)
+            throw new IllegalStateException("!SecurityHandler");
+        LoginService login_service=security.getLoginService();
+        if (login_service==null)
+            throw new IllegalStateException("!LoginService");
+
+        _userIdentity=login_service.login(_name,_credentials);
+        LOG.debug("Deserialized and relogged in {}",this);
+    }
+
+    public void logout()
+    {
+        if (_session!=null && _session.getAttribute(__J_AUTHENTICATED)!=null)
+            _session.removeAttribute(__J_AUTHENTICATED);
+
+        doLogout();
+    }
+
+    private void doLogout()
+    {
+        SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+        if (security!=null)
+            security.logout(this);
+        if (_session!=null)
+            _session.removeAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s,%s}",this.getClass().getSimpleName(),hashCode(),_session==null?"-":_session.getId(),_userIdentity);
+    }
+
+    @Override
+    public void sessionWillPassivate(HttpSessionEvent se)
+    {
+       
+    }
+
+    @Override
+    public void sessionDidActivate(HttpSessionEvent se)
+    {
+        if (_session==null)
+        {
+            _session=se.getSession();
+        }
+    }
+
+    @Override
+    public void valueBound(HttpSessionBindingEvent event)
+    {
+        if (_session==null)
+        {
+            _session=event.getSession();
+        }
+    }
+
+    @Override
+    public void valueUnbound(HttpSessionBindingEvent event)
+    {
+        doLogout();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java
new file mode 100644 (file)
index 0000000..8469c0a
--- /dev/null
@@ -0,0 +1,116 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+
+public class SpnegoAuthenticator extends LoginAuthenticator
+{
+    private static final Logger LOG = Log.getLogger(SpnegoAuthenticator.class);
+    private String _authMethod = Constraint.__SPNEGO_AUTH;
+
+    public SpnegoAuthenticator()
+    {
+    }
+
+    /**
+     * Allow for a custom authMethod value to be set for instances where SPENGO may not be appropriate
+     * @param authMethod
+     */
+    public SpnegoAuthenticator( String authMethod )
+    {
+       _authMethod = authMethod;
+    }
+
+    @Override
+    public String getAuthMethod()
+    {
+        return _authMethod;
+    }
+
+    @Override
+    public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException
+    {
+        HttpServletRequest req = (HttpServletRequest)request;
+        HttpServletResponse res = (HttpServletResponse)response;
+
+        String header = req.getHeader(HttpHeader.AUTHORIZATION.asString());
+
+        if (!mandatory)
+        {
+            return new DeferredAuthentication(this);
+        }
+
+        // check to see if we have authorization headers required to continue
+        if ( header == null )
+        {
+            try
+            {
+                if (DeferredAuthentication.isDeferred(res))
+                {
+                     return Authentication.UNAUTHENTICATED;
+                }
+
+                LOG.debug("SpengoAuthenticator: sending challenge");
+                res.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), HttpHeader.NEGOTIATE.asString());
+                res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+                return Authentication.SEND_CONTINUE;
+            }
+            catch (IOException ioe)
+            {
+                throw new ServerAuthException(ioe);
+            }
+        }
+        else if (header != null && header.startsWith(HttpHeader.NEGOTIATE.asString()))
+        {
+            String spnegoToken = header.substring(10);
+
+            UserIdentity user = login(null,spnegoToken, request);
+
+            if ( user != null )
+            {
+                return new UserAuthentication(getAuthMethod(),user);
+            }
+        }
+
+        return Authentication.UNAUTHENTICATED;
+    }
+
+    @Override
+    public boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/package-info.java b/lib/jetty/org/eclipse/jetty/security/authentication/package-info.java
new file mode 100644 (file)
index 0000000..0e0c617
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Security : Authenticators and Callbacks
+ */
+package org.eclipse.jetty.security.authentication;
+
diff --git a/lib/jetty/org/eclipse/jetty/security/package-info.java b/lib/jetty/org/eclipse/jetty/security/package-info.java
new file mode 100644 (file)
index 0000000..edb5ea4
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Security : Modular Support for Security in Jetty
+ */
+package org.eclipse.jetty.security;
+
diff --git a/lib/jetty/org/eclipse/jetty/server/AbstractConnectionFactory.java b/lib/jetty/org/eclipse/jetty/server/AbstractConnectionFactory.java
new file mode 100644 (file)
index 0000000..96fb3a2
--- /dev/null
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public abstract class AbstractConnectionFactory extends ContainerLifeCycle implements ConnectionFactory
+{
+    private final String _protocol;
+    private int _inputbufferSize = 8192;
+
+    protected AbstractConnectionFactory(String protocol)
+    {
+        _protocol=protocol;
+    }
+
+    @Override
+    public String getProtocol()
+    {
+        return _protocol;
+    }
+
+    public int getInputBufferSize()
+    {
+        return _inputbufferSize;
+    }
+
+    public void setInputBufferSize(int size)
+    {
+        _inputbufferSize=size;
+    }
+
+    protected AbstractConnection configure(AbstractConnection connection, Connector connector, EndPoint endPoint)
+    {
+        connection.setInputBufferSize(getInputBufferSize());
+
+        if (connector instanceof ContainerLifeCycle)
+        {
+            ContainerLifeCycle aggregate = (ContainerLifeCycle)connector;
+            for (Connection.Listener listener : aggregate.getBeans(Connection.Listener.class))
+                connection.addListener(listener);
+        }
+        return connection;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),getProtocol());
+    }
+
+    public static ConnectionFactory[] getFactories(SslContextFactory sslContextFactory, ConnectionFactory... factories)
+    {
+        factories=ArrayUtil.removeNulls(factories);
+
+        if (sslContextFactory==null)
+            return factories;
+
+        for (ConnectionFactory factory : factories)
+        {
+            if (factory instanceof HttpConfiguration.ConnectionFactory)
+            {
+                HttpConfiguration config = ((HttpConfiguration.ConnectionFactory)factory).getHttpConfiguration();
+                if (config.getCustomizer(SecureRequestCustomizer.class)==null)
+                    config.addCustomizer(new SecureRequestCustomizer());
+            }
+        }
+        return ArrayUtil.prependToArray(new SslConnectionFactory(sslContextFactory,factories[0].getProtocol()),factories,ConnectionFactory.class);
+
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AbstractConnector.java b/lib/jetty/org/eclipse/jetty/server/AbstractConnector.java
new file mode 100644 (file)
index 0000000..7d3402f
--- /dev/null
@@ -0,0 +1,564 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.ArrayByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * <p>An abstract implementation of {@link Connector} that provides a {@link ConnectionFactory} mechanism
+ * for creating {@link Connection} instances for various protocols (HTTP, SSL, SPDY, etc).</p>
+ *
+ * <h2>Connector Services</h2>
+ * The abstract connector manages the dependent services needed by all specific connector instances:
+ * <ul>
+ * <li>The {@link Executor} service is used to run all active tasks needed by this connector such as accepting connections
+ * or handle HTTP requests. The default is to use the {@link Server#getThreadPool()} as an executor.
+ * </li>
+ * <li>The {@link Scheduler} service is used to monitor the idle timeouts of all connections and is also made available
+ * to the connections to time such things as asynchronous request timeouts.  The default is to use a new
+ * {@link ScheduledExecutorScheduler} instance.
+ * </li>
+ * <li>The {@link ByteBufferPool} service is made available to all connections to be used to acquire and release
+ * {@link ByteBuffer} instances from a pool.  The default is to use a new {@link ArrayByteBufferPool} instance.
+ * </li>
+ * </ul>
+ * These services are managed as aggregate beans by the {@link ContainerLifeCycle} super class and
+ * may either be managed or unmanaged beans.
+ *
+ * <h2>Connection Factories</h2>
+ * The connector keeps a collection of {@link ConnectionFactory} instances, each of which are known by their
+ * protocol name.  The protocol name may be a real protocol (eg http/1.1 or spdy/3) or it may be a private name
+ * that represents a special connection factory. For example, the name "SSL-http/1.1" is used for
+ * an {@link SslConnectionFactory} that has been instantiated with the {@link HttpConnectionFactory} as it's
+ * next protocol.
+ *
+ * <h4>Configuring Connection Factories</h4>
+ * The collection of available {@link ConnectionFactory} may be constructor injected or modified with the
+ * methods {@link #addConnectionFactory(ConnectionFactory)}, {@link #removeConnectionFactory(String)} and
+ * {@link #setConnectionFactories(Collection)}.  Only a single {@link ConnectionFactory} instance may be configured
+ * per protocol name, so if two factories with the same {@link ConnectionFactory#getProtocol()} are set, then
+ * the second will replace the first.
+ * <p>
+ * The protocol factory used for newly accepted connections is specified by
+ * the method {@link #setDefaultProtocol(String)} or defaults to the protocol of the first configured factory.
+ * <p>
+ * Each Connection factory type is responsible for the configuration of the protocols that it accepts. Thus to
+ * configure the HTTP protocol, you pass a {@link HttpConfiguration} instance to the {@link HttpConnectionFactory}
+ * (or the SPDY factories that can also provide HTTP Semantics).  Similarly the {@link SslConnectionFactory} is
+ * configured by passing it a {@link SslContextFactory} and a next protocol name.
+ *
+ * <h4>Connection Factory Operation</h4>
+ * {@link ConnectionFactory}s may simply create a {@link Connection} instance to support a specific
+ * protocol.  For example, the {@link HttpConnectionFactory} will create a {@link HttpConnection} instance
+ * that can handle http/1.1, http/1.0 and http/0.9.
+ * <p>
+ * {@link ConnectionFactory}s may also create a chain of {@link Connection} instances, using other {@link ConnectionFactory} instances.
+ * For example, the {@link SslConnectionFactory} is configured with a next protocol name, so that once it has accepted
+ * a connection and created an {@link SslConnection}, it then used the next {@link ConnectionFactory} from the
+ * connector using the {@link #getConnectionFactory(String)} method, to create a {@link Connection} instance that
+ * will handle the unecrypted bytes from the {@link SslConnection}.   If the next protocol is "http/1.1", then the
+ * {@link SslConnectionFactory} will have a protocol name of "SSL-http/1.1" and lookup "http/1.1" for the protocol
+ * to run over the SSL connection.
+ * <p>
+ * {@link ConnectionFactory}s may also create temporary {@link Connection} instances that will exchange bytes
+ * over the connection to determine what is the next protocol to use.  For example the NPN protocol is an extension
+ * of SSL to allow a protocol to be specified during the SSL handshake. NPN is used by the SPDY protocol to
+ * negotiate the version of SPDY or HTTP that the client and server will speak.  Thus to accept a SPDY connection, the
+ * connector will be configured with {@link ConnectionFactory}s for "SSL-NPN", "NPN", "spdy/3", "spdy/2", "http/1.1"
+ * with the default protocol being "SSL-NPN".  Thus a newly accepted connection uses "SSL-NPN", which specifies a
+ * SSLConnectionFactory with "NPN" as the next protocol.  Thus an SslConnection instance is created chained to an NPNConnection
+ * instance.  The NPN connection then negotiates with the client to determined the next protocol, which could be
+ * "spdy/3", "spdy/2" or the default of "http/1.1".  Once the next protocol is determined, the NPN connection
+ * calls {@link #getConnectionFactory(String)} to create a connection instance that will replace the NPN connection as
+ * the connection chained to the SSLConnection.
+ * <p>
+ * <h2>Acceptors</h2>
+ * The connector will execute a number of acceptor tasks to the {@link Exception} service passed to the constructor.
+ * The acceptor tasks run in a loop while the connector is running and repeatedly call the abstract {@link #accept(int)} method.
+ * The implementation of the accept method must:
+ * <nl>
+ * <li>block waiting for new connections
+ * <li>accept the connection (eg socket accept)
+ * <li>perform any configuration of the connection (eg. socket linger times)
+ * <li>call the {@link #getDefaultConnectionFactory()} {@link ConnectionFactory#newConnection(Connector, org.eclipse.jetty.io.EndPoint)}
+ * method to create a new Connection instance.
+ * </nl>
+ * The default number of acceptor tasks is the minimum of 1 and half the number of available CPUs. Having more acceptors may reduce
+ * the latency for servers that see a high rate of new connections (eg HTTP/1.0 without keep-alive).  Typically the default is
+ * sufficient for modern persistent protocols (HTTP/1.1, SPDY etc.)
+ */
+@ManagedObject("Abstract implementation of the Connector Interface")
+public abstract class AbstractConnector extends ContainerLifeCycle implements Connector, Dumpable
+{
+    protected final Logger LOG = Log.getLogger(getClass());
+    // Order is important on server side, so we use a LinkedHashMap
+    private final Map<String, ConnectionFactory> _factories = new LinkedHashMap<>();
+    private final Server _server;
+    private final Executor _executor;
+    private final Scheduler _scheduler;
+    private final ByteBufferPool _byteBufferPool;
+    private final Thread[] _acceptors;
+    private final Set<EndPoint> _endpoints = Collections.newSetFromMap(new ConcurrentHashMap());
+    private final Set<EndPoint> _immutableEndPoints = Collections.unmodifiableSet(_endpoints);
+    private volatile CountDownLatch _stopping;
+    private long _idleTimeout = 30000;
+    private String _defaultProtocol;
+    private ConnectionFactory _defaultConnectionFactory;
+    private String _name;
+
+
+    /**
+     * @param server The server this connector will be added to. Must not be null.
+     * @param executor An executor for this connector or null to use the servers executor
+     * @param scheduler A scheduler for this connector or null to either a {@link Scheduler} set as a server bean or if none set, then a new {@link ScheduledExecutorScheduler} instance.
+     * @param pool A buffer pool for this connector or null to either a {@link ByteBufferPool} set as a server bean or none set, the new  {@link ArrayByteBufferPool} instance.
+     * @param acceptors the number of acceptor threads to use, or 0 for a default value.
+     * @param factories The Connection Factories to use.
+     */
+    public AbstractConnector(
+            Server server,
+            Executor executor,
+            Scheduler scheduler,
+            ByteBufferPool pool,
+            int acceptors,
+            ConnectionFactory... factories)
+    {
+        _server=server;
+        _executor=executor!=null?executor:_server.getThreadPool();
+        if (scheduler==null)
+            scheduler=_server.getBean(Scheduler.class);
+        _scheduler=scheduler!=null?scheduler:new ScheduledExecutorScheduler();
+        if (pool==null)
+            pool=_server.getBean(ByteBufferPool.class);
+        _byteBufferPool = pool!=null?pool:new ArrayByteBufferPool();
+
+        addBean(_server,false);
+        addBean(_executor);
+        if (executor==null)
+            unmanage(_executor); // inherited from server
+        addBean(_scheduler);
+        addBean(_byteBufferPool);
+
+        for (ConnectionFactory factory:factories)
+            addConnectionFactory(factory);
+
+        int cores = Runtime.getRuntime().availableProcessors();
+        if (acceptors < 0)
+            acceptors = 1 + cores / 16;
+        if (acceptors > 2 * cores)
+            LOG.warn("Acceptors should be <= 2*availableProcessors: " + this);
+        _acceptors = new Thread[acceptors];
+    }
+
+
+    @Override
+    public Server getServer()
+    {
+        return _server;
+    }
+
+    @Override
+    public Executor getExecutor()
+    {
+        return _executor;
+    }
+
+    @Override
+    public ByteBufferPool getByteBufferPool()
+    {
+        return _byteBufferPool;
+    }
+
+    @Override
+    @ManagedAttribute("Idle timeout")
+    public long getIdleTimeout()
+    {
+        return _idleTimeout;
+    }
+
+    /**
+     * <p>Sets the maximum Idle time for a connection, which roughly translates to the {@link Socket#setSoTimeout(int)}
+     * call, although with NIO implementations other mechanisms may be used to implement the timeout.</p>
+     * <p>The max idle time is applied:</p>
+     * <ul>
+     * <li>When waiting for a new message to be received on a connection</li>
+     * <li>When waiting for a new message to be sent on a connection</li>
+     * </ul>
+     * <p>This value is interpreted as the maximum time between some progress being made on the connection.
+     * So if a single byte is read or written, then the timeout is reset.</p>
+     *
+     * @param idleTimeout the idle timeout
+     */
+    public void setIdleTimeout(long idleTimeout)
+    {
+        _idleTimeout = idleTimeout;
+    }
+
+    /**
+     * @return Returns the number of acceptor threads.
+     */
+    @ManagedAttribute("number of acceptor threads")
+    public int getAcceptors()
+    {
+        return _acceptors.length;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        _defaultConnectionFactory = getConnectionFactory(_defaultProtocol);
+        if(_defaultConnectionFactory==null)
+            throw new IllegalStateException("No protocol factory for default protocol: "+_defaultProtocol);
+
+        super.doStart();
+
+        _stopping=new CountDownLatch(_acceptors.length);
+        for (int i = 0; i < _acceptors.length; i++)
+            getExecutor().execute(new Acceptor(i));
+
+        LOG.info("Started {}", this);
+    }
+
+
+    protected void interruptAcceptors()
+    {
+        synchronized (this)
+        {
+            for (Thread thread : _acceptors)
+            {
+                if (thread != null)
+                    thread.interrupt();
+            }
+        }
+    }
+
+    @Override
+    public Future<Void> shutdown()
+    {
+        return new FutureCallback(true);
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        // Tell the acceptors we are stopping
+        interruptAcceptors();
+
+        // If we have a stop timeout
+        long stopTimeout = getStopTimeout();
+        CountDownLatch stopping=_stopping;
+        if (stopTimeout > 0 && stopping!=null)
+            stopping.await(stopTimeout,TimeUnit.MILLISECONDS);
+        _stopping=null;
+
+        super.doStop();
+
+        LOG.info("Stopped {}", this);
+    }
+
+    public void join() throws InterruptedException
+    {
+        join(0);
+    }
+
+    public void join(long timeout) throws InterruptedException
+    {
+        synchronized (this)
+        {
+            for (Thread thread : _acceptors)
+                if (thread != null)
+                    thread.join(timeout);
+        }
+    }
+
+    protected abstract void accept(int acceptorID) throws IOException, InterruptedException;
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Is the connector accepting new connections
+     */
+    protected boolean isAccepting()
+    {
+        return isRunning();
+    }
+
+    @Override
+    public ConnectionFactory getConnectionFactory(String protocol)
+    {
+        synchronized (_factories)
+        {
+            return _factories.get(protocol.toLowerCase(Locale.ENGLISH));
+        }
+    }
+
+    @Override
+    public <T> T getConnectionFactory(Class<T> factoryType)
+    {
+        synchronized (_factories)
+        {
+            for (ConnectionFactory f : _factories.values())
+                if (factoryType.isAssignableFrom(f.getClass()))
+                    return (T)f;
+            return null;
+        }
+    }
+
+    public void addConnectionFactory(ConnectionFactory factory)
+    {
+        synchronized (_factories)
+        {
+            ConnectionFactory old=_factories.remove(factory.getProtocol());
+            if (old!=null)
+                removeBean(old);
+            _factories.put(factory.getProtocol().toLowerCase(Locale.ENGLISH), factory);
+            addBean(factory);
+            if (_defaultProtocol==null)
+                _defaultProtocol=factory.getProtocol();
+        }
+    }
+
+    public ConnectionFactory removeConnectionFactory(String protocol)
+    {
+        synchronized (_factories)
+        {
+            ConnectionFactory factory= _factories.remove(protocol.toLowerCase(Locale.ENGLISH));
+            removeBean(factory);
+            return factory;
+        }
+    }
+
+    @Override
+    public Collection<ConnectionFactory> getConnectionFactories()
+    {
+        synchronized (_factories)
+        {
+            return _factories.values();
+        }
+    }
+
+    public void setConnectionFactories(Collection<ConnectionFactory> factories)
+    {
+        synchronized (_factories)
+        {
+            List<ConnectionFactory> existing = new ArrayList<>(_factories.values());
+            for (ConnectionFactory factory: existing)
+                removeConnectionFactory(factory.getProtocol());
+            for (ConnectionFactory factory: factories)
+                if (factory!=null)
+                    addConnectionFactory(factory);
+        }
+    }
+
+
+    @Override
+    @ManagedAttribute("Protocols supported by this connector")
+    public List<String> getProtocols()
+    {
+        synchronized (_factories)
+        {
+            return new ArrayList<>(_factories.keySet());
+        }
+    }
+
+    public void clearConnectionFactories()
+    {
+        synchronized (_factories)
+        {
+            _factories.clear();
+        }
+    }
+
+    @ManagedAttribute("This connector's default protocol")
+    public String getDefaultProtocol()
+    {
+        return _defaultProtocol;
+    }
+
+    public void setDefaultProtocol(String defaultProtocol)
+    {
+        _defaultProtocol = defaultProtocol.toLowerCase(Locale.ENGLISH);
+        if (isRunning())
+            _defaultConnectionFactory=getConnectionFactory(_defaultProtocol);
+    }
+
+    @Override
+    public ConnectionFactory getDefaultConnectionFactory()
+    {
+        if (isStarted())
+            return _defaultConnectionFactory;
+        return getConnectionFactory(_defaultProtocol);
+    }
+
+    private class Acceptor implements Runnable
+    {
+        private final int _acceptor;
+
+        private Acceptor(int id)
+        {
+            _acceptor = id;
+        }
+
+        @Override
+        public void run()
+        {
+            Thread current = Thread.currentThread();
+            String name = current.getName();
+            current.setName(name + "-acceptor-" + _acceptor + "-" + AbstractConnector.this);
+
+            synchronized (AbstractConnector.this)
+            {
+                _acceptors[_acceptor] = current;
+            }
+
+            try
+            {
+                while (isAccepting())
+                {
+                    try
+                    {
+                        accept(_acceptor);
+                    }
+                    catch (Throwable e)
+                    {
+                        if (isAccepting())
+                            LOG.warn(e);
+                        else
+                            LOG.ignore(e);
+                    }
+                }
+            }
+            finally
+            {
+                current.setName(name);
+
+                synchronized (AbstractConnector.this)
+                {
+                    _acceptors[_acceptor] = null;
+                }
+                CountDownLatch stopping=_stopping;
+                if (stopping!=null)
+                    stopping.countDown();
+            }
+        }
+    }
+
+
+
+
+//    protected void connectionOpened(Connection connection)
+//    {
+//        _stats.connectionOpened();
+//        connection.onOpen();
+//    }
+//
+//    protected void connectionClosed(Connection connection)
+//    {
+//        connection.onClose();
+//        long duration = System.currentTimeMillis() - connection.getEndPoint().getCreatedTimeStamp();
+//        _stats.connectionClosed(duration, connection.getMessagesIn(), connection.getMessagesOut());
+//    }
+//
+//    public void connectionUpgraded(Connection oldConnection, Connection newConnection)
+//    {
+//        oldConnection.onClose();
+//        _stats.connectionUpgraded(oldConnection.getMessagesIn(), oldConnection.getMessagesOut());
+//        newConnection.onOpen();
+//    }
+
+    @Override
+    public Collection<EndPoint> getConnectedEndPoints()
+    {
+        return _immutableEndPoints;
+    }
+
+    protected void onEndPointOpened(EndPoint endp)
+    {
+        _endpoints.add(endp);
+    }
+
+    protected void onEndPointClosed(EndPoint endp)
+    {
+        _endpoints.remove(endp);
+    }
+
+    @Override
+    public Scheduler getScheduler()
+    {
+        return _scheduler;
+    }
+
+    @Override
+    public String getName()
+    {
+        return _name;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Set a connector name.   A context may be configured with
+     * virtual hosts in the form "@contextname" and will only serve
+     * requests from the named connector,
+     * @param name A connector name.
+     */
+    public void setName(String name)
+    {
+        _name=name;
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}",
+                _name==null?getClass().getSimpleName():_name,
+                hashCode(),
+                getDefaultProtocol());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AbstractNCSARequestLog.java b/lib/jetty/org/eclipse/jetty/server/AbstractNCSARequestLog.java
new file mode 100644 (file)
index 0000000..dcaecbe
--- /dev/null
@@ -0,0 +1,483 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.servlet.http.Cookie;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.handler.StatisticsHandler;
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Base implementation of the {@link RequestLog} outputs logs in the pseudo-standard NCSA common log format.
+ * Configuration options allow a choice between the standard Common Log Format (as used in the 3 log format) and the
+ * Combined Log Format (single log format). This log format can be output by most web servers, and almost all web log
+ * analysis software can understand these formats.
+ */
+public abstract class AbstractNCSARequestLog extends AbstractLifeCycle implements RequestLog
+{
+    protected static final Logger LOG = Log.getLogger(AbstractNCSARequestLog.class);
+
+    private static ThreadLocal<StringBuilder> _buffers = new ThreadLocal<StringBuilder>()
+    {
+        @Override
+        protected StringBuilder initialValue()
+        {
+            return new StringBuilder(256);
+        }
+    };
+
+
+    private String[] _ignorePaths;
+    private boolean _extended;
+    private transient PathMap<String> _ignorePathMap;
+    private boolean _logLatency = false;
+    private boolean _logCookies = false;
+    private boolean _logServer = false;
+    private boolean _preferProxiedForAddress;
+    private transient DateCache _logDateCache;
+    private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z";
+    private Locale _logLocale = Locale.getDefault();
+    private String _logTimeZone = "GMT";
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Is logging enabled
+     */
+    protected abstract boolean isEnabled();
+    
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Write requestEntry out. (to disk or slf4j log)
+     */
+    public abstract void write(String requestEntry) throws IOException;
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Writes the request and response information to the output stream.
+     *
+     * @see org.eclipse.jetty.server.RequestLog#log(org.eclipse.jetty.server.Request,
+     *      org.eclipse.jetty.server.Response)
+     */
+    @Override
+    public void log(Request request, Response response)
+    {
+        try
+        {
+            if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
+                return;
+
+            if (!isEnabled())
+                return;
+
+            StringBuilder buf = _buffers.get();
+            buf.setLength(0);
+
+            if (_logServer)
+            {
+                buf.append(request.getServerName());
+                buf.append(' ');
+            }
+
+            String addr = null;
+            if (_preferProxiedForAddress)
+            {
+                addr = request.getHeader(HttpHeader.X_FORWARDED_FOR.toString());
+            }
+
+            if (addr == null)
+                addr = request.getRemoteAddr();
+
+            buf.append(addr);
+            buf.append(" - ");
+            Authentication authentication = request.getAuthentication();
+            if (authentication instanceof Authentication.User)
+                buf.append(((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName());
+            else
+                buf.append(" - ");
+
+            buf.append(" [");
+            if (_logDateCache != null)
+                buf.append(_logDateCache.format(request.getTimeStamp()));
+            else
+                buf.append(request.getTimeStamp());
+
+            buf.append("] \"");
+            buf.append(request.getMethod());
+            buf.append(' ');
+            buf.append(request.getUri().toString());
+            buf.append(' ');
+            buf.append(request.getProtocol());
+            buf.append("\" ");
+
+            int status = response.getStatus();
+            if (status <= 0)
+                status = 404;
+            buf.append((char)('0' + ((status / 100) % 10)));
+            buf.append((char)('0' + ((status / 10) % 10)));
+            buf.append((char)('0' + (status % 10)));
+
+            long responseLength = response.getLongContentLength();
+            if (responseLength >= 0)
+            {
+                buf.append(' ');
+                if (responseLength > 99999)
+                    buf.append(responseLength);
+                else
+                {
+                    if (responseLength > 9999)
+                        buf.append((char)('0' + ((responseLength / 10000) % 10)));
+                    if (responseLength > 999)
+                        buf.append((char)('0' + ((responseLength / 1000) % 10)));
+                    if (responseLength > 99)
+                        buf.append((char)('0' + ((responseLength / 100) % 10)));
+                    if (responseLength > 9)
+                        buf.append((char)('0' + ((responseLength / 10) % 10)));
+                    buf.append((char)('0' + (responseLength) % 10));
+                }
+                buf.append(' ');
+            }
+            else
+                buf.append(" - ");
+
+
+            if (_extended)
+                logExtended(request, response, buf);
+
+            if (_logCookies)
+            {
+                Cookie[] cookies = request.getCookies();
+                if (cookies == null || cookies.length == 0)
+                    buf.append(" -");
+                else
+                {
+                    buf.append(" \"");
+                    for (int i = 0; i < cookies.length; i++)
+                    {
+                        if (i != 0)
+                            buf.append(';');
+                        buf.append(cookies[i].getName());
+                        buf.append('=');
+                        buf.append(cookies[i].getValue());
+                    }
+                    buf.append('\"');
+                }
+            }
+
+            if (_logLatency)
+            {
+                long now = System.currentTimeMillis();
+
+                if (_logLatency)
+                {
+                    buf.append(' ');
+                    buf.append(now - request.getTimeStamp());
+                }
+            }
+
+            String log = buf.toString();
+            write(log);
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Writes extended request and response information to the output stream.
+     *
+     * @param request  request object
+     * @param response response object
+     * @param b        StringBuilder to write to
+     * @throws IOException
+     */
+    protected void logExtended(Request request,
+                               Response response,
+                               StringBuilder b) throws IOException
+    {
+        String referer = request.getHeader(HttpHeader.REFERER.toString());
+        if (referer == null)
+            b.append("\"-\" ");
+        else
+        {
+            b.append('"');
+            b.append(referer);
+            b.append("\" ");
+        }
+
+        String agent = request.getHeader(HttpHeader.USER_AGENT.toString());
+        if (agent == null)
+            b.append("\"-\" ");
+        else
+        {
+            b.append('"');
+            b.append(agent);
+            b.append('"');
+        }
+    }
+
+
+    /**
+     * Set request paths that will not be logged.
+     *
+     * @param ignorePaths array of request paths
+     */
+    public void setIgnorePaths(String[] ignorePaths)
+    {
+        _ignorePaths = ignorePaths;
+    }
+
+    /**
+     * Retrieve the request paths that will not be logged.
+     *
+     * @return array of request paths
+     */
+    public String[] getIgnorePaths()
+    {
+        return _ignorePaths;
+    }
+
+    /**
+     * Controls logging of the request cookies.
+     *
+     * @param logCookies true - values of request cookies will be logged, false - values of request cookies will not be
+     *                   logged
+     */
+    public void setLogCookies(boolean logCookies)
+    {
+        _logCookies = logCookies;
+    }
+
+    /**
+     * Retrieve log cookies flag
+     *
+     * @return value of the flag
+     */
+    public boolean getLogCookies()
+    {
+        return _logCookies;
+    }
+
+    /**
+     * Controls logging of the request hostname.
+     *
+     * @param logServer true - request hostname will be logged, false - request hostname will not be logged
+     */
+    public void setLogServer(boolean logServer)
+    {
+        _logServer = logServer;
+    }
+
+    /**
+     * Retrieve log hostname flag.
+     *
+     * @return value of the flag
+     */
+    public boolean getLogServer()
+    {
+        return _logServer;
+    }
+
+    /**
+     * Controls logging of request processing time.
+     *
+     * @param logLatency true - request processing time will be logged false - request processing time will not be
+     *                   logged
+     */
+    public void setLogLatency(boolean logLatency)
+    {
+        _logLatency = logLatency;
+    }
+
+    /**
+     * Retrieve log request processing time flag.
+     *
+     * @return value of the flag
+     */
+    public boolean getLogLatency()
+    {
+        return _logLatency;
+    }
+
+    /**
+     * @deprecated use {@link StatisticsHandler}
+     */
+    public void setLogDispatch(boolean value)
+    {
+    }
+
+    /**
+     * @deprecated use {@link StatisticsHandler}
+     */
+    public boolean isLogDispatch()
+    {
+        return false;
+    }
+
+    /**
+     * Controls whether the actual IP address of the connection or the IP address from the X-Forwarded-For header will
+     * be logged.
+     *
+     * @param preferProxiedForAddress true - IP address from header will be logged, false - IP address from the
+     *                                connection will be logged
+     */
+    public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
+    {
+        _preferProxiedForAddress = preferProxiedForAddress;
+    }
+
+    /**
+     * Retrieved log X-Forwarded-For IP address flag.
+     *
+     * @return value of the flag
+     */
+    public boolean getPreferProxiedForAddress()
+    {
+        return _preferProxiedForAddress;
+    }
+
+    /**
+     * Set the extended request log format flag.
+     *
+     * @param extended true - log the extended request information, false - do not log the extended request information
+     */
+    public void setExtended(boolean extended)
+    {
+        _extended = extended;
+    }
+
+    /**
+     * Retrieve the extended request log format flag.
+     *
+     * @return value of the flag
+     */
+    @ManagedAttribute("use extended NCSA format")
+    public boolean isExtended()
+    {
+        return _extended;
+    }
+
+    /**
+     * Set up request logging and open log file.
+     *
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected synchronized void doStart() throws Exception
+    {
+        if (_logDateFormat != null)
+        {
+            _logDateCache = new DateCache(_logDateFormat, _logLocale ,_logTimeZone);
+        }
+
+        if (_ignorePaths != null && _ignorePaths.length > 0)
+        {
+            _ignorePathMap = new PathMap<>();
+            for (int i = 0; i < _ignorePaths.length; i++)
+                _ignorePathMap.put(_ignorePaths[i], _ignorePaths[i]);
+        }
+        else
+            _ignorePathMap = null;
+
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        _logDateCache = null;
+        super.doStop();
+    }
+
+    /**
+     * Set the timestamp format for request log entries in the file. If this is not set, the pre-formated request
+     * timestamp is used.
+     *
+     * @param format timestamp format string
+     */
+    public void setLogDateFormat(String format)
+    {
+        _logDateFormat = format;
+    }
+
+    /**
+     * Retrieve the timestamp format string for request log entries.
+     *
+     * @return timestamp format string.
+     */
+    public String getLogDateFormat()
+    {
+        return _logDateFormat;
+    }
+
+    /**
+     * Set the locale of the request log.
+     *
+     * @param logLocale locale object
+     */
+    public void setLogLocale(Locale logLocale)
+    {
+        _logLocale = logLocale;
+    }
+
+    /**
+     * Retrieve the locale of the request log.
+     *
+     * @return locale object
+     */
+    public Locale getLogLocale()
+    {
+        return _logLocale;
+    }
+
+    /**
+     * Set the timezone of the request log.
+     *
+     * @param tz timezone string
+     */
+    public void setLogTimeZone(String tz)
+    {
+        _logTimeZone = tz;
+    }
+
+    /**
+     * Retrieve the timezone of the request log.
+     *
+     * @return timezone string
+     */
+    @ManagedAttribute("the timezone")
+    public String getLogTimeZone()
+    {
+        return _logTimeZone;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AbstractNetworkConnector.java b/lib/jetty/org/eclipse/jetty/server/AbstractNetworkConnector.java
new file mode 100644 (file)
index 0000000..08b65bc
--- /dev/null
@@ -0,0 +1,125 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An abstract Network Connector.
+ * <p>
+ * Extends the {@link AbstractConnector} support for the {@link NetworkConnector} interface.
+ */
+@ManagedObject("AbstractNetworkConnector")
+public abstract class AbstractNetworkConnector extends AbstractConnector implements NetworkConnector
+{
+
+    private volatile String _host;
+    private volatile int _port = 0;
+
+    public AbstractNetworkConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, ConnectionFactory... factories)
+    {
+        super(server,executor,scheduler,pool,acceptors,factories);
+    }
+
+    public void setHost(String host)
+    {
+        _host = host;
+    }
+
+    @Override
+    @ManagedAttribute("The network interface this connector binds to as an IP address or a hostname.  If null or 0.0.0.0, then bind to all interfaces.")
+    public String getHost()
+    {
+        return _host;
+    }
+
+    public void setPort(int port)
+    {
+        _port = port;
+    }
+
+    @Override
+    @ManagedAttribute("Port this connector listens on. If set the 0 a random port is assigned which may be obtained with getLocalPort()")
+    public int getPort()
+    {
+        return _port;
+    }
+
+    @Override
+    public int getLocalPort()
+    {
+        return -1;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        open();
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        close();
+        super.doStop();
+    }
+
+    @Override
+    public void open() throws IOException
+    {
+    }
+
+    @Override
+    public void close()
+    {
+        // Interrupting is often sufficient to close the channel
+        interruptAcceptors();
+    }
+    
+
+    @Override
+    public Future<Void> shutdown()
+    {
+        close();
+        return super.shutdown();
+    }
+
+    @Override
+    protected boolean isAccepting()
+    {
+        return super.isAccepting() && isOpen();
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s{%s:%d}",
+                super.toString(),
+                getHost() == null ? "0.0.0.0" : getHost(),
+                getLocalPort() <= 0 ? getPort() : getLocalPort());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AsyncContextEvent.java b/lib/jetty/org/eclipse/jetty/server/AsyncContextEvent.java
new file mode 100644 (file)
index 0000000..cc0eec8
--- /dev/null
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class AsyncContextEvent extends AsyncEvent implements Runnable
+{
+    final private Context _context;
+    final private AsyncContextState _asyncContext;
+    private volatile HttpChannelState _state;
+    private ServletContext _dispatchContext;
+    private String _dispatchPath;
+    private volatile Scheduler.Task _timeoutTask;
+    private Throwable _throwable;
+
+    public AsyncContextEvent(Context context,AsyncContextState asyncContext, HttpChannelState state, Request baseRequest, ServletRequest request, ServletResponse response)
+    {
+        super(null,request,response,null);
+        _context=context;
+        _asyncContext=asyncContext;
+        _state=state;
+
+        // If we haven't been async dispatched before
+        if (baseRequest.getAttribute(AsyncContext.ASYNC_REQUEST_URI)==null)
+        {
+            // We are setting these attributes during startAsync, when the spec implies that
+            // they are only available after a call to AsyncContext.dispatch(...);
+
+            // have we been forwarded before?
+            String uri=(String)baseRequest.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
+            if (uri!=null)
+            {
+                baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,uri);
+                baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,baseRequest.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH));
+                baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,baseRequest.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
+                baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO,baseRequest.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
+                baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,baseRequest.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
+            }
+            else
+            {
+                baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,baseRequest.getRequestURI());
+                baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,baseRequest.getContextPath());
+                baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,baseRequest.getServletPath());
+                baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO,baseRequest.getPathInfo());
+                baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,baseRequest.getQueryString());
+            }
+        }
+    }
+
+    public ServletContext getSuspendedContext()
+    {
+        return _context;
+    }
+    
+    public Context getContext()
+    {
+        return _context;
+    }
+
+    public ServletContext getDispatchContext()
+    {
+        return _dispatchContext;
+    }
+
+    public ServletContext getServletContext()
+    {
+        return _dispatchContext==null?_context:_dispatchContext;
+    }
+
+    /**
+     * @return The path in the context
+     */
+    public String getPath()
+    {
+        return _dispatchPath;
+    }
+    
+    public void setTimeoutTask(Scheduler.Task task)
+    {
+        _timeoutTask = task;
+    }
+    
+    public void cancelTimeoutTask()
+    {
+        Scheduler.Task task=_timeoutTask;
+        _timeoutTask=null;
+        if (task!=null)
+            task.cancel();
+    }
+
+    @Override
+    public AsyncContext getAsyncContext()
+    {
+        return _asyncContext;
+    }
+    
+    @Override
+    public Throwable getThrowable()
+    {
+        return _throwable;
+    }
+    
+    public void setThrowable(Throwable throwable)
+    {
+        _throwable=throwable;
+    }
+
+    public void setDispatchContext(ServletContext context)
+    {
+        _dispatchContext=context;
+    }
+    
+    public void setDispatchPath(String path)
+    {
+        _dispatchPath=path;
+    }
+    
+    public void completed()
+    {
+        _timeoutTask=null;
+        _asyncContext.reset();
+    }
+
+    public HttpChannelState getHttpChannelState()
+    {
+        return _state;
+    }
+
+    @Override
+    public void run()
+    {
+        Scheduler.Task task=_timeoutTask;
+        _timeoutTask=null;
+        if (task!=null)
+            _state.expired();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AsyncContextState.java b/lib/jetty/org/eclipse/jetty/server/AsyncContextState.java
new file mode 100644 (file)
index 0000000..6503424
--- /dev/null
@@ -0,0 +1,185 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+
+public class AsyncContextState implements AsyncContext
+{
+    volatile HttpChannelState _state;
+
+    public AsyncContextState(HttpChannelState state)
+    {
+        _state=state;
+    }
+    
+    HttpChannelState state()
+    {
+        HttpChannelState state=_state;
+        if (state==null)
+            throw new IllegalStateException("AsyncContext completed");
+        return state;
+    }
+
+    @Override
+    public void addListener(final AsyncListener listener, final ServletRequest request, final ServletResponse response)
+    {
+        AsyncListener wrap = new AsyncListener()
+        {
+            @Override
+            public void onTimeout(AsyncEvent event) throws IOException
+            {
+                listener.onTimeout(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+            }
+            
+            @Override
+            public void onStartAsync(AsyncEvent event) throws IOException
+            {
+                listener.onStartAsync(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+            }
+            
+            @Override
+            public void onError(AsyncEvent event) throws IOException
+            {
+                listener.onComplete(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+            }
+            
+            @Override
+            public void onComplete(AsyncEvent event) throws IOException
+            {                
+                listener.onComplete(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+            }
+        };
+        state().addListener(wrap);
+    }
+
+    @Override
+    public void addListener(AsyncListener listener)
+    {
+        state().addListener(listener);
+    }
+
+    @Override
+    public void complete()
+    {
+        state().complete();
+    }
+
+    @Override
+    public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException
+    {    
+        ContextHandler contextHandler = state().getContextHandler();
+        if (contextHandler != null)
+            return contextHandler.getServletContext().createListener(clazz);
+        try
+        {
+            return clazz.newInstance();
+        }
+        catch (Exception e)
+        {
+            throw new ServletException(e);
+        }
+    }
+
+    @Override
+    public void dispatch()
+    {
+        state().dispatch(null,null);
+    }
+
+    @Override
+    public void dispatch(String path)
+    {
+        state().dispatch(null,path);
+    }
+    
+    @Override
+    public void dispatch(ServletContext context, String path)
+    {
+        state().dispatch(context,path);
+    }
+
+    @Override
+    public ServletRequest getRequest()
+    {
+        return state().getAsyncContextEvent().getSuppliedRequest();
+    }
+
+    @Override
+    public ServletResponse getResponse()
+    {
+        return state().getAsyncContextEvent().getSuppliedResponse();
+    }
+
+    @Override
+    public long getTimeout()
+    {
+        return state().getTimeout();
+    }
+
+    @Override
+    public boolean hasOriginalRequestAndResponse()
+    {
+        HttpChannel<?> channel=state().getHttpChannel();
+        return channel.getRequest()==getRequest() && channel.getResponse()==getResponse();
+    }
+
+    @Override
+    public void setTimeout(long arg0)
+    {
+        state().setTimeout(arg0);
+    }
+
+    @Override
+    public void start(final Runnable task)
+    {
+        state().getHttpChannel().execute(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                state().getAsyncContextEvent().getContext().getContextHandler().handle(task);
+            }
+        });
+    }
+
+    public void reset()
+    {
+        _state=null;
+    }
+
+    public HttpChannelState getHttpChannelState()
+    {
+        return state();
+    }
+
+    
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AsyncNCSARequestLog.java b/lib/jetty/org/eclipse/jetty/server/AsyncNCSARequestLog.java
new file mode 100644 (file)
index 0000000..33ba21d
--- /dev/null
@@ -0,0 +1,129 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * An asynchronously writing NCSA Request Log
+ */
+public class AsyncNCSARequestLog extends NCSARequestLog
+{
+    private static final Logger LOG = Log.getLogger(AsyncNCSARequestLog.class);
+    private final BlockingQueue<String> _queue;
+    private transient WriterThread _thread;
+    private boolean _warnedFull;
+
+    public AsyncNCSARequestLog()
+    {
+        this(null,null);
+    }
+
+    public AsyncNCSARequestLog(BlockingQueue<String> queue)
+    {
+        this(null,queue);
+    }
+
+    public AsyncNCSARequestLog(String filename)
+    {
+        this(filename,null);
+    }
+
+    public AsyncNCSARequestLog(String filename,BlockingQueue<String> queue)
+    {
+        super(filename);
+        if (queue==null)
+            queue=new BlockingArrayQueue<>(1024);
+        _queue=queue;
+    }
+
+    private class WriterThread extends Thread
+    {
+        WriterThread()
+        {
+            setName("AsyncNCSARequestLog@"+Integer.toString(AsyncNCSARequestLog.this.hashCode(),16));
+        }
+
+        @Override
+        public void run()
+        {
+            while (isRunning())
+            {
+                try
+                {
+                    String log = _queue.poll(10,TimeUnit.SECONDS);
+                    if (log!=null)
+                        AsyncNCSARequestLog.super.write(log);
+
+                    while(!_queue.isEmpty())
+                    {
+                        log=_queue.poll();
+                        if (log!=null)
+                            AsyncNCSARequestLog.super.write(log);
+                    }
+                }
+                catch (IOException e)
+                {
+                    LOG.warn(e);
+                }
+                catch (InterruptedException e)
+                {
+                    LOG.ignore(e);
+                }
+            }
+        }
+    }
+
+    @Override
+    protected synchronized void doStart() throws Exception
+    {
+        super.doStart();
+        _thread = new WriterThread();
+        _thread.start();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        _thread.interrupt();
+        _thread.join();
+        super.doStop();
+        _thread=null;
+    }
+
+    @Override
+    public void write(String log) throws IOException
+    {
+        if (!_queue.offer(log))
+        {
+            if (_warnedFull)
+                LOG.warn("Log Queue overflow");
+            _warnedFull=true;
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Authentication.java b/lib/jetty/org/eclipse/jetty/server/Authentication.java
new file mode 100644 (file)
index 0000000..ccdf4c0
--- /dev/null
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/* ------------------------------------------------------------ */
+/** The Authentication state of a request.
+ * <p>
+ * The Authentication state can be one of several sub-types that
+ * reflects where the request is in the many different authentication
+ * cycles. Authentication might not yet be checked or it might be checked
+ * and failed, checked and deferred or succeeded. 
+ * 
+ */
+public interface Authentication
+{
+    /* ------------------------------------------------------------ */
+    public static class Failed extends QuietServletException
+    {
+       public Failed(String message)
+       {
+           super(message);
+       }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** A successful Authentication with User information.
+     */
+    public interface User extends Authentication
+    {
+        String getAuthMethod();
+        UserIdentity getUserIdentity(); 
+        boolean isUserInRole(UserIdentity.Scope scope,String role);
+        void logout();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** A wrapped authentication with methods provide the
+     * wrapped request/response for use by the application
+     */
+    public interface Wrapped extends Authentication
+    {
+        HttpServletRequest getHttpServletRequest();
+        HttpServletResponse getHttpServletResponse();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** A deferred authentication with methods to progress 
+     * the authentication process.
+     */
+    public interface Deferred extends Authentication
+    {
+        /* ------------------------------------------------------------ */
+        /** Authenticate if possible without sending a challenge.
+         * This is used to check credentials that have been sent for 
+         * non-manditory authentication.
+         * @return The new Authentication state.
+         */
+        Authentication authenticate(ServletRequest request);
+
+        /* ------------------------------------------------------------ */
+        /** Authenticate and possibly send a challenge.
+         * This is used to initiate authentication for previously 
+         * non-manditory authentication.
+         * @return The new Authentication state.
+         */
+        Authentication authenticate(ServletRequest request,ServletResponse response);
+        
+        
+        /* ------------------------------------------------------------ */
+        /** Login with the LOGIN authenticator
+         * @param username
+         * @param password
+         * @return The new Authentication state
+         */
+        Authentication login(String username,Object password,ServletRequest request);
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /** Authentication Response sent state.
+     * Responses are sent by authenticators either to issue an
+     * authentication challenge or on successful authentication in
+     * order to redirect the user to the original URL.
+     */
+    public interface ResponseSent extends Authentication
+    { 
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** An Authentication Challenge has been sent.
+     */
+    public interface Challenge extends ResponseSent
+    { 
+    }
+
+    /* ------------------------------------------------------------ */
+    /** An Authentication Failure has been sent.
+     */
+    public interface Failure extends ResponseSent
+    { 
+    }
+
+    public interface SendSuccess extends ResponseSent
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Unauthenticated state.
+     * <p> 
+     * This convenience instance is for non mandatory authentication where credentials
+     * have been presented and checked, but failed authentication. 
+     */
+    public final static Authentication UNAUTHENTICATED = new Authentication(){@Override
+    public String toString(){return "UNAUTHENTICATED";}};
+
+    /* ------------------------------------------------------------ */
+    /** Authentication not checked
+     * <p>
+     * This convenience instance us for non mandatory authentication when no 
+     * credentials are present to be checked.
+     */
+    public final static Authentication NOT_CHECKED = new Authentication(){@Override
+    public String toString(){return "NOT CHECKED";}};
+
+    /* ------------------------------------------------------------ */
+    /** Authentication challenge sent.
+     * <p>
+     * This convenience instance is for when an authentication challenge has been sent.
+     */
+    public final static Authentication SEND_CONTINUE = new Authentication.Challenge(){@Override
+    public String toString(){return "CHALLENGE";}};
+
+    /* ------------------------------------------------------------ */
+    /** Authentication failure sent.
+     * <p>
+     * This convenience instance is for when an authentication failure has been sent.
+     */
+    public final static Authentication SEND_FAILURE = new Authentication.Failure(){@Override
+    public String toString(){return "FAILURE";}};
+    public final static Authentication SEND_SUCCESS = new SendSuccess(){@Override
+    public String toString(){return "SEND_SUCCESS";}};
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java b/lib/jetty/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java
new file mode 100644 (file)
index 0000000..cd2d12e
--- /dev/null
@@ -0,0 +1,53 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.nio.ByteBuffer;
+
+/**
+ * <p>An implementation of HttpInput using {@link ByteBuffer} as items.</p>
+ */
+public class ByteBufferQueuedHttpInput extends QueuedHttpInput<ByteBuffer>
+{
+    @Override
+    protected int remaining(ByteBuffer item)
+    {
+        return item.remaining();
+    }
+
+    @Override
+    protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
+    {
+        int l = Math.min(item.remaining(), length);
+        item.get(buffer, offset, l);
+        return l;
+    }
+    
+    @Override
+    protected void consume(ByteBuffer item, int length)
+    {
+        item.position(item.position()+length);
+    }
+
+    @Override
+    protected void onContentConsumed(ByteBuffer item)
+    {
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ClassLoaderDump.java b/lib/jetty/org/eclipse/jetty/server/ClassLoaderDump.java
new file mode 100644 (file)
index 0000000..466775e
--- /dev/null
@@ -0,0 +1,66 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.URLClassLoader;
+import java.util.Collections;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+
+public class ClassLoaderDump implements Dumpable
+{
+    final ClassLoader _loader;
+
+    public ClassLoaderDump(ClassLoader loader)
+    {
+        _loader = loader;
+    }
+
+    @Override
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        if (_loader==null)
+            out.append("No ClassLoader\n");
+        else
+        {
+            out.append(String.valueOf(_loader)).append("\n");
+
+            Object parent = _loader.getParent();
+            if (parent != null)
+            {
+                if (!(parent instanceof Dumpable))
+                    parent = new ClassLoaderDump((ClassLoader)parent);
+
+                if (_loader instanceof URLClassLoader)
+                    ContainerLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()),Collections.singleton(parent));
+                else
+                    ContainerLifeCycle.dump(out,indent,Collections.singleton(parent));
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ConnectionFactory.java b/lib/jetty/org/eclipse/jetty/server/ConnectionFactory.java
new file mode 100644 (file)
index 0000000..e23739d
--- /dev/null
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+
+/**
+ * <p>A Factory to create {@link Connection} instances for {@link Connector}s.</p>
+ * <p>A Connection factory is responsible for instantiating and configuring a {@link Connection} instance
+ * to handle an {@link EndPoint} accepted by a {@link Connector}.</p>
+ * <p>
+ * A ConnectionFactory has a protocol name that represents the protocol of the Connections
+ * created.  Example of protocol names include:<dl>
+ * <dt>http</dt><dd>Creates a HTTP connection that can handle multiple versions of HTTP from 0.9 to 1.1</dd>
+ * <dt>spdy/2</dt><dd>Creates a HTTP connection that handles a specific version of the SPDY protocol</dd>
+ * <dt>SSL-XYZ</dt><dd>Create an SSL connection chained to a connection obtained from a connection factory 
+ * with a protocol "XYZ".</dd>
+ * <dt>SSL-http</dt><dd>Create an SSL connection chained to a HTTP connection (aka https)</dd>
+ * <dt>SSL-npn</dt><dd>Create an SSL connection chained to a NPN connection, that uses a negotiation with
+ * the client to determine the next protocol.</dd>
+ * </dl>
+ */
+public interface ConnectionFactory
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * @return A string representing the protocol name.
+     */
+    public String getProtocol();
+    
+    /**
+     * <p>Creates a new {@link Connection} with the given parameters</p>
+     * @param connector The {@link Connector} creating this connection
+     * @param endPoint the {@link EndPoint} associated with the connection
+     * @return a new {@link Connection}
+     */
+    public Connection newConnection(Connector connector, EndPoint endPoint);
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Connector.java b/lib/jetty/org/eclipse/jetty/server/Connector.java
new file mode 100644 (file)
index 0000000..ce4544c
--- /dev/null
@@ -0,0 +1,105 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.Graceful;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * <p>A {@link Connector} accept connections and data from remote peers,
+ * and allows applications to send data to remote peers, by setting up
+ * the machinery needed to handle such tasks.</p>
+ */
+@ManagedObject("Connector Interface")
+public interface Connector extends LifeCycle, Graceful
+{
+    /**
+     * @return the {@link Server} instance associated with this {@link Connector}
+     */
+    public Server getServer();
+
+    /**
+     * @return the {@link Executor} used to submit tasks
+     */
+    public Executor getExecutor();
+
+    /**
+     * @return the {@link Scheduler} used to schedule tasks
+     */
+    public Scheduler getScheduler();
+
+    /**
+     * @return the {@link ByteBufferPool} to acquire buffers from and release buffers to
+     */
+    public ByteBufferPool getByteBufferPool();
+
+    /**
+     * @return the {@link ConnectionFactory} associated with the protocol name
+     */
+    public ConnectionFactory getConnectionFactory(String nextProtocol);
+    
+
+    public <T> T getConnectionFactory(Class<T> factoryType);
+    
+    /**
+     * @return the default {@link ConnectionFactory} associated with the default protocol name
+     */
+    public ConnectionFactory getDefaultConnectionFactory();
+    
+    public Collection<ConnectionFactory> getConnectionFactories();
+    
+    public List<String> getProtocols();
+    
+    /**
+     * @return the max idle timeout for connections in milliseconds
+     */
+    @ManagedAttribute("maximum time a connection can be idle before being closed (in ms)")
+    public long getIdleTimeout();
+
+    /**
+     * @return the underlying socket, channel, buffer etc. for the connector.
+     */
+    public Object getTransport();
+    
+    /**
+     * @return immutable collection of connected endpoints
+     */
+    public Collection<EndPoint> getConnectedEndPoints();
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the connector name if set.
+     * <p>A {@link ContextHandler} may be configured with
+     * virtual hosts in the form "@connectorName" and will only serve
+     * requests from the named connector.
+     * @return The connector name or null.
+     */
+    public String getName();
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ConnectorStatistics.java b/lib/jetty/org/eclipse/jetty/server/ConnectorStatistics.java
new file mode 100644 (file)
index 0000000..cae5e6e
--- /dev/null
@@ -0,0 +1,308 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.Container;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+
+
+/* ------------------------------------------------------------ */
+/** A Connector.Listener that gathers Connector and Connections Statistics.
+ * Adding an instance of this class as with {@link AbstractConnector#addBean(Object)} 
+ * will register the listener with all connections accepted by that connector.
+ */
+@ManagedObject("Connector Statistics")
+public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable, Connection.Listener
+{
+    private final static Sample ZERO=new Sample();
+    private final AtomicLong _startMillis = new AtomicLong(-1L);
+    private final CounterStatistic _connectionStats = new CounterStatistic();
+    private final SampleStatistic _messagesIn = new SampleStatistic();
+    private final SampleStatistic _messagesOut = new SampleStatistic();
+    private final SampleStatistic _connectionDurationStats = new SampleStatistic();
+    private final ConcurrentMap<Connection, Sample> _samples = new ConcurrentHashMap<>();
+    private final AtomicInteger _closedIn = new AtomicInteger();
+    private final AtomicInteger _closedOut = new AtomicInteger();
+    private AtomicLong _nanoStamp=new AtomicLong();
+    private volatile int _messagesInPerSecond;
+    private volatile int _messagesOutPerSecond;
+
+    @Override
+    public void onOpened(Connection connection)
+    {
+        if (isStarted())
+        {
+            _connectionStats.increment();
+            _samples.put(connection,ZERO);
+        }
+    }
+
+    @Override
+    public void onClosed(Connection connection)
+    {
+        if (isStarted())
+        {
+            int msgsIn=connection.getMessagesIn();
+            int msgsOut=connection.getMessagesOut();
+            _messagesIn.set(msgsIn);
+            _messagesOut.set(msgsOut);
+            _connectionStats.decrement();
+            _connectionDurationStats.set(System.currentTimeMillis()-connection.getCreatedTimeStamp());
+
+            Sample sample=_samples.remove(connection);
+            if (sample!=null)
+            {
+                _closedIn.addAndGet(msgsIn-sample._messagesIn);
+                _closedOut.addAndGet(msgsOut-sample._messagesOut);
+            }
+        }
+    }
+
+    @ManagedAttribute("Total number of bytes received by this connector")
+    public int getBytesIn()
+    {
+        // TODO
+        return -1;
+    }
+
+    @ManagedAttribute("Total number of bytes sent by this connector")
+    public int getBytesOut()
+    {
+        // TODO
+        return -1;
+    }
+
+    @ManagedAttribute("Total number of connections seen by this connector")
+    public int getConnections()
+    {
+        return (int)_connectionStats.getTotal();
+    }
+
+    @ManagedAttribute("Connection duration maximum in ms")
+    public long getConnectionDurationMax()
+    {
+        return _connectionDurationStats.getMax();
+    }
+
+    @ManagedAttribute("Connection duration mean in ms")
+    public double getConnectionDurationMean()
+    {
+        return _connectionDurationStats.getMean();
+    }
+
+    @ManagedAttribute("Connection duration standard deviation")
+    public double getConnectionDurationStdDev()
+    {
+        return _connectionDurationStats.getStdDev();
+    }
+
+    @ManagedAttribute("Messages In for all connections")
+    public int getMessagesIn()
+    {
+        return (int)_messagesIn.getTotal();
+    }
+
+    @ManagedAttribute("Messages In per connection maximum")
+    public int getMessagesInPerConnectionMax()
+    {
+        return (int)_messagesIn.getMax();
+    }
+
+    @ManagedAttribute("Messages In per connection mean")
+    public double getMessagesInPerConnectionMean()
+    {
+        return _messagesIn.getMean();
+    }
+
+    @ManagedAttribute("Messages In per connection standard deviation")
+    public double getMessagesInPerConnectionStdDev()
+    {
+        return _messagesIn.getStdDev();
+    }
+
+    @ManagedAttribute("Connections open")
+    public int getConnectionsOpen()
+    {
+        return (int)_connectionStats.getCurrent();
+    }
+
+    @ManagedAttribute("Connections open maximum")
+    public int getConnectionsOpenMax()
+    {
+        return (int)_connectionStats.getMax();
+    }
+
+    @ManagedAttribute("Messages Out for all connections")
+    public int getMessagesOut()
+    {
+        return (int)_messagesIn.getTotal();
+    }
+
+    @ManagedAttribute("Messages In per connection maximum")
+    public int getMessagesOutPerConnectionMax()
+    {
+        return (int)_messagesIn.getMax();
+    }
+
+    @ManagedAttribute("Messages In per connection mean")
+    public double getMessagesOutPerConnectionMean()
+    {
+        return _messagesIn.getMean();
+    }
+
+    @ManagedAttribute("Messages In per connection standard deviation")
+    public double getMessagesOutPerConnectionStdDev()
+    {
+        return _messagesIn.getStdDev();
+    }
+
+    @ManagedAttribute("Connection statistics started ms since epoch")
+    public long getStartedMillis()
+    {
+        long start = _startMillis.get();
+        return start < 0 ? 0 : System.currentTimeMillis() - start;
+    }
+
+    @ManagedAttribute("Messages in per second calculated over period since last called")
+    public int getMessagesInPerSecond()
+    {
+        update();
+        return _messagesInPerSecond;
+    }
+
+    @ManagedAttribute("Messages out per second calculated over period since last called")
+    public int getMessagesOutPerSecond()
+    {
+        update();
+        return _messagesOutPerSecond;
+    }
+
+    @Override
+    public void doStart()
+    {
+        reset();
+    }
+
+    @Override
+    public void doStop()
+    {
+        _samples.clear();
+    }
+
+    @ManagedOperation("Reset the statistics")
+    public void reset()
+    {
+        _startMillis.set(System.currentTimeMillis());
+        _messagesIn.reset();
+        _messagesOut.reset();
+        _connectionStats.reset();
+        _connectionDurationStats.reset();
+        _samples.clear();
+    }
+
+    @Override
+    @ManagedOperation("dump thread state")
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        ContainerLifeCycle.dumpObject(out,this);
+        ContainerLifeCycle.dump(out,indent,Arrays.asList(new String[]{"connections="+_connectionStats,"duration="+_connectionDurationStats,"in="+_messagesIn,"out="+_messagesOut}));
+    }
+    
+    public static void addToAllConnectors(Server server)
+    {
+        for (Connector connector : server.getConnectors())
+        {
+            if (connector instanceof Container)
+             ((Container)connector).addBean(new ConnectorStatistics());
+        }
+    }  
+    
+    private static final long SECOND_NANOS=TimeUnit.SECONDS.toNanos(1);
+    private synchronized void update()
+    {
+        long now=System.nanoTime();
+        long then=_nanoStamp.get();
+        long duration=now-then;
+                
+        if (duration>SECOND_NANOS/2)
+        {
+            if (_nanoStamp.compareAndSet(then,now))
+            {
+                long msgsIn=_closedIn.getAndSet(0);
+                long msgsOut=_closedOut.getAndSet(0);
+
+                for (Map.Entry<Connection, Sample> entry : _samples.entrySet())
+                {
+                    Connection connection=entry.getKey();
+                    Sample sample = entry.getValue();
+                    Sample next = new Sample(connection);
+                    if (_samples.replace(connection,sample,next))
+                    {
+                        msgsIn+=next._messagesIn-sample._messagesIn;
+                        msgsOut+=next._messagesOut-sample._messagesOut;
+                    }
+                }
+                
+                _messagesInPerSecond=(int)(msgsIn*SECOND_NANOS/duration);
+                _messagesOutPerSecond=(int)(msgsOut*SECOND_NANOS/duration);
+            }
+        }
+    }
+    
+    private static class Sample
+    {
+        Sample()
+        {
+            _messagesIn=0;
+            _messagesOut=0;
+        }
+        
+        Sample(Connection connection)
+        {
+            _messagesIn=connection.getMessagesIn();
+            _messagesOut=connection.getMessagesOut();
+        }
+        
+        final int _messagesIn;
+        final int _messagesOut;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/CookieCutter.java b/lib/jetty/org/eclipse/jetty/server/CookieCutter.java
new file mode 100644 (file)
index 0000000..703f804
--- /dev/null
@@ -0,0 +1,328 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import javax.servlet.http.Cookie;
+
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** Cookie parser
+ * <p>Optimized stateful cookie parser.  Cookies fields are added with the
+ * {@link #addCookieField(String)} method and parsed on the next subsequent
+ * call to {@link #getCookies()}.
+ * If the added fields are identical to those last added (as strings), then the 
+ * cookies are not re parsed.
+ * 
+ *
+ */
+public class CookieCutter
+{
+    private static final Logger LOG = Log.getLogger(CookieCutter.class);
+
+    private Cookie[] _cookies;
+    private Cookie[] _lastCookies;
+    private final List<String> _fieldList = new ArrayList<>();
+    int _fields;
+    
+    public CookieCutter()
+    {  
+    }
+    
+    public Cookie[] getCookies()
+    {
+        if (_cookies!=null)
+            return _cookies;
+        
+        if (_lastCookies!=null && _fields==_fieldList.size())
+            _cookies=_lastCookies;
+        else
+            parseFields();
+        _lastCookies=_cookies;
+        return _cookies;
+    }
+    
+    public void setCookies(Cookie[] cookies)
+    {
+        _cookies=cookies;
+        _lastCookies=null;
+        _fieldList.clear();
+        _fields=0;
+    }
+    
+    public void reset()
+    {
+        _cookies=null;
+        _fields=0;
+    }
+    
+    public void addCookieField(String f)
+    {
+        if (f==null)
+            return;
+        f=f.trim();
+        if (f.length()==0)
+            return;
+            
+        if (_fieldList.size()>_fields)
+        {
+            if (f.equals(_fieldList.get(_fields)))
+            {
+                _fields++;
+                return;
+            }
+            
+            while (_fieldList.size()>_fields)
+                _fieldList.remove(_fields);
+        }
+        _cookies=null;
+        _lastCookies=null;
+        _fieldList.add(_fields++,f);
+    }
+    
+    
+    protected void parseFields()
+    {
+        _lastCookies=null;
+        _cookies=null;
+        
+        List<Cookie> cookies = new ArrayList<>();
+
+        int version = 0;
+
+        // delete excess fields
+        while (_fieldList.size()>_fields)
+            _fieldList.remove(_fields);
+        
+        // For each cookie field
+        for (String hdr : _fieldList)
+        {
+            // Parse the header
+            String name = null;
+            String value = null;
+
+            Cookie cookie = null;
+
+            boolean invalue=false;
+            boolean quoted=false;
+            boolean escaped=false;
+            int tokenstart=-1;
+            int tokenend=-1;
+            for (int i = 0, length = hdr.length(), last=length-1; i < length; i++)
+            {
+                char c = hdr.charAt(i);
+                
+                // Handle quoted values for name or value
+                if (quoted)
+                {
+                    if (escaped)
+                    {
+                        escaped=false;
+                        continue;
+                    }
+                    
+                    switch (c)
+                    {
+                        case '"':
+                            tokenend=i;
+                            quoted=false;
+
+                            // handle quote as last character specially
+                            if (i==last)
+                            {
+                                if (invalue)
+                                    value = hdr.substring(tokenstart, tokenend+1);
+                                else
+                                {
+                                    name = hdr.substring(tokenstart, tokenend+1);
+                                    value = "";
+                                }
+                            }
+                            break;
+                            
+                        case '\\':
+                            escaped=true;
+                            continue;
+                        default:
+                            continue;
+                    }
+                }
+                else
+                {
+                    // Handle name and value state machines
+                    if (invalue)
+                    {
+                        // parse the value
+                        switch (c)
+                        {
+                            case ' ':
+                            case '\t':
+                                continue;
+                                
+                            case '"':
+                                if (tokenstart<0)
+                                {
+                                    quoted=true;
+                                    tokenstart=i;
+                                }
+                                tokenend=i;
+                                if (i==last)
+                                {
+                                    value = hdr.substring(tokenstart, tokenend+1);
+                                    break;
+                                }
+                                continue;
+
+                            case ';':
+                                if (tokenstart>=0)
+                                    value = hdr.substring(tokenstart, tokenend+1);
+                                else
+                                    value="";
+                                tokenstart = -1;
+                                invalue=false;
+                                break;
+                                
+                            default:
+                                if (tokenstart<0)
+                                    tokenstart=i;
+                                tokenend=i;
+                                if (i==last)
+                                {
+                                    value = hdr.substring(tokenstart, tokenend+1);
+                                    break;
+                                }
+                                continue;
+                        }
+                    }
+                    else
+                    {
+                        // parse the name
+                        switch (c)
+                        {
+                            case ' ':
+                            case '\t':
+                                continue;
+                                
+                            case '"':
+                                if (tokenstart<0)
+                                {
+                                    quoted=true;
+                                    tokenstart=i;
+                                }
+                                tokenend=i;
+                                if (i==last)
+                                {
+                                    name = hdr.substring(tokenstart, tokenend+1);
+                                    value = "";
+                                    break;
+                                }
+                                continue;
+
+                            case ';':
+                                if (tokenstart>=0)
+                                {
+                                    name = hdr.substring(tokenstart, tokenend+1);
+                                    value = "";
+                                }
+                                tokenstart = -1;
+                                break;
+
+                            case '=':
+                                if (tokenstart>=0)
+                                    name = hdr.substring(tokenstart, tokenend+1);
+                                tokenstart = -1;
+                                invalue=true;
+                                continue;
+                                
+                            default:
+                                if (tokenstart<0)
+                                    tokenstart=i;
+                                tokenend=i;
+                                if (i==last)
+                                {
+                                    name = hdr.substring(tokenstart, tokenend+1);
+                                    value = "";
+                                    break;
+                                }
+                                continue;
+                        }
+                    }
+                }
+
+                // If after processing the current character we have a value and a name, then it is a cookie
+                if (value!=null && name!=null)
+                {
+                    name=QuotedStringTokenizer.unquoteOnly(name);
+                    value=QuotedStringTokenizer.unquoteOnly(value);
+                    
+                    try
+                    {
+                        if (name.startsWith("$"))
+                        {
+                            String lowercaseName = name.toLowerCase(Locale.ENGLISH);
+                            if ("$path".equals(lowercaseName))
+                            {
+                                if (cookie!=null)
+                                    cookie.setPath(value);
+                            }
+                            else if ("$domain".equals(lowercaseName))
+                            {
+                                if (cookie!=null)
+                                    cookie.setDomain(value);
+                            }
+                            else if ("$port".equals(lowercaseName))
+                            {
+                                if (cookie!=null)
+                                    cookie.setComment("$port="+value);
+                            }
+                            else if ("$version".equals(lowercaseName))
+                            {
+                                version = Integer.parseInt(value);
+                            }
+                        }
+                        else
+                        {
+                            cookie = new Cookie(name, value);
+                            if (version > 0)
+                                cookie.setVersion(version);
+                            cookies.add(cookie);
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        LOG.debug(e);
+                    }
+
+                    name = null;
+                    value = null;
+                }
+            }
+        }
+
+        _cookies = (Cookie[]) cookies.toArray(new Cookie[cookies.size()]);
+        _lastCookies=_cookies;
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Dispatcher.java b/lib/jetty/org/eclipse/jetty/server/Dispatcher.java
new file mode 100644 (file)
index 0000000..2567289
--- /dev/null
@@ -0,0 +1,456 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.MultiMap;
+
+public class Dispatcher implements RequestDispatcher
+{
+    /** Dispatch include attribute names */
+    public final static String __INCLUDE_PREFIX="javax.servlet.include.";
+
+    /** Dispatch include attribute names */
+    public final static String __FORWARD_PREFIX="javax.servlet.forward.";
+
+    private final ContextHandler _contextHandler;
+    private final String _uri;
+    private final String _path;
+    private final String _query;
+    private final String _named;
+
+    public Dispatcher(ContextHandler contextHandler, String uri, String pathInContext, String query)
+    {
+        _contextHandler=contextHandler;
+        _uri=uri;
+        _path=pathInContext;
+        _query=query;
+        _named=null;
+    }
+
+    public Dispatcher(ContextHandler contextHandler, String name) throws IllegalStateException
+    {
+        _contextHandler=contextHandler;
+        _named=name;
+        _uri=null;
+        _path=null;
+        _query=null;
+    }
+
+    @Override
+    public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException
+    {
+        forward(request, response, DispatcherType.FORWARD);
+    }
+
+    public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException
+    {
+        forward(request, response, DispatcherType.ERROR);
+    }
+
+    @Override
+    public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException
+    {
+        Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
+
+        if (!(request instanceof HttpServletRequest))
+            request = new ServletRequestHttpWrapper(request);
+        if (!(response instanceof HttpServletResponse))
+            response = new ServletResponseHttpWrapper(response);
+
+        final DispatcherType old_type = baseRequest.getDispatcherType();
+        final Attributes old_attr=baseRequest.getAttributes();
+        final MultiMap<String> old_query_params=baseRequest.getQueryParameters();
+        try
+        {
+            baseRequest.setDispatcherType(DispatcherType.INCLUDE);
+            baseRequest.getResponse().include();
+            if (_named!=null)
+            {
+                _contextHandler.handle(_named,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+            }
+            else
+            {
+                IncludeAttributes attr = new IncludeAttributes(old_attr);
+
+                attr._requestURI=_uri;
+                attr._contextPath=_contextHandler.getContextPath();
+                attr._servletPath=null; // set by ServletHandler
+                attr._pathInfo=_path;
+                attr._query=_query;
+
+                if (_query!=null)
+                    baseRequest.mergeQueryParameters(_query, false);
+                baseRequest.setAttributes(attr);
+
+                _contextHandler.handle(_path, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+            }
+        }
+        finally
+        {
+            baseRequest.setAttributes(old_attr);
+            baseRequest.getResponse().included();
+            baseRequest.setQueryParameters(old_query_params);
+            baseRequest.resetParameters();
+            baseRequest.setDispatcherType(old_type);
+        }
+    }
+
+    protected void forward(ServletRequest request, ServletResponse response, DispatcherType dispatch) throws ServletException, IOException
+    {
+        Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
+        Response base_response=baseRequest.getResponse();
+        base_response.resetForForward();
+
+        if (!(request instanceof HttpServletRequest))
+            request = new ServletRequestHttpWrapper(request);
+        if (!(response instanceof HttpServletResponse))
+            response = new ServletResponseHttpWrapper(response);
+
+        final boolean old_handled=baseRequest.isHandled();
+        final String old_uri=baseRequest.getRequestURI();
+        final String old_context_path=baseRequest.getContextPath();
+        final String old_servlet_path=baseRequest.getServletPath();
+        final String old_path_info=baseRequest.getPathInfo();
+        final String old_query=baseRequest.getQueryString();
+        final MultiMap<String> old_query_params=baseRequest.getQueryParameters();
+        final Attributes old_attr=baseRequest.getAttributes();
+        final DispatcherType old_type=baseRequest.getDispatcherType();
+
+        try
+        {
+            baseRequest.setHandled(false);
+            baseRequest.setDispatcherType(dispatch);
+
+            if (_named!=null)
+            {
+                _contextHandler.handle(_named, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+            }
+            else
+            {
+                ForwardAttributes attr = new ForwardAttributes(old_attr);
+
+                //If we have already been forwarded previously, then keep using the established
+                //original value. Otherwise, this is the first forward and we need to establish the values.
+                //Note: the established value on the original request for pathInfo and
+                //for queryString is allowed to be null, but cannot be null for the other values.
+                if (old_attr.getAttribute(FORWARD_REQUEST_URI) != null)
+                {
+                    attr._pathInfo=(String)old_attr.getAttribute(FORWARD_PATH_INFO);
+                    attr._query=(String)old_attr.getAttribute(FORWARD_QUERY_STRING);
+                    attr._requestURI=(String)old_attr.getAttribute(FORWARD_REQUEST_URI);
+                    attr._contextPath=(String)old_attr.getAttribute(FORWARD_CONTEXT_PATH);
+                    attr._servletPath=(String)old_attr.getAttribute(FORWARD_SERVLET_PATH);
+                }
+                else
+                {
+                    attr._pathInfo=old_path_info;
+                    attr._query=old_query;
+                    attr._requestURI=old_uri;
+                    attr._contextPath=old_context_path;
+                    attr._servletPath=old_servlet_path;
+                }
+
+                baseRequest.setRequestURI(_uri);
+                baseRequest.setContextPath(_contextHandler.getContextPath());
+                baseRequest.setServletPath(null);
+                baseRequest.setPathInfo(_uri);
+                if (_query!=null)
+                    baseRequest.mergeQueryParameters(_query, true);
+                baseRequest.setAttributes(attr);
+
+                _contextHandler.handle(_path, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+
+                if (!baseRequest.getHttpChannelState().isAsync())
+                    commitResponse(response,baseRequest);
+            }
+        }
+        finally
+        {
+            baseRequest.setHandled(old_handled);
+            baseRequest.setRequestURI(old_uri);
+            baseRequest.setContextPath(old_context_path);
+            baseRequest.setServletPath(old_servlet_path);
+            baseRequest.setPathInfo(old_path_info);
+            baseRequest.setQueryString(old_query);
+            baseRequest.setQueryParameters(old_query_params);
+            baseRequest.resetParameters();
+            baseRequest.setAttributes(old_attr);
+            baseRequest.setDispatcherType(old_type);
+        }
+    }
+
+    private void commitResponse(ServletResponse response, Request baseRequest) throws IOException
+    {
+        if (baseRequest.getResponse().isWriting())
+        {
+            try
+            {
+                response.getWriter().close();
+            }
+            catch (IllegalStateException e)
+            {
+                response.getOutputStream().close();
+            }
+        }
+        else
+        {
+            try
+            {
+                response.getOutputStream().close();
+            }
+            catch (IllegalStateException e)
+            {
+                response.getWriter().close();
+            }
+        }
+    }
+
+    private class ForwardAttributes implements Attributes
+    {
+        final Attributes _attr;
+
+        String _requestURI;
+        String _contextPath;
+        String _servletPath;
+        String _pathInfo;
+        String _query;
+
+        ForwardAttributes(Attributes attributes)
+        {
+            _attr=attributes;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public Object getAttribute(String key)
+        {
+            if (Dispatcher.this._named==null)
+            {
+                if (key.equals(FORWARD_PATH_INFO))
+                    return _pathInfo;
+                if (key.equals(FORWARD_REQUEST_URI))
+                    return _requestURI;
+                if (key.equals(FORWARD_SERVLET_PATH))
+                    return _servletPath;
+                if (key.equals(FORWARD_CONTEXT_PATH))
+                    return _contextPath;
+                if (key.equals(FORWARD_QUERY_STRING))
+                    return _query;
+            }
+
+            if (key.startsWith(__INCLUDE_PREFIX))
+                return null;
+
+            return _attr.getAttribute(key);
+        }
+
+        @Override
+        public Enumeration<String> getAttributeNames()
+        {
+            HashSet<String> set=new HashSet<>();
+            Enumeration<String> e=_attr.getAttributeNames();
+            while(e.hasMoreElements())
+            {
+                String name=e.nextElement();
+                if (!name.startsWith(__INCLUDE_PREFIX) &&
+                    !name.startsWith(__FORWARD_PREFIX))
+                    set.add(name);
+            }
+
+            if (_named==null)
+            {
+                if (_pathInfo!=null)
+                    set.add(FORWARD_PATH_INFO);
+                else
+                    set.remove(FORWARD_PATH_INFO);
+                set.add(FORWARD_REQUEST_URI);
+                set.add(FORWARD_SERVLET_PATH);
+                set.add(FORWARD_CONTEXT_PATH);
+                if (_query!=null)
+                    set.add(FORWARD_QUERY_STRING);
+                else
+                    set.remove(FORWARD_QUERY_STRING);
+            }
+
+            return Collections.enumeration(set);
+        }
+
+        @Override
+        public void setAttribute(String key, Object value)
+        {
+            if (_named==null && key.startsWith("javax.servlet."))
+            {
+                if (key.equals(FORWARD_PATH_INFO))
+                    _pathInfo=(String)value;
+                else if (key.equals(FORWARD_REQUEST_URI))
+                    _requestURI=(String)value;
+                else if (key.equals(FORWARD_SERVLET_PATH))
+                    _servletPath=(String)value;
+                else if (key.equals(FORWARD_CONTEXT_PATH))
+                    _contextPath=(String)value;
+                else if (key.equals(FORWARD_QUERY_STRING))
+                    _query=(String)value;
+
+                else if (value==null)
+                    _attr.removeAttribute(key);
+                else
+                    _attr.setAttribute(key,value);
+            }
+            else if (value==null)
+                _attr.removeAttribute(key);
+            else
+                _attr.setAttribute(key,value);
+        }
+
+        @Override
+        public String toString()
+        {
+            return "FORWARD+"+_attr.toString();
+        }
+
+        @Override
+        public void clearAttributes()
+        {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public void removeAttribute(String name)
+        {
+            setAttribute(name,null);
+        }
+    }
+
+    private class IncludeAttributes implements Attributes
+    {
+        final Attributes _attr;
+
+        String _requestURI;
+        String _contextPath;
+        String _servletPath;
+        String _pathInfo;
+        String _query;
+
+        IncludeAttributes(Attributes attributes)
+        {
+            _attr=attributes;
+        }
+
+        @Override
+        public Object getAttribute(String key)
+        {
+            if (Dispatcher.this._named==null)
+            {
+                if (key.equals(INCLUDE_PATH_INFO))    return _pathInfo;
+                if (key.equals(INCLUDE_SERVLET_PATH)) return _servletPath;
+                if (key.equals(INCLUDE_CONTEXT_PATH)) return _contextPath;
+                if (key.equals(INCLUDE_QUERY_STRING)) return _query;
+                if (key.equals(INCLUDE_REQUEST_URI))  return _requestURI;
+            }
+            else if (key.startsWith(__INCLUDE_PREFIX))
+                    return null;
+
+
+            return _attr.getAttribute(key);
+        }
+
+        @Override
+        public Enumeration<String> getAttributeNames()
+        {
+            HashSet<String> set=new HashSet<>();
+            Enumeration<String> e=_attr.getAttributeNames();
+            while(e.hasMoreElements())
+            {
+                String name=e.nextElement();
+                if (!name.startsWith(__INCLUDE_PREFIX))
+                    set.add(name);
+            }
+
+            if (_named==null)
+            {
+                if (_pathInfo!=null)
+                    set.add(INCLUDE_PATH_INFO);
+                else
+                    set.remove(INCLUDE_PATH_INFO);
+                set.add(INCLUDE_REQUEST_URI);
+                set.add(INCLUDE_SERVLET_PATH);
+                set.add(INCLUDE_CONTEXT_PATH);
+                if (_query!=null)
+                    set.add(INCLUDE_QUERY_STRING);
+                else
+                    set.remove(INCLUDE_QUERY_STRING);
+            }
+
+            return Collections.enumeration(set);
+        }
+
+        @Override
+        public void setAttribute(String key, Object value)
+        {
+            if (_named==null && key.startsWith("javax.servlet."))
+            {
+                if (key.equals(INCLUDE_PATH_INFO))         _pathInfo=(String)value;
+                else if (key.equals(INCLUDE_REQUEST_URI))  _requestURI=(String)value;
+                else if (key.equals(INCLUDE_SERVLET_PATH)) _servletPath=(String)value;
+                else if (key.equals(INCLUDE_CONTEXT_PATH)) _contextPath=(String)value;
+                else if (key.equals(INCLUDE_QUERY_STRING)) _query=(String)value;
+                else if (value==null)
+                    _attr.removeAttribute(key);
+                else
+                    _attr.setAttribute(key,value);
+            }
+            else if (value==null)
+                _attr.removeAttribute(key);
+            else
+                _attr.setAttribute(key,value);
+        }
+
+        @Override
+        public String toString()
+        {
+            return "INCLUDE+"+_attr.toString();
+        }
+
+        @Override
+        public void clearAttributes()
+        {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public void removeAttribute(String name)
+        {
+            setAttribute(name,null);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/EncodingHttpWriter.java b/lib/jetty/org/eclipse/jetty/server/EncodingHttpWriter.java
new file mode 100644 (file)
index 0000000..88301d4
--- /dev/null
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+/**
+ *
+ */
+public class EncodingHttpWriter extends HttpWriter
+{
+    final Writer _converter;
+
+    /* ------------------------------------------------------------ */
+    public EncodingHttpWriter(HttpOutput out, String encoding)
+    {
+        super(out);
+        try
+        {
+            _converter = new OutputStreamWriter(_bytes, encoding);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (char[] s,int offset, int length) throws IOException
+    {
+        HttpOutput out = _out;
+        if (length==0 && out.isAllContentWritten())
+        {
+            out.close();
+            return;
+        }
+            
+        while (length > 0)
+        {
+            _bytes.reset();
+            int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
+
+            _converter.write(s, offset, chars);
+            _converter.flush();
+            _bytes.writeTo(out);
+            length-=chars;
+            offset+=chars;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ForwardedRequestCustomizer.java b/lib/jetty/org/eclipse/jetty/server/ForwardedRequestCustomizer.java
new file mode 100644 (file)
index 0000000..891e8ee
--- /dev/null
@@ -0,0 +1,291 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.net.InetSocketAddress;
+
+import javax.servlet.ServletRequest;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.server.HttpConfiguration.Customizer;
+
+
+/* ------------------------------------------------------------ */
+/** Customize Requests for Proxy Forwarding.
+ * <p>
+ * This customizer looks at at HTTP request for headers that indicate
+ * it has been forwarded by one or more proxies.  Specifically handled are:
+ * <ul>
+ * <li>X-Forwarded-Host</li>
+ * <li>X-Forwarded-Server</li>
+ * <li>X-Forwarded-For</li>
+ * <li>X-Forwarded-Proto</li>
+ * </ul>
+ * <p>If these headers are present, then the {@link Request} object is updated
+ * so that the proxy is not seen as the other end point of the connection on which
+ * the request came</p>
+ * <p>Headers can also be defined so that forwarded SSL Session IDs and Cipher
+ * suites may be customised</p> 
+ * @see <a href="http://en.wikipedia.org/wiki/X-Forwarded-For">Wikipedia: X-Forwarded-For</a>
+ */
+public class ForwardedRequestCustomizer implements Customizer
+{
+    private String _hostHeader;
+    private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
+    private String _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
+    private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
+    private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
+    private String _forwardedCipherSuiteHeader;
+    private String _forwardedSslSessionIdHeader;
+    
+
+    /* ------------------------------------------------------------ */
+    public String getHostHeader()
+    {
+        return _hostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
+     *
+     * @param hostHeader
+     *            The value of the host header to force.
+     */
+    public void setHostHeader(String hostHeader)
+    {
+        _hostHeader = hostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     *
+     * @see #setForwarded(boolean)
+     */
+    public String getForwardedHostHeader()
+    {
+        return _forwardedHostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedHostHeader
+     *            The header name for forwarded hosts (default x-forwarded-host)
+     */
+    public void setForwardedHostHeader(String forwardedHostHeader)
+    {
+        _forwardedHostHeader = forwardedHostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the header name for forwarded server.
+     */
+    public String getForwardedServerHeader()
+    {
+        return _forwardedServerHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedServerHeader
+     *            The header name for forwarded server (default x-forwarded-server)
+     */
+    public void setForwardedServerHeader(String forwardedServerHeader)
+    {
+        _forwardedServerHeader = forwardedServerHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the forwarded for header
+     */
+    public String getForwardedForHeader()
+    {
+        return _forwardedForHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedRemoteAddressHeader
+     *            The header name for forwarded for (default x-forwarded-for)
+     */
+    public void setForwardedForHeader(String forwardedRemoteAddressHeader)
+    {
+        _forwardedForHeader = forwardedRemoteAddressHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the forwardedProtoHeader.
+     *
+     * @return the forwardedProtoHeader (default X-Forwarded-For)
+     */
+    public String getForwardedProtoHeader()
+    {
+        return _forwardedProtoHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the forwardedProtoHeader.
+     *
+     * @param forwardedProtoHeader
+     *            the forwardedProtoHeader to set (default X-Forwarded-For)
+     */
+    public void setForwardedProtoHeader(String forwardedProtoHeader)
+    {
+        _forwardedProtoHeader = forwardedProtoHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The header name holding a forwarded cipher suite (default null)
+     */
+    public String getForwardedCipherSuiteHeader()
+    {
+        return _forwardedCipherSuiteHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedCipherSuite
+     *            The header name holding a forwarded cipher suite (default null)
+     */
+    public void setForwardedCipherSuiteHeader(String forwardedCipherSuite)
+    {
+        _forwardedCipherSuiteHeader = forwardedCipherSuite;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The header name holding a forwarded SSL Session ID (default null)
+     */
+    public String getForwardedSslSessionIdHeader()
+    {
+        return _forwardedSslSessionIdHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedSslSessionId
+     *            The header name holding a forwarded SSL Session ID (default null)
+     */
+    public void setForwardedSslSessionIdHeader(String forwardedSslSessionId)
+    {
+        _forwardedSslSessionIdHeader = forwardedSslSessionId;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void customize(Connector connector, HttpConfiguration config, Request request)
+    {
+        HttpFields httpFields = request.getHttpFields();
+
+        // Do SSL first
+        if (getForwardedCipherSuiteHeader()!=null)
+        {
+            String cipher_suite=httpFields.getStringField(getForwardedCipherSuiteHeader());
+            if (cipher_suite!=null)
+                request.setAttribute("javax.servlet.request.cipher_suite",cipher_suite);
+        }
+        if (getForwardedSslSessionIdHeader()!=null)
+        {
+            String ssl_session_id=httpFields.getStringField(getForwardedSslSessionIdHeader());
+            if(ssl_session_id!=null)
+            {
+                request.setAttribute("javax.servlet.request.ssl_session_id", ssl_session_id);
+                request.setScheme(HttpScheme.HTTPS.asString());
+            }
+        }
+
+        // Retrieving headers from the request
+        String forwardedHost = getLeftMostFieldValue(httpFields,getForwardedHostHeader());
+        String forwardedServer = getLeftMostFieldValue(httpFields,getForwardedServerHeader());
+        String forwardedFor = getLeftMostFieldValue(httpFields,getForwardedForHeader());
+        String forwardedProto = getLeftMostFieldValue(httpFields,getForwardedProtoHeader());
+
+        if (_hostHeader != null)
+        {
+            // Update host header
+            httpFields.put(HttpHeader.HOST.toString(),_hostHeader);
+            request.setServerName(null);
+            request.setServerPort(-1);
+            request.getServerName();
+        }
+        else if (forwardedHost != null)
+        {
+            // Update host header
+            httpFields.put(HttpHeader.HOST.toString(),forwardedHost);
+            request.setServerName(null);
+            request.setServerPort(-1);
+            request.getServerName();
+        }
+        else if (forwardedServer != null)
+        {
+            // Use provided server name
+            request.setServerName(forwardedServer);
+        }
+
+        if (forwardedFor != null)
+        {
+            request.setRemoteAddr(InetSocketAddress.createUnresolved(forwardedFor,request.getRemotePort()));
+        }
+
+        if (forwardedProto != null)
+        {
+            request.setScheme(forwardedProto);
+            if (forwardedProto.equals(config.getSecureScheme()))
+                request.setSecure(true);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected String getLeftMostFieldValue(HttpFields fields, String header)
+    {
+        if (header == null)
+            return null;
+
+        String headerValue = fields.getStringField(header);
+
+        if (headerValue == null)
+            return null;
+
+        int commaIndex = headerValue.indexOf(',');
+
+        if (commaIndex == -1)
+        {
+            // Single value
+            return headerValue;
+        }
+
+        // The left-most value is the farthest downstream client
+        return headerValue.substring(0,commaIndex);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Handler.java b/lib/jetty/org/eclipse/jetty/server/Handler.java
new file mode 100644 (file)
index 0000000..cfe7b9a
--- /dev/null
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.Destroyable;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* ------------------------------------------------------------ */
+/** A Jetty Server Handler.
+ *
+ * A Handler instance is required by a {@link Server} to handle incoming
+ * HTTP requests.  A Handler may: <ul>
+ * <li>Completely generate the HTTP Response</li>
+ * <li>Examine/modify the request and call another Handler (see {@link HandlerWrapper}).
+ * <li>Pass the request to one or more other Handlers (see {@link HandlerCollection}).
+ * </ul>
+ *
+ * Handlers are passed the servlet API request and response object, but are
+ * not Servlets.  The servlet container is implemented by handlers for
+ * context, security, session and servlet that modify the request object
+ * before passing it to the next stage of handling.
+ *
+ */
+@ManagedObject("Jetty Handler")
+public interface Handler extends LifeCycle, Destroyable
+{
+    /* ------------------------------------------------------------ */
+    /** Handle a request.
+     * @param target The target of the request - either a URI or a name.
+     * @param baseRequest The original unwrapped request object.
+     * @param request The request either as the {@link Request}
+     * object or a wrapper of that request. The {@link HttpChannel#getCurrentHttpChannel()}
+     * method can be used access the Request object if required.
+     * @param response The response as the {@link Response}
+     * object or a wrapper of that request. The {@link HttpChannel#getCurrentHttpChannel()}
+     * method can be used access the Response object if required.
+     * @throws IOException
+     * @throws ServletException
+     */
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException;
+
+    public void setServer(Server server);
+
+    @ManagedAttribute(value="the jetty server for this handler", readonly=true)
+    public Server getServer();
+
+    @ManagedOperation(value="destroy associated resources", impact="ACTION")
+    public void destroy();
+
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/server/HandlerContainer.java b/lib/jetty/org/eclipse/jetty/server/HandlerContainer.java
new file mode 100644 (file)
index 0000000..51fd479
--- /dev/null
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/**
+ * A Handler that contains other Handlers.
+ * <p>
+ * The contained handlers may be one (see @{link {@link org.eclipse.jetty.server.handler.HandlerWrapper})
+ * or many (see {@link org.eclipse.jetty.server.handler.HandlerList} or {@link org.eclipse.jetty.server.handler.HandlerCollection}. 
+ *
+ */
+@ManagedObject("Handler of Multiple Handlers")
+public interface HandlerContainer extends LifeCycle
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * @return array of handlers directly contained by this handler.
+     */
+    @ManagedAttribute("handlers in this container")
+    public Handler[] getHandlers();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return array of all handlers contained by this handler and it's children
+     */
+    @ManagedAttribute("all contained handlers")
+    public Handler[] getChildHandlers();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param byclass
+     * @return array of all handlers contained by this handler and it's children of the passed type.
+     */
+    public Handler[] getChildHandlersByClass(Class<?> byclass);
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param byclass
+     * @return first handler of all handlers contained by this handler and it's children of the passed type.
+     */
+    public <T extends Handler> T getChildHandlerByClass(Class<T> byclass);
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HostHeaderCustomizer.java b/lib/jetty/org/eclipse/jetty/server/HostHeaderCustomizer.java
new file mode 100644 (file)
index 0000000..b39b25d
--- /dev/null
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Objects;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Customizes requests that lack the {@code Host} header (for example, HTTP 1.0 requests).
+ * <p />
+ * In case of HTTP 1.0 requests that lack the {@code Host} header, the application may issue
+ * a redirect, and the {@code Location} header is usually constructed from the {@code Host}
+ * header; if the {@code Host} header is missing, the server may query the connector for its
+ * IP address in order to construct the {@code Location} header, and thus leak to clients
+ * internal IP addresses.
+ * <p />
+ * This {@link HttpConfiguration.Customizer} is configured with a {@code serverName} and
+ * optionally a {@code serverPort}.
+ * If the {@code Host} header is absent, the configured {@code serverName} will be set on
+ * the request so that {@link HttpServletRequest#getServerName()} will return that value,
+ * and likewise for {@code serverPort} and {@link HttpServletRequest#getServerPort()}.
+ */
+public class HostHeaderCustomizer implements HttpConfiguration.Customizer
+{
+    private final String serverName;
+    private final int serverPort;
+
+    /**
+     * @param serverName the {@code serverName} to set on the request (the {@code serverPort} will not be set)
+     */
+    public HostHeaderCustomizer(String serverName)
+    {
+        this(serverName, 0);
+    }
+
+    /**
+     * @param serverName the {@code serverName} to set on the request
+     * @param serverPort the {@code serverPort} to set on the request
+     */
+    public HostHeaderCustomizer(String serverName, int serverPort)
+    {
+        this.serverName = Objects.requireNonNull(serverName);
+        this.serverPort = serverPort;
+    }
+
+    @Override
+    public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
+    {
+        if (request.getHeader("Host") == null)
+        {
+            request.setServerName(serverName);
+            if (serverPort > 0)
+                request.setServerPort(serverPort);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpChannel.java b/lib/jetty/org/eclipse/jetty/server/HttpChannel.java
new file mode 100644 (file)
index 0000000..07b09ab
--- /dev/null
@@ -0,0 +1,860 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ChannelEndPoint;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.server.HttpChannelState.Action;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+/* ------------------------------------------------------------ */
+/** HttpChannel.
+ * Represents a single endpoint for HTTP semantic processing.
+ * The HttpChannel is both a HttpParser.RequestHandler, where it passively receives events from
+ * an incoming HTTP request, and a Runnable, where it actively takes control of the request/response
+ * life cycle and calls the application (perhaps suspending and resuming with multiple calls to run).
+ * The HttpChannel signals the switch from passive mode to active mode by returning true to one of the
+ * HttpParser.RequestHandler callbacks.   The completion of the active phase is signalled by a call to
+ * HttpTransport.completed().
+ *
+ */
+public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable, HttpParser.ProxyHandler
+{
+    private static final Logger LOG = Log.getLogger(HttpChannel.class);
+    private static final ThreadLocal<HttpChannel<?>> __currentChannel = new ThreadLocal<>();
+
+    /* ------------------------------------------------------------ */
+    /** Get the current channel that this thread is dispatched to.
+     * @see Request#getAttribute(String) for a more general way to access the HttpChannel
+     * @return the current HttpChannel or null
+     */
+    public static HttpChannel<?> getCurrentHttpChannel()
+    {
+        return __currentChannel.get();
+    }
+
+    protected static HttpChannel<?> setCurrentHttpChannel(HttpChannel<?> channel)
+    {
+        HttpChannel<?> last=__currentChannel.get();
+        if (channel==null)
+            __currentChannel.remove();
+        else 
+            __currentChannel.set(channel);
+        return last;
+    }
+
+    private final AtomicBoolean _committed = new AtomicBoolean();
+    private final AtomicInteger _requests = new AtomicInteger();
+    private final Connector _connector;
+    private final HttpConfiguration _configuration;
+    private final EndPoint _endPoint;
+    private final HttpTransport _transport;
+    private final HttpURI _uri;
+    private final HttpChannelState _state;
+    private final Request _request;
+    private final Response _response;
+    private HttpVersion _version = HttpVersion.HTTP_1_1;
+    private boolean _expect = false;
+    private boolean _expect100Continue = false;
+    private boolean _expect102Processing = false;
+
+    public HttpChannel(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<T> input)
+    {
+        _connector = connector;
+        _configuration = configuration;
+        _endPoint = endPoint;
+        _transport = transport;
+
+        _uri = new HttpURI(URIUtil.__CHARSET);
+        _state = new HttpChannelState(this);
+        input.init(_state);
+        _request = new Request(this, input);
+        _response = new Response(this, new HttpOutput(this));
+    }
+
+    public HttpChannelState getState()
+    {
+        return _state;
+    }
+
+    public HttpVersion getHttpVersion()
+    {
+        return _version;
+    }
+    /**
+     * @return the number of requests handled by this connection
+     */
+    public int getRequests()
+    {
+        return _requests.get();
+    }
+
+    public Connector getConnector()
+    {
+        return _connector;
+    }
+
+    public HttpTransport getHttpTransport()
+    {
+        return _transport;
+    }
+    
+    public ByteBufferPool getByteBufferPool()
+    {
+        return _connector.getByteBufferPool();
+    }
+
+    public HttpConfiguration getHttpConfiguration()
+    {
+        return _configuration;
+    }
+
+    public Server getServer()
+    {
+        return _connector.getServer();
+    }
+
+    public Request getRequest()
+    {
+        return _request;
+    }
+
+    public Response getResponse()
+    {
+        return _response;
+    }
+
+    public EndPoint getEndPoint()
+    {
+        return _endPoint;
+    }
+
+    public InetSocketAddress getLocalAddress()
+    {
+        return _endPoint.getLocalAddress();
+    }
+
+    public InetSocketAddress getRemoteAddress()
+    {
+        return _endPoint.getRemoteAddress();
+    }
+
+    @Override
+    public int getHeaderCacheSize()
+    {
+        return _configuration.getHeaderCacheSize();
+    }
+
+    /**
+     * If the associated response has the Expect header set to 100 Continue,
+     * then accessing the input stream indicates that the handler/servlet
+     * is ready for the request body and thus a 100 Continue response is sent.
+     *
+     * @throws IOException if the InputStream cannot be created
+     */
+    public void continue100(int available) throws IOException
+    {
+        // If the client is expecting 100 CONTINUE, then send it now.
+        // TODO: consider using an AtomicBoolean ?
+        if (isExpecting100Continue())
+        {
+            _expect100Continue = false;
+
+            // is content missing?
+            if (available == 0)
+            {
+                if (_response.isCommitted())
+                    throw new IOException("Committed before 100 Continues");
+
+                // TODO: break this dependency with HttpGenerator
+                boolean committed = sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false);
+                if (!committed)
+                    throw new IOException("Concurrent commit while trying to send 100-Continue");
+            }
+        }
+    }
+
+    public void reset()
+    {
+        _committed.set(false);
+        _expect = false;
+        _expect100Continue = false;
+        _expect102Processing = false;
+        _request.recycle();
+        _response.recycle();
+        _uri.clear();
+    }
+
+    @Override
+    public void run()
+    {
+        handle();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if the channel is ready to continue handling (ie it is not suspended)
+     */
+    public boolean handle()
+    {
+        LOG.debug("{} handle enter", this);
+
+        final HttpChannel<?>last = setCurrentHttpChannel(this);
+
+        String threadName = null;
+        if (LOG.isDebugEnabled())
+        {
+            threadName = Thread.currentThread().getName();
+            Thread.currentThread().setName(threadName + " - " + _uri);
+        }
+
+        HttpChannelState.Action action = _state.handling();
+        try
+        {
+            // Loop here to handle async request redispatches.
+            // The loop is controlled by the call to async.unhandle in the
+            // finally block below.  Unhandle will return false only if an async dispatch has
+            // already happened when unhandle is called.
+            loop: while (action.ordinal()<HttpChannelState.Action.WAIT.ordinal() && getServer().isRunning())
+            {
+                boolean error=false;
+                try
+                {
+                    LOG.debug("{} action {}",this,action);
+
+                    switch(action)
+                    {
+                        case REQUEST_DISPATCH:
+                            _request.setHandled(false);
+                            _response.getHttpOutput().reopen();
+                            _request.setDispatcherType(DispatcherType.REQUEST);
+
+                            for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers())
+                                customizer.customize(getConnector(),_configuration,_request);
+                            getServer().handle(this);
+                            break;
+
+                        case ASYNC_DISPATCH:
+                            _request.setHandled(false);
+                            _response.getHttpOutput().reopen();
+                            _request.setDispatcherType(DispatcherType.ASYNC);
+                            getServer().handleAsync(this);
+                            break;
+
+                        case ASYNC_EXPIRED:
+                            _request.setHandled(false);
+                            _response.getHttpOutput().reopen();
+                            _request.setDispatcherType(DispatcherType.ERROR);
+
+                            Throwable ex=_state.getAsyncContextEvent().getThrowable();
+                            String reason="Async Timeout";
+                            if (ex!=null)
+                            {
+                                reason="Async Exception";
+                                _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,ex);
+                            }
+                            _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500));
+                            _request.setAttribute(RequestDispatcher.ERROR_MESSAGE,reason);
+                            _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,_request.getRequestURI());
+
+                            _response.setStatusWithReason(500,reason);
+
+                            
+                            ErrorHandler eh = ErrorHandler.getErrorHandler(getServer(),_state.getContextHandler());                                
+                            if (eh instanceof ErrorHandler.ErrorPageMapper)
+                            {
+                                String error_page=((ErrorHandler.ErrorPageMapper)eh).getErrorPage((HttpServletRequest)_state.getAsyncContextEvent().getSuppliedRequest());
+                                if (error_page!=null)
+                                    _state.getAsyncContextEvent().setDispatchPath(error_page);
+                            }
+
+                            getServer().handleAsync(this);
+                            break;
+
+                        case READ_CALLBACK:
+                        {
+                            ContextHandler handler=_state.getContextHandler();
+                            if (handler!=null)
+                                handler.handle(_request.getHttpInput());
+                            else
+                                _request.getHttpInput().run();
+                            break;
+                        }
+
+                        case WRITE_CALLBACK:
+                        {
+                            ContextHandler handler=_state.getContextHandler();
+
+                            if (handler!=null)
+                                handler.handle(_response.getHttpOutput());
+                            else
+                                _response.getHttpOutput().run();
+                            break;
+                        }   
+
+                        default:
+                            break loop;
+
+                    }
+                }
+                catch (Error e)
+                {
+                    if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
+                        LOG.ignore(e);
+                    else
+                    {
+                        error=true;
+                        throw e;
+                    }
+                }
+                catch (Exception e)
+                {
+                    error=true;
+                    if (e instanceof EofException)
+                        LOG.debug(e);
+                    else
+                        LOG.warn(String.valueOf(_uri), e);
+                    _state.error(e);
+                    _request.setHandled(true);
+                    handleException(e);
+                }
+                finally
+                {
+                    if (error && _state.isAsyncStarted())
+                        _state.errorComplete();
+                    action = _state.unhandle();
+                }
+            }
+
+        }
+        finally
+        {
+            setCurrentHttpChannel(last);
+            if (threadName != null && LOG.isDebugEnabled())
+                Thread.currentThread().setName(threadName);
+        }
+
+        if (action==Action.COMPLETE)
+        {
+            try
+            {
+                _state.completed();
+
+                if (!_response.isCommitted() && !_request.isHandled())
+                    _response.sendError(404);
+                else
+                    // Complete generating the response
+                    _response.closeOutput();
+            }
+            catch(EofException|ClosedChannelException e)
+            {
+                LOG.debug(e);
+            }
+            catch(Exception e)
+            {
+                LOG.warn("complete failed",e);
+            }
+            finally
+            {
+                _request.setHandled(true);
+                _transport.completed();
+            }
+        }
+
+        LOG.debug("{} handle exit, result {}", this, action);
+
+        return action!=Action.WAIT;
+    }
+
+    /**
+     * <p>Sends an error 500, performing a special logic to detect whether the request is suspended,
+     * to avoid concurrent writes from the application.</p>
+     * <p>It may happen that the application suspends, and then throws an exception, while an application
+     * spawned thread writes the response content; in such case, we attempt to commit the error directly
+     * bypassing the {@link ErrorHandler} mechanisms and the response OutputStream.</p>
+     *
+     * @param x the Throwable that caused the problem
+     */
+    protected void handleException(Throwable x)
+    {
+        try
+        {
+            _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,x);
+            _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,x.getClass());
+            if (_state.isSuspended())
+            {
+                HttpFields fields = new HttpFields();
+                fields.add(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
+                ResponseInfo info = new ResponseInfo(_request.getHttpVersion(), fields, 0, HttpStatus.INTERNAL_SERVER_ERROR_500, null, _request.isHead());
+                boolean committed = sendResponse(info, null, true);
+                if (!committed)
+                    LOG.warn("Could not send response error 500: "+x);
+                _request.getAsyncContext().complete();
+            }
+            else if (isCommitted())
+            {
+                _transport.abort();
+                if (!(x instanceof EofException))
+                    LOG.warn("Could not send response error 500: "+x);
+            }
+            else
+            {
+                _response.setHeader(HttpHeader.CONNECTION.asString(),HttpHeaderValue.CLOSE.asString());
+                _response.sendError(500, x.getMessage());
+            }
+        }
+        catch (IOException e)
+        {
+            // We tried our best, just log
+            LOG.debug("Could not commit response error 500", e);
+        }
+    }
+
+    public boolean isExpecting100Continue()
+    {
+        return _expect100Continue;
+    }
+
+    public boolean isExpecting102Processing()
+    {
+        return _expect102Processing;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{r=%s,a=%s,uri=%s}",
+                getClass().getSimpleName(),
+                hashCode(),
+                _requests,
+                _state.getState(),
+                _state.getState()==HttpChannelState.State.IDLE?"-":_request.getRequestURI()
+            );
+    }
+
+    @Override
+    public void proxied(String protocol, String sAddr, String dAddr, int sPort, int dPort)
+    {
+        _request.setAttribute("PROXY", protocol);
+        _request.setServerName(sAddr);
+        _request.setServerPort(dPort);
+        _request.setRemoteAddr(InetSocketAddress.createUnresolved(sAddr,sPort));
+    }
+    
+    @Override
+    public boolean startRequest(HttpMethod httpMethod, String method, ByteBuffer uri, HttpVersion version)
+    {
+        _expect = false;
+        _expect100Continue = false;
+        _expect102Processing = false;
+
+        _request.setTimeStamp(System.currentTimeMillis());
+        _request.setMethod(httpMethod, method);
+
+        if (httpMethod == HttpMethod.CONNECT)
+            _uri.parseConnect(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
+        else
+            _uri.parse(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
+        _request.setUri(_uri);
+
+        String path;
+        try
+        {
+            path = _uri.getDecodedPath();
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Failed UTF-8 decode for request path, trying ISO-8859-1");
+            LOG.ignore(e);
+            path = _uri.getDecodedPath(StandardCharsets.ISO_8859_1);
+        }
+        
+        String info = URIUtil.canonicalPath(path);
+
+        if (info == null)
+        {
+            if( path==null && _uri.getScheme()!=null &&_uri.getHost()!=null)
+            {
+                info = "/";
+                _request.setRequestURI("");
+            }
+            else
+            {
+                badMessage(400,null);
+                return true;
+            }
+        }
+        _request.setPathInfo(info);
+        _version = version == null ? HttpVersion.HTTP_0_9 : version;
+        _request.setHttpVersion(_version);
+
+        return false;
+    }
+
+    @Override
+    public boolean parsedHeader(HttpField field)
+    {
+        HttpHeader header=field.getHeader();
+        String value=field.getValue();
+        if (value == null)
+            value = "";
+        if (header != null)
+        {
+            switch (header)
+            {
+                case EXPECT:
+                    if (_version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
+                    {
+                        HttpHeaderValue expect = HttpHeaderValue.CACHE.get(value);
+                        switch (expect == null ? HttpHeaderValue.UNKNOWN : expect)
+                        {
+                            case CONTINUE:
+                                _expect100Continue = true;
+                                break;
+
+                            case PROCESSING:
+                                _expect102Processing = true;
+                                break;
+
+                            default:
+                                String[] values = value.split(",");
+                                for (int i = 0; values != null && i < values.length; i++)
+                                {
+                                    expect = HttpHeaderValue.CACHE.get(values[i].trim());
+                                    if (expect == null)
+                                        _expect = true;
+                                    else
+                                    {
+                                        switch (expect)
+                                        {
+                                            case CONTINUE:
+                                                _expect100Continue = true;
+                                                break;
+                                            case PROCESSING:
+                                                _expect102Processing = true;
+                                                break;
+                                            default:
+                                                _expect = true;
+                                        }
+                                    }
+                                }
+                        }
+                    }
+                    break;
+
+                case CONTENT_TYPE:
+                    MimeTypes.Type mime = MimeTypes.CACHE.get(value);
+                    String charset = (mime == null || mime.getCharset() == null) ? MimeTypes.getCharsetFromContentType(value) : mime.getCharset().toString();
+                    if (charset != null)
+                        _request.setCharacterEncodingUnchecked(charset);
+                    break;
+                default:
+            }
+        }
+
+        if (field.getName()!=null)
+            _request.getHttpFields().add(field);
+        return false;
+    }
+
+    @Override
+    public boolean parsedHostHeader(String host, int port)
+    {
+        if (_uri.getHost()==null)
+        {
+            _request.setServerName(host);
+            _request.setServerPort(port);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean headerComplete()
+    {
+        _requests.incrementAndGet();
+        switch (_version)
+        {
+            case HTTP_0_9:
+                break;
+
+            case HTTP_1_0:
+                if (_configuration.getSendDateHeader())
+                    _response.getHttpFields().put(_connector.getServer().getDateField());
+                break;
+
+            case HTTP_1_1:
+                if (_configuration.getSendDateHeader())
+                    _response.getHttpFields().put(_connector.getServer().getDateField());
+
+                if (_expect)
+                {
+                    badMessage(HttpStatus.EXPECTATION_FAILED_417,null);
+                    return true;
+                }
+
+                break;
+
+            default:
+                throw new IllegalStateException();
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean content(T item)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} content {}", this, item);
+        @SuppressWarnings("unchecked")
+        HttpInput<T> input = (HttpInput<T>)_request.getHttpInput();
+        input.content(item);
+
+        return false;
+    }
+
+    @Override
+    public boolean messageComplete()
+    {
+        LOG.debug("{} messageComplete", this);
+        _request.getHttpInput().messageComplete();
+        return true;
+    }
+
+    @Override
+    public void earlyEOF()
+    {
+        _request.getHttpInput().earlyEOF();
+    }
+
+    @Override
+    public void badMessage(int status, String reason)
+    {
+        if (status < 400 || status > 599)
+            status = HttpStatus.BAD_REQUEST_400;
+
+        try
+        {
+            if (_state.handling()==Action.REQUEST_DISPATCH)
+            {
+                ByteBuffer content=null;
+                HttpFields fields=new HttpFields();
+
+                ErrorHandler handler=getServer().getBean(ErrorHandler.class);
+                if (handler!=null)
+                    content=handler.badMessageError(status,reason,fields);
+
+                sendResponse(new ResponseInfo(HttpVersion.HTTP_1_1,fields,0,status,reason,false),content ,true);
+            }
+        }
+        catch (IOException e)
+        {
+            LOG.debug(e);
+        }
+        finally
+        {
+            if (_state.unhandle()==Action.COMPLETE)
+                _state.completed();
+            else 
+                throw new IllegalStateException();
+        }
+    }
+
+    protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete, final Callback callback)
+    {
+        // TODO check that complete only set true once by changing _committed to AtomicRef<Enum>
+        boolean committing = _committed.compareAndSet(false, true);
+        if (committing)
+        {
+            // We need an info to commit
+            if (info==null)
+                info = _response.newResponseInfo();
+
+            // wrap callback to process 100 or 500 responses
+            final int status=info.getStatus();
+            final Callback committed = (status<200&&status>=100)?new Commit100Callback(callback):new CommitCallback(callback);
+
+            // committing write
+            _transport.send(info, content, complete, committed);
+        }
+        else if (info==null)
+        {
+            // This is a normal write
+            _transport.send(content, complete, callback);
+        }
+        else
+        {
+            callback.failed(new IllegalStateException("committed"));
+        }
+        return committing;
+    }
+
+    protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
+    {
+        try(Blocker blocker = _response.getHttpOutput().acquireWriteBlockingCallback())
+        {
+            boolean committing = sendResponse(info,content,complete,blocker);
+            blocker.block();
+            return committing;
+        }
+    }
+
+    public boolean isCommitted()
+    {
+        return _committed.get();
+    }
+
+    /**
+     * <p>Non-Blocking write, committing the response if needed.</p>
+     *
+     * @param content  the content buffer to write
+     * @param complete whether the content is complete for the response
+     * @param callback Callback when complete or failed
+     */
+    protected void write(ByteBuffer content, boolean complete, Callback callback)
+    {
+        sendResponse(null,content,complete,callback);
+    }
+
+    protected void execute(Runnable task)
+    {
+        _connector.getExecutor().execute(task);
+    }
+
+    public Scheduler getScheduler()
+    {
+        return _connector.getScheduler();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the HttpChannel can efficiently use direct buffer (typically this means it is not over SSL or a multiplexed protocol)
+     */
+    public boolean useDirectBuffers()
+    {
+        return getEndPoint() instanceof ChannelEndPoint;
+    }
+
+    /**
+     * If a write or similar to this channel fails this method should be called. The standard implementation
+     * of {@link #failed()} is a noop. But the different implementations of HttpChannel might want to take actions.
+     */
+    public void failed()
+    {
+    }
+
+    private class CommitCallback implements Callback
+    {
+        private final Callback _callback;
+
+        private CommitCallback(Callback callback)
+        {
+            _callback = callback;
+        }
+
+        @Override
+        public void succeeded()
+        {
+            _callback.succeeded();
+        }
+
+        @Override
+        public void failed(final Throwable x)
+        {
+            if (x instanceof EofException || x instanceof ClosedChannelException)
+            {
+                LOG.debug(x);
+                _callback.failed(x);
+                _response.getHttpOutput().closed();
+            }
+            else
+            {
+                LOG.warn("Commit failed",x);
+                _transport.send(HttpGenerator.RESPONSE_500_INFO,null,true,new Callback()
+                {
+                    @Override
+                    public void succeeded()
+                    {
+                        _callback.failed(x);
+                        _response.getHttpOutput().closed();
+                    }
+
+                    @Override
+                    public void failed(Throwable th)
+                    {
+                        LOG.ignore(th);
+                        _callback.failed(x);
+                        _response.getHttpOutput().closed();
+                    }
+                });
+            }
+        }
+    }
+
+    private class Commit100Callback extends CommitCallback
+    {
+        private Commit100Callback(Callback callback)
+        {
+            super(callback);
+        }
+
+        @Override
+        public void succeeded()
+        {
+             _committed.set(false);
+             super.succeeded();
+        }
+
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpChannelState.java b/lib/jetty/org/eclipse/jetty/server/HttpChannelState.java
new file mode 100644 (file)
index 0000000..7ff4d5a
--- /dev/null
@@ -0,0 +1,714 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.AsyncListener;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * Implementation of AsyncContext interface that holds the state of request-response cycle.
+ */
+public class HttpChannelState
+{
+    private static final Logger LOG = Log.getLogger(HttpChannelState.class);
+
+    private final static long DEFAULT_TIMEOUT=30000L;
+
+    /** The dispatched state of the HttpChannel, used to control the overall livecycle
+     */
+    public enum State
+    {
+        IDLE,             // Idle request
+        DISPATCHED,       // Request dispatched to filter/servlet
+        ASYNC_WAIT,       // Suspended and parked
+        ASYNC_WOKEN,      // A thread has been dispatch to handle from ASYNCWAIT
+        ASYNC_IO,         // Has been dispatched for async IO
+        COMPLETING,       // Request is completable
+        COMPLETED         // Request is complete
+    }
+
+    /**
+     * The actions to take as the channel moves from state to state.
+     */
+    public enum Action
+    {
+        REQUEST_DISPATCH, // handle a normal request dispatch  
+        ASYNC_DISPATCH,   // handle an async request dispatch
+        ASYNC_EXPIRED,    // handle an async timeout
+        WRITE_CALLBACK,   // handle an IO write callback
+        READ_CALLBACK,    // handle an IO read callback
+        WAIT,             // Wait for further events 
+        COMPLETE          // Complete the channel
+    }
+    
+    /**
+     * The state of the servlet async API.  This can lead or follow the 
+     * channel dispatch state and also includes reasons such as expired,
+     * dispatched or completed.
+     */
+    public enum Async
+    {
+        STARTED,
+        DISPATCH,
+        COMPLETE,
+        EXPIRING,
+        EXPIRED
+    }
+
+    private final boolean DEBUG=LOG.isDebugEnabled();
+    private final HttpChannel<?> _channel;
+
+    private List<AsyncListener> _asyncListeners;
+    private State _state;
+    private Async _async;
+    private boolean _initial;
+    private boolean _asyncRead;
+    private boolean _asyncWrite;
+    private long _timeoutMs=DEFAULT_TIMEOUT;
+    private AsyncContextEvent _event;
+
+    protected HttpChannelState(HttpChannel<?> channel)
+    {
+        _channel=channel;
+        _state=State.IDLE;
+        _async=null;
+        _initial=true;
+    }
+
+    public State getState()
+    {
+        synchronized(this)
+        {
+            return _state;
+        }
+    }
+
+    public void addListener(AsyncListener listener)
+    {
+        synchronized(this)
+        {
+            if (_asyncListeners==null)
+                _asyncListeners=new ArrayList<>();
+            _asyncListeners.add(listener);
+        }
+    }
+
+    public void setTimeout(long ms)
+    {
+        synchronized(this)
+        {
+            _timeoutMs=ms;
+        }
+    }
+
+    public long getTimeout()
+    {
+        synchronized(this)
+        {
+            return _timeoutMs;
+        }
+    }
+
+    public AsyncContextEvent getAsyncContextEvent()
+    {
+        synchronized(this)
+        {
+            return _event;
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        synchronized (this)
+        {
+            return String.format("%s@%x{s=%s i=%b a=%s}",getClass().getSimpleName(),hashCode(),_state,_initial,_async);
+        }
+    }
+
+    public String getStatusString()
+    {
+        synchronized (this)
+        {
+            return String.format("s=%s i=%b a=%s",_state,_initial,_async);
+        }
+    }
+
+    /**
+     * @return Next handling of the request should proceed
+     */
+    protected Action handling()
+    {
+        synchronized (this)
+        {
+            if(DEBUG)
+                LOG.debug("{} handling {}",this,_state);
+            switch(_state)
+            {
+                case IDLE:
+                    _initial=true;
+                    _state=State.DISPATCHED;
+                    return Action.REQUEST_DISPATCH;
+
+                case COMPLETING:
+                    return Action.COMPLETE;
+
+                case COMPLETED:
+                    return Action.WAIT;
+
+                case ASYNC_WOKEN:
+                    if (_asyncRead)
+                    {
+                        _state=State.ASYNC_IO;
+                        _asyncRead=false;
+                        return Action.READ_CALLBACK;
+                    }
+                    if (_asyncWrite)
+                    {
+                        _state=State.ASYNC_IO;
+                        _asyncWrite=false;
+                        return Action.WRITE_CALLBACK;
+                    }
+                    
+                    if (_async!=null)
+                    {
+                        Async async=_async;
+                        switch(async)
+                        {
+                            case COMPLETE:
+                                _state=State.COMPLETING;
+                                return Action.COMPLETE;
+                            case DISPATCH:
+                                _state=State.DISPATCHED;
+                                _async=null;
+                                return Action.ASYNC_DISPATCH;
+                            case EXPIRING:
+                                break;
+                            case EXPIRED:
+                                _state=State.DISPATCHED;
+                                _async=null;
+                                return Action.ASYNC_EXPIRED;
+                            case STARTED:
+                                // TODO
+                                if (DEBUG)
+                                    LOG.debug("TODO Fix this double dispatch",new IllegalStateException(this
+                                            .getStatusString()));
+                                return Action.WAIT;
+                        }
+                    }
+                    
+                    return Action.WAIT;
+
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+    }
+
+    public void startAsync(AsyncContextEvent event)
+    {
+        final List<AsyncListener> lastAsyncListeners;
+        
+        synchronized (this)
+        {
+            if (_state!=State.DISPATCHED || _async!=null)
+                throw new IllegalStateException(this.getStatusString());
+            
+            _async=Async.STARTED;
+            _event=event;
+            lastAsyncListeners=_asyncListeners;
+            _asyncListeners=null;
+        }
+
+        if (lastAsyncListeners!=null)
+        {
+            for (AsyncListener listener : lastAsyncListeners)
+            {
+                try
+                {
+                    listener.onStartAsync(event);
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+    }
+
+    protected void error(Throwable th)
+    {
+        synchronized (this)
+        {
+            if (_event!=null)
+                _event.setThrowable(th);
+        }
+    }
+
+    /**
+     * Signal that the HttpConnection has finished handling the request.
+     * For blocking connectors, this call may block if the request has
+     * been suspended (startAsync called).
+     * @return next actions
+     * be handled again (eg because of a resume that happened before unhandle was called)
+     */
+    protected Action unhandle()
+    {
+        synchronized (this)
+        {
+            if(DEBUG)
+                LOG.debug("{} unhandle {}",this,_state);
+            
+            switch(_state)
+            {
+                case DISPATCHED:
+                case ASYNC_IO:
+                    break;
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+
+            if (_asyncRead)
+            {
+                _state=State.ASYNC_IO;
+                _asyncRead=false;
+                return Action.READ_CALLBACK;
+            }
+            
+            if (_asyncWrite)
+            {
+                _asyncWrite=false;
+                _state=State.ASYNC_IO;
+                return Action.WRITE_CALLBACK;
+            }
+
+            if (_async!=null)
+            {
+                _initial=false;
+                switch(_async)
+                {
+                    case COMPLETE:
+                        _state=State.COMPLETING;
+                        _async=null;
+                        return Action.COMPLETE;
+                    case DISPATCH:
+                        _state=State.DISPATCHED;
+                        _async=null;
+                        return Action.ASYNC_DISPATCH;
+                    case EXPIRED:
+                        _state=State.DISPATCHED;
+                        _async=null;
+                        return Action.ASYNC_EXPIRED;
+                    case EXPIRING:
+                    case STARTED:
+                        scheduleTimeout();
+                        _state=State.ASYNC_WAIT;
+                        return Action.WAIT;
+                }
+            }
+            
+            _state=State.COMPLETING;
+            return Action.COMPLETE;
+        }
+    }
+
+    public void dispatch(ServletContext context, String path)
+    {
+        boolean dispatch;
+        synchronized (this)
+        {
+            if (_async!=Async.STARTED && _async!=Async.EXPIRING)
+                throw new IllegalStateException("AsyncContext#dispath "+this.getStatusString());
+            _async=Async.DISPATCH;
+            
+            if (context!=null)
+                _event.setDispatchContext(context);
+            if (path!=null)
+                _event.setDispatchPath(path);
+           
+            switch(_state)
+            {
+                case DISPATCHED:
+                case ASYNC_IO:
+                    dispatch=false;
+                    break;
+                case ASYNC_WAIT:
+                    _state=State.ASYNC_WOKEN;
+                    dispatch=true;
+                    break;
+                case ASYNC_WOKEN:
+                    dispatch=false;
+                    break;
+                default:
+                    LOG.warn("async dispatched when complete {}",this);
+                    dispatch=false;
+                    break;
+            }
+        }
+
+        cancelTimeout();
+        if (dispatch)
+            scheduleDispatch();
+    }
+
+    protected void expired()
+    {
+        final List<AsyncListener> aListeners;
+        AsyncContextEvent event;
+        synchronized (this)
+        {
+            if (_async!=Async.STARTED)
+                return;
+            _async=Async.EXPIRING;
+            event=_event;
+            aListeners=_asyncListeners;
+        }
+
+        if (aListeners!=null)
+        {
+            for (AsyncListener listener : aListeners)
+            {
+                try
+                {
+                    listener.onTimeout(event);
+                }
+                catch(Exception e)
+                {
+                    LOG.debug(e);
+                    event.setThrowable(e);
+                    _channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
+                    break;
+                }
+            }
+        }
+        
+        boolean dispatch=false;
+        synchronized (this)
+        {
+            if (_async==Async.EXPIRING)
+            {
+                _async=Async.EXPIRED;
+                if (_state==State.ASYNC_WAIT)
+                {
+                    _state=State.ASYNC_WOKEN;
+                    dispatch=true;
+                }
+            }
+        }
+
+        if (dispatch)
+            scheduleDispatch();
+    }
+
+    public void complete()
+    {
+        // just like resume, except don't set _dispatched=true;
+        boolean handle=false;
+        synchronized (this)
+        {
+            if (_async!=Async.STARTED && _async!=Async.EXPIRING)
+                throw new IllegalStateException(this.getStatusString());
+            _async=Async.COMPLETE;
+            if (_state==State.ASYNC_WAIT)
+            {
+                handle=true;
+                _state=State.ASYNC_WOKEN;
+            }
+        }
+
+        cancelTimeout();
+        if (handle)
+        {
+            ContextHandler handler=getContextHandler();
+            if (handler!=null)
+                handler.handle(_channel);
+            else
+                _channel.handle();
+        }
+    }
+
+    public void errorComplete()
+    {
+        synchronized (this)
+        {
+            _async=Async.COMPLETE;
+            _event.setDispatchContext(null);
+            _event.setDispatchPath(null);
+        }
+
+        cancelTimeout();
+    }
+
+    protected void completed()
+    {
+        final List<AsyncListener> aListeners;
+        final AsyncContextEvent event;
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case COMPLETING:
+                    _state=State.COMPLETED;
+                    aListeners=_asyncListeners;
+                    event=_event;
+                    break;
+
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+
+        if (event!=null)
+        {
+            if (aListeners!=null)
+            {
+                if (event.getThrowable()!=null)
+                {
+                    event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
+                    event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage());
+                }
+
+                for (AsyncListener listener : aListeners)
+                {
+                    try
+                    {
+                        if (event.getThrowable()!=null)
+                            listener.onError(event);
+                        else
+                            listener.onComplete(event);
+                    }
+                    catch(Exception e)
+                    {
+                        LOG.warn(e);
+                    }
+                }
+            }
+
+            event.completed();
+        }
+    }
+
+    protected void recycle()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case DISPATCHED:
+                case ASYNC_IO:
+                    throw new IllegalStateException(getStatusString());
+                default:
+                    break;
+            }
+            _asyncListeners=null;
+            _state=State.IDLE;
+            _async=null;
+            _initial=true;
+            _asyncRead=false;
+            _asyncWrite=false;
+            _timeoutMs=DEFAULT_TIMEOUT;
+            cancelTimeout();
+            _event=null;
+        }
+    }
+
+    protected void scheduleDispatch()
+    {
+        _channel.execute(_channel);
+    }
+
+    protected void scheduleTimeout()
+    {
+        Scheduler scheduler = _channel.getScheduler();
+        if (scheduler!=null && _timeoutMs>0)
+            _event.setTimeoutTask(scheduler.schedule(_event,_timeoutMs,TimeUnit.MILLISECONDS));
+    }
+
+    protected void cancelTimeout()
+    {
+        final AsyncContextEvent event;
+        synchronized (this)
+        { 
+            event=_event;
+        }
+        if (event!=null)
+            event.cancelTimeoutTask();
+    }
+
+    public boolean isExpired()
+    {
+        synchronized (this)
+        {
+            return _async==Async.EXPIRED;
+        }
+    }
+
+    public boolean isInitial()
+    {
+        synchronized(this)
+        {
+            return _initial;
+        }
+    }
+
+    public boolean isSuspended()
+    {
+        synchronized(this)
+        {
+            return _state==State.ASYNC_WAIT || _state==State.DISPATCHED && _async==Async.STARTED;
+        }
+    }
+
+    boolean isCompleting()
+    {
+        synchronized (this)
+        {
+            return _state==State.COMPLETING;
+        }
+    }
+
+    boolean isCompleted()
+    {
+        synchronized (this)
+        {
+            return _state == State.COMPLETED;
+        }
+    }
+
+    public boolean isAsyncStarted()
+    {
+        synchronized (this)
+        {    
+            if (_state==State.DISPATCHED)
+                return _async!=null;
+            return _async==Async.STARTED || _async==Async.EXPIRING;
+        }
+    }
+
+    public boolean isAsync()
+    {
+        synchronized (this)
+        {
+            return !_initial || _async!=null;
+        }
+    }
+
+    public Request getBaseRequest()
+    {
+        return _channel.getRequest();
+    }
+
+    public HttpChannel<?> getHttpChannel()
+    {
+        return _channel;
+    }
+
+    public ContextHandler getContextHandler()
+    {
+        final AsyncContextEvent event;
+        synchronized (this)
+        { 
+            event=_event;
+        }
+       
+        if (event!=null)
+        {
+            Context context=((Context)event.getServletContext());
+            if (context!=null)
+                return context.getContextHandler();
+        }
+        return null;
+    }
+
+    public ServletResponse getServletResponse()
+    {
+        final AsyncContextEvent event;
+        synchronized (this)
+        { 
+            event=_event;
+        }
+        if (event!=null && event.getSuppliedResponse()!=null)
+            return event.getSuppliedResponse();
+        return _channel.getResponse();
+    }
+
+    public Object getAttribute(String name)
+    {
+        return _channel.getRequest().getAttribute(name);
+    }
+
+    public void removeAttribute(String name)
+    {
+        _channel.getRequest().removeAttribute(name);
+    }
+
+    public void setAttribute(String name, Object attribute)
+    {
+        _channel.getRequest().setAttribute(name,attribute);
+    }
+
+    public void onReadPossible()
+    {
+        boolean handle=false;
+
+        synchronized (this)
+        {
+            _asyncRead=true;
+            if (_state==State.ASYNC_WAIT)
+            {
+                _state=State.ASYNC_WOKEN;
+                handle=true;
+            }
+        }
+
+        if (handle)
+            _channel.execute(_channel);
+    }
+    
+    public void onWritePossible()
+    {
+        boolean handle=false;
+
+        synchronized (this)
+        {
+            _asyncWrite=true;
+            if (_state==State.ASYNC_WAIT)
+            {
+                _state=State.ASYNC_WOKEN;
+                handle=true;
+            }
+        }
+
+        if (handle)
+            _channel.execute(_channel);
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpConfiguration.java b/lib/jetty/org/eclipse/jetty/server/HttpConfiguration.java
new file mode 100644 (file)
index 0000000..352af25
--- /dev/null
@@ -0,0 +1,269 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.util.Jetty;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+
+/* ------------------------------------------------------------ */
+/** HTTP Configuration.
+ * <p>This class is a holder of HTTP configuration for use by the 
+ * {@link HttpChannel} class.  Typically a HTTPConfiguration instance
+ * is instantiated and passed to a {@link ConnectionFactory} that can 
+ * create HTTP channels (eg HTTP, AJP or SPDY).</p>
+ * <p>The configuration held by this class is not for the wire protocol,
+ * but for the interpretation and handling of HTTP requests that could
+ * be transported by a variety of protocols.
+ * </p>
+ */
+@ManagedObject("HTTP Configuration")
+public class HttpConfiguration
+{
+    public static final String SERVER_VERSION = "Jetty(" + Jetty.VERSION + ")";
+
+    private List<Customizer> _customizers=new CopyOnWriteArrayList<>();
+    private int _outputBufferSize=32*1024;
+    private int _requestHeaderSize=8*1024;
+    private int _responseHeaderSize=8*1024;
+    private int _headerCacheSize=512;
+    private int _securePort;
+    private String _secureScheme = HttpScheme.HTTPS.asString();
+    private boolean _sendServerVersion = true; //send Server: header
+    private boolean _sendXPoweredBy = false; //send X-Powered-By: header
+    private boolean _sendDateHeader = true; //send Date: header
+
+    public interface Customizer
+    {
+        public void customize(Connector connector, HttpConfiguration channelConfig, Request request);
+    }
+    
+    public interface ConnectionFactory
+    {
+        HttpConfiguration getHttpConfiguration();
+    }
+    
+    public HttpConfiguration()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Create a configuration from another.
+     * @param config The configuration to copy.
+     */
+    public HttpConfiguration(HttpConfiguration config)
+    {
+        _customizers.addAll(config._customizers);
+        _outputBufferSize=config._outputBufferSize;
+        _requestHeaderSize=config._requestHeaderSize;
+        _responseHeaderSize=config._responseHeaderSize;
+        _securePort=config._securePort;
+        _secureScheme=config._secureScheme;
+        _sendDateHeader=config._sendDateHeader;
+        _sendServerVersion=config._sendServerVersion;
+        _headerCacheSize=config._headerCacheSize;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * <p>Add a {@link Customizer} that is invoked for every 
+     * request received.</p>
+     * <p>Customiser are often used to interpret optional headers (eg {@link ForwardedRequestCustomizer}) or 
+     * optional protocol semantics (eg {@link SecureRequestCustomizer}). 
+     * @param customizer A request customizer
+     */
+    public void addCustomizer(Customizer customizer)
+    {
+        _customizers.add(customizer);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public List<Customizer> getCustomizers()
+    {
+        return _customizers;
+    }
+    
+    public <T> T getCustomizer(Class<T> type)
+    {
+        for (Customizer c : _customizers)
+            if (type.isAssignableFrom(c.getClass()))
+                return (T)c;
+        return null;
+    }
+
+    @ManagedAttribute("The size in bytes of the output buffer used to aggregate HTTP output")
+    public int getOutputBufferSize()
+    {
+        return _outputBufferSize;
+    }
+    
+    @ManagedAttribute("The maximum allowed size in bytes for a HTTP request header")
+    public int getRequestHeaderSize()
+    {
+        return _requestHeaderSize;
+    }
+    
+    @ManagedAttribute("The maximum allowed size in bytes for a HTTP response header")
+    public int getResponseHeaderSize()
+    {
+        return _responseHeaderSize;
+    }
+
+    @ManagedAttribute("The maximum allowed size in bytes for a HTTP header field cache")
+    public int getHeaderCacheSize()
+    {
+        return _headerCacheSize;
+    }
+
+    @ManagedAttribute("The port to which Integral or Confidential security constraints are redirected")
+    public int getSecurePort()
+    {
+        return _securePort;
+    }
+    
+    @ManagedAttribute("The scheme with which Integral or Confidential security constraints are redirected")
+    public String getSecureScheme()
+    {
+        return _secureScheme;
+    }
+
+    public void setSendServerVersion (boolean sendServerVersion)
+    {
+        _sendServerVersion = sendServerVersion;
+    }
+
+    @ManagedAttribute("if true, send the Server header in responses")
+    public boolean getSendServerVersion()
+    {
+        return _sendServerVersion;
+    }
+    
+    public void setSendXPoweredBy (boolean sendXPoweredBy)
+    {
+        _sendXPoweredBy=sendXPoweredBy;
+    }
+
+    @ManagedAttribute("if true, send the X-Powered-By header in responses")
+    public boolean getSendXPoweredBy()
+    {
+        return _sendXPoweredBy;
+    }
+
+    public void setSendDateHeader(boolean sendDateHeader)
+    {
+        _sendDateHeader = sendDateHeader;
+    }
+
+    @ManagedAttribute("if true, include the date in HTTP headers")
+    public boolean getSendDateHeader()
+    {
+        return _sendDateHeader;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * <p>Set the {@link Customizer}s that are invoked for every 
+     * request received.</p>
+     * <p>Customisers are often used to interpret optional headers (eg {@link ForwardedRequestCustomizer}) or 
+     * optional protocol semantics (eg {@link SecureRequestCustomizer}). 
+     * @param customizers
+     */
+    public void setCustomizers(List<Customizer> customizers)
+    {
+        _customizers.clear();
+        _customizers.addAll(customizers);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the size of the buffer into which response content is aggregated
+     * before being sent to the client.  A larger buffer can improve performance by allowing
+     * a content producer to run without blocking, however larger buffers consume more memory and
+     * may induce some latency before a client starts processing the content.
+     * @param responseBufferSize buffer size in bytes.
+     */
+    public void setOutputBufferSize(int responseBufferSize)
+    {
+        _outputBufferSize = responseBufferSize;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Set the maximum size of a request header.
+     * <p>Larger headers will allow for more and/or larger cookies plus larger form content encoded 
+     * in a URL. However, larger headers consume more memory and can make a server more vulnerable to denial of service
+     * attacks.</p>
+     * @param requestHeaderSize Max header size in bytes
+     */
+    public void setRequestHeaderSize(int requestHeaderSize)
+    {
+        _requestHeaderSize = requestHeaderSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the maximum size of a response header.
+     * 
+     * <p>Larger headers will allow for more and/or larger cookies and longer HTTP headers (eg for redirection). 
+     * However, larger headers will also consume more memory.</p>
+     * @param responseHeaderSize Response header size in bytes.
+     */
+    public void setResponseHeaderSize(int responseHeaderSize)
+    {
+        _responseHeaderSize = responseHeaderSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the header field cache size.
+     * @param headerCacheSize The size in bytes of the header field cache.
+     */
+    public void setHeaderCacheSize(int headerCacheSize)
+    {
+        _headerCacheSize = headerCacheSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the TCP/IP port used for CONFIDENTIAL and INTEGRAL 
+     * redirections.
+     * @param confidentialPort
+     */
+    public void setSecurePort(int confidentialPort)
+    {
+        _securePort = confidentialPort;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the  URI scheme used for CONFIDENTIAL and INTEGRAL 
+     * redirections.
+     * @param confidentialScheme A string like"https"
+     */
+    public void setSecureScheme(String confidentialScheme)
+    {
+        _secureScheme = confidentialScheme;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%d,%d/%d,%s://:%d,%s}",this.getClass().getSimpleName(),hashCode(),_outputBufferSize,_requestHeaderSize,_responseHeaderSize,_secureScheme,_securePort,_customizers);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpConnection.java b/lib/jetty/org/eclipse/jetty/server/HttpConnection.java
new file mode 100644 (file)
index 0000000..16cbbf9
--- /dev/null
@@ -0,0 +1,790 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.RejectedExecutionException;
+
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>A {@link Connection} that handles the HTTP protocol.</p>
+ */
+public class HttpConnection extends AbstractConnection implements Runnable, HttpTransport
+{
+    public static final String UPGRADE_CONNECTION_ATTRIBUTE = "org.eclipse.jetty.server.HttpConnection.UPGRADE";
+    private static final boolean REQUEST_BUFFER_DIRECT=false;
+    private static final boolean HEADER_BUFFER_DIRECT=false;
+    private static final boolean CHUNK_BUFFER_DIRECT=false;
+    private static final Logger LOG = Log.getLogger(HttpConnection.class);
+    private static final ThreadLocal<HttpConnection> __currentConnection = new ThreadLocal<>();
+
+    private final HttpConfiguration _config;
+    private final Connector _connector;
+    private final ByteBufferPool _bufferPool;
+    private final HttpGenerator _generator;
+    private final HttpChannelOverHttp _channel;
+    private final HttpParser _parser;
+    private volatile ByteBuffer _requestBuffer = null;
+    private volatile ByteBuffer _chunk = null;
+
+
+    /* ------------------------------------------------------------ */
+    /** Get the current connection that this thread is dispatched to.
+     * Note that a thread may be processing a request asynchronously and 
+     * thus not be dispatched to the connection.  
+     * @see Request#getAttribute(String) for a more general way to access the HttpConnection
+     * @return the current HttpConnection or null
+     */
+    public static HttpConnection getCurrentConnection()
+    {
+        return __currentConnection.get();
+    }
+
+    protected static HttpConnection setCurrentConnection(HttpConnection connection)
+    {
+        HttpConnection last=__currentConnection.get();
+        if (connection==null)
+            __currentConnection.remove();
+        else 
+            __currentConnection.set(connection);
+        return last;
+    }
+
+    public HttpConfiguration getHttpConfiguration()
+    {
+        return _config;
+    }
+
+    public HttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint)
+    {
+        // Tell AbstractConnector executeOnFillable==true because we want the same thread that
+        // does the HTTP parsing to handle the request so its cache is hot
+        super(endPoint, connector.getExecutor(),true);
+
+        _config = config;
+        _connector = connector;
+        _bufferPool = _connector.getByteBufferPool();
+        _generator = newHttpGenerator();
+        HttpInput<ByteBuffer> input = newHttpInput();
+        _channel = newHttpChannel(input);
+        _parser = newHttpParser();
+        LOG.debug("New HTTP Connection {}", this);
+    }
+
+    protected HttpGenerator newHttpGenerator()
+    {
+        return new HttpGenerator(_config.getSendServerVersion(),_config.getSendXPoweredBy());
+    }
+    
+    protected HttpInput<ByteBuffer> newHttpInput()
+    {
+        return new HttpInputOverHTTP(this);
+    }
+    
+    protected HttpChannelOverHttp newHttpChannel(HttpInput<ByteBuffer> httpInput)
+    {
+        return new HttpChannelOverHttp(_connector, _config, getEndPoint(), this, httpInput);
+    }
+    
+    protected HttpParser newHttpParser()
+    {
+        return new HttpParser(newRequestHandler(), getHttpConfiguration().getRequestHeaderSize());
+    }
+
+    protected HttpParser.RequestHandler<ByteBuffer> newRequestHandler()
+    {
+        return _channel;
+    }
+
+    public Server getServer()
+    {
+        return _connector.getServer();
+    }
+
+    public Connector getConnector()
+    {
+        return _connector;
+    }
+
+    public HttpChannel<?> getHttpChannel()
+    {
+        return _channel;
+    }
+
+    public HttpParser getParser()
+    {
+        return _parser;
+    }
+
+    @Override
+    public int getMessagesIn()
+    {
+        return getHttpChannel().getRequests();
+    }
+
+    @Override
+    public int getMessagesOut()
+    {
+        return getHttpChannel().getRequests();
+    }
+
+    void releaseRequestBuffer()
+    {
+        if (_requestBuffer != null && !_requestBuffer.hasRemaining())
+        {
+            ByteBuffer buffer=_requestBuffer;
+            _requestBuffer=null;
+            _bufferPool.release(buffer);
+        }
+    }
+    
+    public ByteBuffer getRequestBuffer()
+    {
+        if (_requestBuffer == null)
+            _requestBuffer = _bufferPool.acquire(getInputBufferSize(), REQUEST_BUFFER_DIRECT);
+        return _requestBuffer;
+    }
+
+    /**
+     * <p>Parses and handles HTTP messages.</p>
+     * <p>This method is called when this {@link Connection} is ready to read bytes from the {@link EndPoint}.
+     * However, it can also be called if there is unconsumed data in the _requestBuffer, as a result of
+     * resuming a suspended request when there is a pipelined request already read into the buffer.</p>
+     * <p>This method fills bytes and parses them until either: EOF is filled; 0 bytes are filled;
+     * the HttpChannel finishes handling; or the connection has changed.</p>
+     */
+    @Override
+    public void onFillable()
+    {
+        LOG.debug("{} onFillable {}", this, _channel.getState());
+
+        final HttpConnection last=setCurrentConnection(this);
+        int filled=Integer.MAX_VALUE;
+        boolean suspended=false;
+        try
+        {
+            // while not suspended and not upgraded
+            while (!suspended && getEndPoint().getConnection()==this)
+            {
+                // Do we need some data to parse
+                if (BufferUtil.isEmpty(_requestBuffer))
+                {
+                    // If the previous iteration filled 0 bytes or saw a close, then break here 
+                    if (filled<=0)
+                        break;
+                        
+                    // Can we fill?
+                    if(getEndPoint().isInputShutdown())
+                    {
+                        // No pretend we read -1
+                        filled=-1;
+                        _parser.atEOF();
+                    }
+                    else
+                    {
+                        // Get a buffer
+                        // We are not in a race here for the request buffer as we have not yet received a request,
+                        // so there are not an possible legal threads calling #parseContent or #completed.
+                        _requestBuffer = getRequestBuffer();
+
+                        // fill
+                        filled = getEndPoint().fill(_requestBuffer);
+                        if (filled==0) // Do a retry on fill 0 (optimization for SSL connections)
+                            filled = getEndPoint().fill(_requestBuffer);
+                        
+                        // tell parser
+                        if (filled < 0)
+                            _parser.atEOF();
+                    }
+                }
+                
+                // Parse the buffer
+                if (_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer))
+                {
+                    // The parser returned true, which indicates the channel is ready to handle a request.
+                    // Call the channel and this will either handle the request/response to completion OR,
+                    // if the request suspends, the request/response will be incomplete so the outer loop will exit.
+                    // Not that onFillable no longer manipulates the request buffer from this point and that is
+                    // left to threads calling #completed or #parseContent (which may be this thread inside handle())
+                    suspended = !_channel.handle();
+                }
+                else
+                {
+                    // We parsed what we could, recycle the request buffer
+                    // We are not in a race here for the request buffer as we have not yet received a request,
+                    // so there are not an possible legal threads calling #parseContent or #completed.
+                    releaseRequestBuffer();
+                }
+            }
+        }
+        catch (EofException e)
+        {
+            LOG.debug(e);
+        }
+        catch (Exception e)
+        {
+            if (_parser.isIdle())
+                LOG.debug(e);
+            else
+                LOG.warn(this.toString(), e);
+            close();
+        }
+        finally
+        {                        
+            setCurrentConnection(last);
+            if (!suspended && getEndPoint().isOpen() && getEndPoint().getConnection()==this)
+            {
+                fillInterested();
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Fill and parse data looking for content
+     * @throws IOException
+     */
+    protected void parseContent() throws IOException
+    {
+        // Not in a race here for the request buffer with #onFillable because an async consumer of
+        // content would only be started after onFillable has given up control.
+        // In a little bit of a race with #completed, but then not sure if it is legal to be doing 
+        // async calls to IO and have a completed call at the same time.
+        ByteBuffer requestBuffer = getRequestBuffer();
+
+        while (_parser.inContentState())
+        {
+            // Can the parser progress (even with an empty buffer)
+            boolean parsed = _parser.parseNext(requestBuffer==null?BufferUtil.EMPTY_BUFFER:requestBuffer);
+
+            // No, we can we try reading some content?
+            if (BufferUtil.isEmpty(requestBuffer) && getEndPoint().isInputShutdown())
+            {
+                _parser.atEOF();
+                if (parsed)
+                    break;
+                continue;
+            }
+
+            if (parsed)
+                break;
+            
+            // OK lets read some data
+            int filled=getEndPoint().fill(requestBuffer);
+            if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
+                LOG.debug("{} filled {}",this,filled);
+            if (filled<=0)
+            {
+                if (filled<0)
+                {
+                    _parser.atEOF();
+                    continue;
+                }
+                break;
+            }
+        }
+    }
+    
+    @Override
+    public void completed()
+    {
+        // Handle connection upgrades
+        if (_channel.getResponse().getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
+        {
+            Connection connection = (Connection)_channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE);
+            if (connection != null)
+            {
+                LOG.debug("Upgrade from {} to {}", this, connection);
+                onClose();
+                getEndPoint().setConnection(connection);
+                connection.onOpen();
+                _channel.reset();
+                _parser.reset();
+                _generator.reset();
+                releaseRequestBuffer();
+                return;
+            }
+        }
+        
+        // Finish consuming the request
+        // If we are still expecting
+        if (_channel.isExpecting100Continue())
+            // close to seek EOF
+            _parser.close();
+        else if (_parser.inContentState() && _generator.isPersistent())
+            // Complete reading the request
+            _channel.getRequest().getHttpInput().consumeAll();
+
+        // Reset the channel, parsers and generator
+        _channel.reset();
+        if (_generator.isPersistent() && !_parser.isClosed())
+            _parser.reset();
+        else
+            _parser.close();
+        
+        // Not in a race here with onFillable, because it has given up control before calling handle.
+        // in a slight race with #completed, but not sure what to do with that anyway.
+        releaseRequestBuffer();
+        if (_chunk!=null)
+            _bufferPool.release(_chunk);
+        _chunk=null;
+        _generator.reset();
+
+        // if we are not called from the onfillable thread, schedule completion
+        if (getCurrentConnection()!=this)
+        {
+            // If we are looking for the next request
+            if (_parser.isStart())
+            {
+                // if the buffer is empty
+                if (BufferUtil.isEmpty(_requestBuffer))
+                {
+                    // look for more data
+                    fillInterested();
+                }
+                // else if we are still running
+                else if (getConnector().isRunning())
+                {
+                    // Dispatched to handle a pipelined request
+                    try
+                    {
+                        getExecutor().execute(this);
+                    }
+                    catch (RejectedExecutionException e)
+                    {
+                        if (getConnector().isRunning())
+                            LOG.warn(e);
+                        else
+                            LOG.ignore(e);
+                        getEndPoint().close();
+                    }
+                }
+                else
+                {
+                    getEndPoint().close();
+                }
+            }
+            // else the parser must be closed, so seek the EOF if we are still open 
+            else if (getEndPoint().isOpen())
+                fillInterested();
+        }
+    }
+
+    @Override
+    protected void onFillInterestedFailed(Throwable cause)
+    {
+        _parser.close();
+        super.onFillInterestedFailed(cause);
+    }
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        fillInterested();
+    }
+
+    @Override
+    public void run()
+    {
+        onFillable();
+    }
+
+
+    @Override
+    public void send(ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
+    {
+        if (info==null)
+            new ContentCallback(content,lastContent,callback).iterate();
+        else
+        {
+            // If we are still expecting a 100 continues
+            if (_channel.isExpecting100Continue())
+                // then we can't be persistent
+                _generator.setPersistent(false);
+            new CommitCallback(info,content,lastContent,callback).iterate();
+        }
+    }
+
+    @Override
+    public void send(ByteBuffer content, boolean lastContent, Callback callback)
+    {
+        new ContentCallback(content,lastContent,callback).iterate();
+    }
+
+    
+    protected class HttpChannelOverHttp extends HttpChannel<ByteBuffer>
+    {
+        public HttpChannelOverHttp(Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport, HttpInput<ByteBuffer> input)
+        {
+            super(connector,config,endPoint,transport,input);
+        }
+        
+        @Override
+        public void earlyEOF()
+        {
+            // If we have no request yet, just close
+            if (getRequest().getMethod()==null)
+                close();
+            else
+                super.earlyEOF();
+        }
+
+        @Override
+        public boolean content(ByteBuffer item)
+        {
+            super.content(item);
+            return true;
+        }
+
+        @Override
+        public void badMessage(int status, String reason)
+        {
+            _generator.setPersistent(false);
+            super.badMessage(status,reason);
+        }
+
+        @Override
+        public boolean headerComplete()
+        {
+            boolean persistent;
+            HttpVersion version = getHttpVersion();
+
+            switch (version)
+            {
+                case HTTP_0_9:
+                {
+                    persistent = false;
+                    break;
+                }
+                case HTTP_1_0:
+                {
+                    persistent = getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString());
+                    if (!persistent)
+                        persistent = HttpMethod.CONNECT.is(getRequest().getMethod());
+                    if (persistent)
+                        getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE);
+                    break;
+                }
+                case HTTP_1_1:
+                {
+                    persistent = !getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
+                    if (!persistent)
+                        persistent = HttpMethod.CONNECT.is(getRequest().getMethod());
+                    if (!persistent)
+                        getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
+                    break;
+                }
+                default:
+                {
+                    throw new IllegalStateException();
+                }
+            }
+
+            if (!persistent)
+                _generator.setPersistent(false);
+
+            return super.headerComplete();
+        }
+
+        @Override
+        protected void handleException(Throwable x)
+        {
+            _generator.setPersistent(false);
+            super.handleException(x);
+        }
+
+        @Override
+        public void failed()
+        {
+            getEndPoint().shutdownOutput();
+        }
+        
+
+        @Override
+        public boolean messageComplete()
+        {
+            super.messageComplete();
+            return false;
+        }
+    }
+
+    private class CommitCallback extends IteratingCallback
+    {
+        final ByteBuffer _content;
+        final boolean _lastContent;
+        final ResponseInfo _info;
+        final Callback _callback;
+        ByteBuffer _header;
+
+        CommitCallback(ResponseInfo info, ByteBuffer content, boolean last, Callback callback)
+        {
+            _info=info;
+            _content=content;
+            _lastContent=last;
+            _callback=callback;
+        }
+
+        @Override
+        public Action process() throws Exception
+        {
+            ByteBuffer chunk = _chunk;
+            while (true)
+            {
+                HttpGenerator.Result result = _generator.generateResponse(_info, _header, chunk, _content, _lastContent);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("{} generate: {} ({},{},{})@{}",
+                        this,
+                        result,
+                        BufferUtil.toSummaryString(_header),
+                        BufferUtil.toSummaryString(_content),
+                        _lastContent,
+                        _generator.getState());
+
+                switch (result)
+                {
+                    case NEED_HEADER:
+                    {
+                        // Look for optimisation to avoid allocating a _header buffer
+                        /*
+                         Cannot use this optimisation unless we work out how not to overwrite data in user passed arrays.
+                        if (_lastContent && _content!=null && !_content.isReadOnly() && _content.hasArray() && BufferUtil.space(_content)>_config.getResponseHeaderSize() )
+                        {
+                            // use spare space in content buffer for header buffer
+                            int p=_content.position();
+                            int l=_content.limit();
+                            _content.position(l);
+                            _content.limit(l+_config.getResponseHeaderSize());
+                            _header=_content.slice();
+                            _header.limit(0);
+                            _content.position(p);
+                            _content.limit(l);
+                        }
+                        else
+                        */
+                            _header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);
+                            
+                        continue;
+                    }
+                    case NEED_CHUNK:
+                    {
+                        chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
+                        continue;
+                    }
+                    case FLUSH:
+                    {
+                        // Don't write the chunk or the content if this is a HEAD response
+                        if (_channel.getRequest().isHead())
+                        {
+                            BufferUtil.clear(chunk);
+                            BufferUtil.clear(_content);
+                        }
+
+                        // If we have a header
+                        if (BufferUtil.hasContent(_header))
+                        {
+                            if (BufferUtil.hasContent(_content))
+                            {
+                                if (BufferUtil.hasContent(chunk))
+                                    getEndPoint().write(this, _header, chunk, _content);
+                                else
+                                    getEndPoint().write(this, _header, _content);
+                            }
+                            else
+                                getEndPoint().write(this, _header);
+                        }
+                        else if (BufferUtil.hasContent(chunk))
+                        {
+                            if (BufferUtil.hasContent(_content))
+                                getEndPoint().write(this, chunk, _content);
+                            else
+                                getEndPoint().write(this, chunk);
+                        }
+                        else if (BufferUtil.hasContent(_content))
+                        {
+                            getEndPoint().write(this, _content);
+                        }
+                        else
+                            continue;
+                        return Action.SCHEDULED;
+                    }
+                    case SHUTDOWN_OUT:
+                    {
+                        getEndPoint().shutdownOutput();
+                        continue;
+                    }
+                    case DONE:
+                    {
+                        if (_header!=null)
+                        {
+                            // don't release header in spare content buffer
+                            if (!_lastContent || _content==null || !_content.hasArray() || !_header.hasArray() ||  _content.array()!=_header.array())
+                                _bufferPool.release(_header);
+                        }
+                        return Action.SUCCEEDED;
+                    }
+                    case CONTINUE:
+                    {
+                        break;
+                    }
+                    default:
+                    {
+                        throw new IllegalStateException("generateResponse="+result);
+                    }
+                }
+            }
+        }
+
+        @Override
+        protected void completed()
+        {
+            _callback.succeeded();
+        }
+
+        @Override
+        public void failed(final Throwable x)
+        {
+            super.failed(x);
+            failedCallback(_callback,x);
+        }
+    }
+
+    private class ContentCallback extends IteratingCallback
+    {
+        final ByteBuffer _content;
+        final boolean _lastContent;
+        final Callback _callback;
+
+        ContentCallback(ByteBuffer content, boolean last, Callback callback)
+        {
+            _content=content;
+            _lastContent=last;
+            _callback=callback;
+        }
+
+        @Override
+        public Action process() throws Exception
+        {
+            ByteBuffer chunk = _chunk;
+            while (true)
+            {
+                HttpGenerator.Result result = _generator.generateResponse(null, null, chunk, _content, _lastContent);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("{} generate: {} ({},{})@{}",
+                        this,
+                        result,
+                        BufferUtil.toSummaryString(_content),
+                        _lastContent,
+                        _generator.getState());
+
+                switch (result)
+                {
+                    case NEED_HEADER:
+                        throw new IllegalStateException();
+                    case NEED_CHUNK:
+                    {
+                        chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
+                        continue;
+                    }
+                    case FLUSH:
+                    {
+                        // Don't write the chunk or the content if this is a HEAD response
+                        if (_channel.getRequest().isHead())
+                        {
+                            BufferUtil.clear(chunk);
+                            BufferUtil.clear(_content);
+                            continue;
+                        }
+                        else if (BufferUtil.hasContent(chunk))
+                        {
+                            if (BufferUtil.hasContent(_content))
+                                getEndPoint().write(this, chunk, _content);
+                            else
+                                getEndPoint().write(this, chunk);
+                        }
+                        else if (BufferUtil.hasContent(_content))
+                        {
+                            getEndPoint().write(this, _content);
+                        }
+                        else
+                            continue;
+                        return Action.SCHEDULED;
+                    }
+                    case SHUTDOWN_OUT:
+                    {
+                        getEndPoint().shutdownOutput();
+                        continue;
+                    }
+                    case DONE:
+                    {
+                        return Action.SUCCEEDED;
+                    }
+                    case CONTINUE:
+                    {
+                        break;
+                    }
+                    default:
+                    {
+                        throw new IllegalStateException("generateResponse="+result);
+                    }
+                }
+            }
+        }
+
+        @Override
+        protected void completed()
+        {
+            _callback.succeeded();
+        }
+
+        @Override
+        public void failed(final Throwable x)
+        {
+            super.failed(x);
+            failedCallback(_callback,x);
+        }
+    }
+
+    @Override
+    public void abort()
+    {
+        // Do a direct close of the output, as this may indicate to a client that the 
+        // response is bad either with RST or by abnormal completion of chunked response.
+        getEndPoint().close();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpConnectionFactory.java b/lib/jetty/org/eclipse/jetty/server/HttpConnectionFactory.java
new file mode 100644 (file)
index 0000000..ce6ffdb
--- /dev/null
@@ -0,0 +1,64 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server;
+
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.annotation.Name;
+
+
+/* ------------------------------------------------------------ */
+/** A Connection Factory for HTTP Connections.
+ * <p>Accepts connections either directly or via SSL and/or NPN chained connection factories.  The accepted 
+ * {@link HttpConnection}s are configured by a {@link HttpConfiguration} instance that is either created by
+ * default or passed in to the constructor.
+ */
+public class HttpConnectionFactory extends AbstractConnectionFactory implements HttpConfiguration.ConnectionFactory
+{
+    private final HttpConfiguration _config;
+
+    public HttpConnectionFactory()
+    {
+        this(new HttpConfiguration());
+        setInputBufferSize(16384);
+    }
+
+    public HttpConnectionFactory(@Name("config") HttpConfiguration config)
+    {
+        super(HttpVersion.HTTP_1_1.toString());
+        _config=config;
+        addBean(_config);
+    }
+
+    @Override
+    public HttpConfiguration getHttpConfiguration()
+    {
+        return _config;
+    }
+
+    @Override
+    public Connection newConnection(Connector connector, EndPoint endPoint)
+    {
+        return configure(new HttpConnection(_config, connector, endPoint), connector, endPoint);
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpInput.java b/lib/jetty/org/eclipse/jetty/server/HttpInput.java
new file mode 100644 (file)
index 0000000..833ecde
--- /dev/null
@@ -0,0 +1,503 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Objects;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * {@link HttpInput} provides an implementation of {@link ServletInputStream} for {@link HttpChannel}.
+ * <p/>
+ * Content may arrive in patterns such as [content(), content(), messageComplete()] so that this class
+ * maintains two states: the content state that tells whether there is content to consume and the EOF
+ * state that tells whether an EOF has arrived.
+ * Only once the content has been consumed the content state is moved to the EOF state.
+ */
+public abstract class HttpInput<T> extends ServletInputStream implements Runnable
+{
+    private final static Logger LOG = Log.getLogger(HttpInput.class);
+
+    private final byte[] _oneByteBuffer = new byte[1];
+    private final Object _lock;
+    private HttpChannelState _channelState;
+    private ReadListener _listener;
+    private Throwable _onError;
+    private boolean _notReady;
+    private State _contentState = STREAM;
+    private State _eofState;
+    private long _contentRead;
+
+    protected HttpInput()
+    {
+        this(null);
+    }
+
+    protected HttpInput(Object lock)
+    {
+        _lock = lock == null ? this : lock;
+    }
+
+    public void init(HttpChannelState state)
+    {
+        synchronized (lock())
+        {
+            _channelState = state;
+        }
+    }
+
+    public final Object lock()
+    {
+        return _lock;
+    }
+
+    public void recycle()
+    {
+        synchronized (lock())
+        {
+            _listener = null;
+            _onError = null;
+            _notReady = false;
+            _contentState = STREAM;
+            _eofState = null;
+            _contentRead = 0;
+        }
+    }
+
+    @Override
+    public int available()
+    {
+        try
+        {
+            synchronized (lock())
+            {
+                T item = getNextContent();
+                return item == null ? 0 : remaining(item);
+            }
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeIOException(e);
+        }
+    }
+
+    @Override
+    public int read() throws IOException
+    {
+        int read = read(_oneByteBuffer, 0, 1);
+        return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF;
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException
+    {
+        synchronized (lock())
+        {
+            T item = getNextContent();
+            if (item == null)
+            {
+                _contentState.waitForContent(this);
+                item = getNextContent();
+                if (item == null)
+                    return _contentState.noContent();
+            }
+            int l = get(item, b, off, len);
+            _contentRead += l;
+            return l;
+        }
+    }
+
+    /**
+     * A convenience method to call nextContent and to check the return value, which if null then the
+     * a check is made for EOF and the state changed accordingly.
+     *
+     * @return Content or null if none available.
+     * @throws IOException
+     * @see #nextContent()
+     */
+    protected T getNextContent() throws IOException
+    {
+        T content = nextContent();
+        if (content == null)
+        {
+            synchronized (lock())
+            {
+                if (_eofState != null)
+                {
+                    LOG.debug("{} eof {}", this, _eofState);
+                    _contentState = _eofState;
+                }
+            }
+        }
+        return content;
+    }
+
+    /**
+     * Access the next content to be consumed from.   Returning the next item does not consume it
+     * and it may be returned multiple times until it is consumed.
+     * <p/>
+     * Calls to {@link #get(Object, byte[], int, int)}
+     * or {@link #consume(Object, int)} are required to consume data from the content.
+     *
+     * @return the content or null if none available.
+     * @throws IOException if retrieving the content fails
+     */
+    protected abstract T nextContent() throws IOException;
+
+    /**
+     * @param item the content
+     * @return how many bytes remain in the given content
+     */
+    protected abstract int remaining(T item);
+
+    /**
+     * Copies the given content into the given byte buffer.
+     *
+     * @param item   the content to copy from
+     * @param buffer the buffer to copy into
+     * @param offset the buffer offset to start copying from
+     * @param length the space available in the buffer
+     * @return the number of bytes actually copied
+     */
+    protected abstract int get(T item, byte[] buffer, int offset, int length);
+
+    /**
+     * Consumes the given content.
+     *
+     * @param item   the content to consume
+     * @param length the number of bytes to consume
+     */
+    protected abstract void consume(T item, int length);
+
+    /**
+     * Blocks until some content or some end-of-file event arrives.
+     *
+     * @throws IOException if the wait is interrupted
+     */
+    protected abstract void blockForContent() throws IOException;
+
+    /**
+     * Adds some content to this input stream.
+     *
+     * @param item the content to add
+     */
+    public abstract void content(T item);
+
+    protected boolean onAsyncRead()
+    {
+        synchronized (lock())
+        {
+            if (_listener == null)
+                return false;
+        }
+        _channelState.onReadPossible();
+        return true;
+    }
+
+    public long getContentRead()
+    {
+        synchronized (lock())
+        {
+            return _contentRead;
+        }
+    }
+
+    /**
+     * This method should be called to signal that an EOF has been
+     * detected before all the expected content arrived.
+     * <p/>
+     * Typically this will result in an EOFException being thrown
+     * from a subsequent read rather than a -1 return.
+     */
+    public void earlyEOF()
+    {
+        synchronized (lock())
+        {
+            if (!isEOF())
+            {
+                LOG.debug("{} early EOF", this);
+                _eofState = EARLY_EOF;
+                if (_listener == null)
+                    return;
+            }
+        }
+        _channelState.onReadPossible();
+    }
+
+    /**
+     * This method should be called to signal that all the expected
+     * content arrived.
+     */
+    public void messageComplete()
+    {
+        synchronized (lock())
+        {
+            if (!isEOF())
+            {
+                LOG.debug("{} EOF", this);
+                _eofState = EOF;
+                if (_listener == null)
+                    return;
+            }
+        }
+        _channelState.onReadPossible();
+    }
+
+    public void consumeAll()
+    {
+        synchronized (lock())
+        {
+            try
+            {
+                while (!isFinished())
+                {
+                    T item = getNextContent();
+                    if (item == null)
+                        _contentState.waitForContent(this);
+                    else
+                        consume(item, remaining(item));
+                }
+            }
+            catch (IOException e)
+            {
+                LOG.debug(e);
+            }
+        }
+    }
+
+    public boolean isAsync()
+    {
+        synchronized (lock())
+        {
+            return _contentState==ASYNC;
+        }
+    }
+    
+    /**
+     * @return whether an EOF has been detected, even though there may be content to consume.
+     */
+    public boolean isEOF()
+    {
+        synchronized (lock())
+        {
+            return _eofState != null && _eofState.isEOF();
+        }
+    }
+
+    @Override
+    public boolean isFinished()
+    {
+        synchronized (lock())
+        {
+            return _contentState.isEOF();
+        }
+    }
+
+    @Override
+    public boolean isReady()
+    {
+        boolean finished;
+        synchronized (lock())
+        {
+            if (_contentState.isEOF())
+                return true;
+            if (_listener == null )
+                return true;
+            if (available() > 0)
+                return true;
+            if (_notReady)
+                return false;
+            _notReady = true;
+            finished = isFinished();
+        }
+        if (finished)
+            _channelState.onReadPossible();
+        else
+            unready();
+        return false;
+    }
+
+    protected void unready()
+    {
+    }
+
+    @Override
+    public void setReadListener(ReadListener readListener)
+    {
+        readListener = Objects.requireNonNull(readListener);
+        synchronized (lock())
+        {
+            if (_contentState != STREAM)
+                throw new IllegalStateException("state=" + _contentState);
+            _contentState = ASYNC;
+            _listener = readListener;
+            _notReady = true;
+        }
+        _channelState.onReadPossible();
+    }
+
+    public void failed(Throwable x)
+    {
+        synchronized (lock())
+        {
+            if (_onError != null)
+                LOG.warn(x);
+            else
+                _onError = x;
+        }
+    }
+
+    @Override
+    public void run()
+    {
+        final Throwable error;
+        final ReadListener listener;
+        boolean available = false;
+        final boolean eof;
+
+        synchronized (lock())
+        {
+            if (!_notReady || _listener == null)
+                return;
+
+            error = _onError;
+            listener = _listener;
+
+            try
+            {
+                T item = getNextContent();
+                available = item != null && remaining(item) > 0;
+            }
+            catch (Exception e)
+            {
+                failed(e);
+            }
+
+            eof = !available && isFinished();
+            _notReady = !available && !eof;
+        }
+
+        try
+        {
+            if (error != null)
+                listener.onError(error);
+            else if (available)
+                listener.onDataAvailable();
+            else if (eof)
+                listener.onAllDataRead();
+            else
+                unready();
+        }
+        catch (Throwable e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+            listener.onError(e);
+        }
+    }
+
+    protected static abstract class State
+    {
+        public void waitForContent(HttpInput<?> in) throws IOException
+        {
+        }
+
+        public int noContent() throws IOException
+        {
+            return -1;
+        }
+
+        public boolean isEOF()
+        {
+            return false;
+        }
+    }
+
+    protected static final State STREAM = new State()
+    {
+        @Override
+        public void waitForContent(HttpInput<?> input) throws IOException
+        {
+            input.blockForContent();
+        }
+
+        @Override
+        public String toString()
+        {
+            return "STREAM";
+        }
+    };
+
+    protected static final State ASYNC = new State()
+    {
+        @Override
+        public int noContent() throws IOException
+        {
+            return 0;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "ASYNC";
+        }
+    };
+
+    protected static final State EARLY_EOF = new State()
+    {
+        @Override
+        public int noContent() throws IOException
+        {
+            throw new EofException();
+        }
+
+        @Override
+        public boolean isEOF()
+        {
+            return true;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "EARLY_EOF";
+        }
+    };
+
+    protected static final State EOF = new State()
+    {
+        @Override
+        public boolean isEOF()
+        {
+            return true;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "EOF";
+        }
+    };
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpInputOverHTTP.java b/lib/jetty/org/eclipse/jetty/server/HttpInputOverHTTP.java
new file mode 100644 (file)
index 0000000..3552450
--- /dev/null
@@ -0,0 +1,145 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.SharedBlockingCallback;
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HttpInputOverHTTP extends HttpInput<ByteBuffer> implements Callback
+{
+    private static final Logger LOG = Log.getLogger(HttpInputOverHTTP.class);
+    private final SharedBlockingCallback _readBlocker = new SharedBlockingCallback();
+    private final HttpConnection _httpConnection;
+    private ByteBuffer _content;
+
+    /**
+     * @param httpConnection
+     */
+    public HttpInputOverHTTP(HttpConnection httpConnection)
+    {
+        _httpConnection = httpConnection;
+    }
+
+    @Override
+    public void recycle()
+    {
+        synchronized (lock())
+        {
+            super.recycle();
+            _content=null;
+        }
+    }
+
+    @Override
+    protected void blockForContent() throws IOException
+    {
+        while(true)
+        {
+            try (Blocker blocker=_readBlocker.acquire())
+            {            
+                _httpConnection.fillInterested(blocker);
+                LOG.debug("{} block readable on {}",this,blocker);
+                blocker.block();
+            }
+
+            Object content=getNextContent();
+            if (content!=null || isFinished())
+                break;
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x",getClass().getSimpleName(),hashCode());
+    }
+
+    @Override
+    protected ByteBuffer nextContent() throws IOException
+    {
+        // If we have some content available, return it
+        if (BufferUtil.hasContent(_content))
+            return _content;
+
+        // No - then we are going to need to parse some more content
+        _content=null;
+        _httpConnection.parseContent();
+        
+        // If we have some content available, return it
+        if (BufferUtil.hasContent(_content))
+            return _content;
+
+        return null;
+
+    }
+
+    @Override
+    protected int remaining(ByteBuffer item)
+    {
+        return item.remaining();
+    }
+
+    @Override
+    protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
+    {
+        int l = Math.min(item.remaining(), length);
+        item.get(buffer, offset, l);
+        return l;
+    }
+
+    @Override
+    protected void consume(ByteBuffer item, int length)
+    {
+        item.position(item.position()+length);
+    }
+
+    @Override
+    public void content(ByteBuffer item)
+    {
+        if (BufferUtil.hasContent(_content))
+            throw new IllegalStateException();
+        _content=item;
+    }
+
+    @Override
+    protected void unready()
+    {
+        _httpConnection.fillInterested(this);
+    }
+
+    @Override
+    public void succeeded()
+    {
+        _httpConnection.getHttpChannel().getState().onReadPossible();
+    }
+
+    @Override
+    public void failed(Throwable x)
+    {
+        super.failed(x);
+        _httpConnection.getHttpChannel().getState().onReadPossible();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpOutput.java b/lib/jetty/org/eclipse/jetty/server/HttpOutput.java
new file mode 100644 (file)
index 0000000..7c0fd89
--- /dev/null
@@ -0,0 +1,1096 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritePendingException;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.WriteListener;
+
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.IteratingNestedCallback;
+import org.eclipse.jetty.util.SharedBlockingCallback;
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>{@link HttpOutput} implements {@link ServletOutputStream}
+ * as required by the Servlet specification.</p>
+ * <p>{@link HttpOutput} buffers content written by the application until a
+ * further write will overflow the buffer, at which point it triggers a commit
+ * of the response.</p>
+ * <p>{@link HttpOutput} can be closed and reopened, to allow requests included
+ * via {@link RequestDispatcher#include(ServletRequest, ServletResponse)} to
+ * close the stream, to be reopened after the inclusion ends.</p>
+ */
+public class HttpOutput extends ServletOutputStream implements Runnable
+{
+    private static Logger LOG = Log.getLogger(HttpOutput.class);
+    private final HttpChannel<?> _channel;
+    private final SharedBlockingCallback _writeblock=new SharedBlockingCallback();
+    private long _written;
+    private ByteBuffer _aggregate;
+    private int _bufferSize;
+    private int _commitSize;
+    private WriteListener _writeListener;
+    private volatile Throwable _onError;
+
+    /*
+    ACTION             OPEN       ASYNC      READY      PENDING       UNREADY       CLOSED
+    -----------------------------------------------------------------------------------------------------
+    setWriteListener() READY->owp ise        ise        ise           ise           ise
+    write()            OPEN       ise        PENDING    wpe           wpe           eof
+    flush()            OPEN       ise        PENDING    wpe           wpe           eof
+    close()            CLOSED     CLOSED     CLOSED     CLOSED        wpe           CLOSED
+    isReady()          OPEN:true  READY:true READY:true UNREADY:false UNREADY:false CLOSED:true
+    write completed    -          -          -          ASYNC         READY->owp    -
+    
+    */
+    enum OutputState { OPEN, ASYNC, READY, PENDING, UNREADY, ERROR, CLOSED }
+    private final AtomicReference<OutputState> _state=new AtomicReference<>(OutputState.OPEN);
+
+    public HttpOutput(HttpChannel<?> channel)
+    {
+        _channel = channel;
+        _bufferSize = _channel.getHttpConfiguration().getOutputBufferSize();
+        _commitSize=_bufferSize/4;
+    }
+    
+    public HttpChannel<?> getHttpChannel()
+    {
+        return _channel;
+    }
+    
+    public boolean isWritten()
+    {
+        return _written > 0;
+    }
+
+    public long getWritten()
+    {
+        return _written;
+    }
+
+    public void reset()
+    {
+        _written = 0;
+        reopen();
+    }
+
+    public void reopen()
+    {
+        _state.set(OutputState.OPEN);
+    }
+
+    public boolean isAllContentWritten()
+    {
+        return _channel.getResponse().isAllContentWritten(_written);
+    }
+
+    protected Blocker acquireWriteBlockingCallback() throws IOException
+    {
+        return _writeblock.acquire();
+    }
+    
+    protected void write(ByteBuffer content, boolean complete) throws IOException
+    {
+        try (Blocker blocker=_writeblock.acquire())
+        {        
+            write(content,complete,blocker);
+            blocker.block();
+        }
+    }
+    
+    protected void write(ByteBuffer content, boolean complete, Callback callback)
+    {
+        _channel.write(content,complete,callback);
+    }
+    
+    @Override
+    public void close()
+    {
+        loop: while(true)
+        {
+            OutputState state=_state.get();
+            switch (state)
+            {
+                case CLOSED:
+                    break loop;
+                    
+                case UNREADY:
+                    if (_state.compareAndSet(state,OutputState.ERROR))
+                        _writeListener.onError(_onError==null?new EofException("Async close"):_onError);
+                    continue;
+                    
+                default:
+                    if (_state.compareAndSet(state,OutputState.CLOSED))
+                    {
+                        try
+                        {
+                            write(BufferUtil.hasContent(_aggregate)?_aggregate:BufferUtil.EMPTY_BUFFER,!_channel.getResponse().isIncluding());
+                        }
+                        catch(IOException e)
+                        {
+                            LOG.debug(e);
+                            _channel.failed();
+                        }
+                        releaseBuffer();
+                        return;
+                    }
+            }
+        }
+    }
+
+    /* Called to indicated that the output is already closed (write with last==true performed) and the state needs to be updated to match */
+    void closed()
+    {
+        loop: while(true)
+        {
+            OutputState state=_state.get();
+            switch (state)
+            {
+                case CLOSED:
+                    break loop;
+                    
+                case UNREADY:
+                    if (_state.compareAndSet(state,OutputState.ERROR))
+                        _writeListener.onError(_onError==null?new EofException("Async closed"):_onError);
+                    continue;
+                    
+                default:
+                    if (_state.compareAndSet(state,OutputState.CLOSED))
+                    {
+                        try
+                        {
+                            _channel.getResponse().closeOutput();
+                        }
+                        catch(IOException e)
+                        {
+                            LOG.debug(e);
+                            _channel.failed();
+                        }
+                        releaseBuffer();
+                        return;
+                    }
+            }
+        }
+    }
+
+    private void releaseBuffer()
+    {
+        if (_aggregate != null)
+        {
+            _channel.getConnector().getByteBufferPool().release(_aggregate);
+            _aggregate = null;
+        }
+    }
+
+    public boolean isClosed()
+    {
+        return _state.get()==OutputState.CLOSED;
+    }
+
+    @Override
+    public void flush() throws IOException
+    {
+        while(true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    write(BufferUtil.hasContent(_aggregate)?_aggregate:BufferUtil.EMPTY_BUFFER, false);
+                    return;
+
+                case ASYNC:
+                    throw new IllegalStateException("isReady() not called");
+
+                case READY:
+                    if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+                        continue;
+                    new AsyncFlush().iterate();
+                    return;
+
+                case PENDING:
+                case UNREADY:
+                    throw new WritePendingException();
+
+                case ERROR:
+                    throw new EofException(_onError);
+                    
+                case CLOSED:
+                    return;
+            }
+            break;
+        }
+    }
+
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException
+    {
+        _written+=len;
+        boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+        // Async or Blocking ?
+        while(true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    // process blocking below
+                    break;
+
+                case ASYNC:
+                    throw new IllegalStateException("isReady() not called");
+
+                case READY:
+                    if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+                        continue;
+
+                    // Should we aggregate?
+                    if (!complete && len<=_commitSize)
+                    {
+                        if (_aggregate == null)
+                            _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+
+                        // YES - fill the aggregate with content from the buffer
+                        int filled = BufferUtil.fill(_aggregate, b, off, len);
+
+                        // return if we are not complete, not full and filled all the content
+                        if (filled==len && !BufferUtil.isFull(_aggregate))
+                        {
+                            if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
+                                throw new IllegalStateException();
+                            return;
+                        }
+
+                        // adjust offset/length
+                        off+=filled;
+                        len-=filled;
+                    }
+
+                    // Do the asynchronous writing from the callback
+                    new AsyncWrite(b,off,len,complete).iterate();
+                    return;
+
+                case PENDING:
+                case UNREADY:
+                    throw new WritePendingException();
+
+                case ERROR:
+                    throw new EofException(_onError);
+                    
+                case CLOSED:
+                    throw new EofException("Closed");
+            }
+            break;
+        }
+
+
+        // handle blocking write
+
+        // Should we aggregate?
+        int capacity = getBufferSize();
+        if (!complete && len<=_commitSize)
+        {
+            if (_aggregate == null)
+                _aggregate = _channel.getByteBufferPool().acquire(capacity, false);
+
+            // YES - fill the aggregate with content from the buffer
+            int filled = BufferUtil.fill(_aggregate, b, off, len);
+
+            // return if we are not complete, not full and filled all the content
+            if (filled==len && !BufferUtil.isFull(_aggregate))
+                return;
+
+            // adjust offset/length
+            off+=filled;
+            len-=filled;
+        }
+
+        // flush any content from the aggregate
+        if (BufferUtil.hasContent(_aggregate))
+        {
+            write(_aggregate, complete && len==0);
+
+            // should we fill aggregate again from the buffer?
+            if (len>0 && !complete && len<=_commitSize)
+            {
+                BufferUtil.append(_aggregate, b, off, len);
+                return;
+            }
+        }
+
+        // write any remaining content in the buffer directly
+        if (len>0)
+        {
+            ByteBuffer wrap = ByteBuffer.wrap(b, off, len);
+            ByteBuffer view = wrap.duplicate();
+
+            // write a buffer capacity at a time to avoid JVM pooling large direct buffers
+            // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6210541
+            while (len>getBufferSize())
+            {
+                int p=view.position();
+                int l=p+getBufferSize();
+                view.limit(p+getBufferSize());
+                write(view,false);
+                len-=getBufferSize();
+                view.limit(l+Math.min(len,getBufferSize()));
+                view.position(l);
+            }
+            write(view,complete);
+        }
+        else if (complete)
+            write(BufferUtil.EMPTY_BUFFER,complete);
+
+        if (complete)
+            closed();
+
+    }
+
+    public void write(ByteBuffer buffer) throws IOException
+    {
+        _written+=buffer.remaining();
+        boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+        // Async or Blocking ?
+        while(true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    // process blocking below
+                    break;
+
+                case ASYNC:
+                    throw new IllegalStateException("isReady() not called");
+
+                case READY:
+                    if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+                        continue;
+
+                    // Do the asynchronous writing from the callback
+                    new AsyncWrite(buffer,complete).iterate();
+                    return;
+
+                case PENDING:
+                case UNREADY:
+                    throw new WritePendingException();
+
+                case ERROR:
+                    throw new EofException(_onError);
+                    
+                case CLOSED:
+                    throw new EofException("Closed");
+            }
+            break;
+        }
+
+
+        // handle blocking write
+        int len=BufferUtil.length(buffer);
+
+        // flush any content from the aggregate
+        if (BufferUtil.hasContent(_aggregate))
+            write(_aggregate, complete && len==0);
+
+        // write any remaining content in the buffer directly
+        if (len>0)
+            write(buffer, complete);
+        else if (complete)
+            write(BufferUtil.EMPTY_BUFFER,complete);
+
+        if (complete)
+            closed();
+    }
+
+    @Override
+    public void write(int b) throws IOException
+    {
+        _written+=1;
+        boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+        // Async or Blocking ?
+        while(true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    if (_aggregate == null)
+                        _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+                    BufferUtil.append(_aggregate, (byte)b);
+
+                    // Check if all written or full
+                    if (complete || BufferUtil.isFull(_aggregate))
+                    {
+                        try(Blocker blocker=_writeblock.acquire())
+                        {
+                            write(_aggregate, complete, blocker);
+                            blocker.block();
+                        }
+                        if (complete)
+                            closed();
+                    }
+                    break;
+
+                case ASYNC:
+                    throw new IllegalStateException("isReady() not called");
+
+                case READY:
+                    if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+                        continue;
+
+                    if (_aggregate == null)
+                        _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+                    BufferUtil.append(_aggregate, (byte)b);
+
+                    // Check if all written or full
+                    if (!complete && !BufferUtil.isFull(_aggregate))
+                    {
+                        if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
+                            throw new IllegalStateException();
+                        return;
+                    }
+
+                    // Do the asynchronous writing from the callback
+                    new AsyncFlush().iterate();
+                    return;
+
+                case PENDING:
+                case UNREADY:
+                    throw new WritePendingException();
+
+                case ERROR:
+                    throw new EofException(_onError);
+                    
+                case CLOSED:
+                    throw new EofException("Closed");
+            }
+            break;
+        }
+    }
+
+    @Override
+    public void print(String s) throws IOException
+    {
+        if (isClosed())
+            throw new IOException("Closed");
+
+        write(s.getBytes(_channel.getResponse().getCharacterEncoding()));
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Blocking send of content.
+     * @param content The content to send.
+     * @throws IOException
+     */
+    public void sendContent(ByteBuffer content) throws IOException
+    {
+        try(Blocker blocker=_writeblock.acquire())
+        {
+            write(content,true,blocker);
+            blocker.block();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Blocking send of content.
+     * @param in The content to send
+     * @throws IOException
+     */
+    public void sendContent(InputStream in) throws IOException
+    {
+        try(Blocker blocker=_writeblock.acquire())
+        {
+            new InputStreamWritingCB(in,blocker).iterate();
+            blocker.block();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Blocking send of content.
+     * @param in The content to send
+     * @throws IOException
+     */
+    public void sendContent(ReadableByteChannel in) throws IOException
+    {
+        try(Blocker blocker=_writeblock.acquire())
+        {
+            new ReadableByteChannelWritingCB(in,blocker).iterate();
+            blocker.block();
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Blocking send of content.
+     * @param content The content to send
+     * @throws IOException
+     */
+    public void sendContent(HttpContent content) throws IOException
+    {
+        try(Blocker blocker=_writeblock.acquire())
+        {
+            sendContent(content,blocker);
+            blocker.block();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Asynchronous send of content.
+     * @param content The content to send
+     * @param callback The callback to use to notify success or failure
+     */
+    public void sendContent(ByteBuffer content, final Callback callback)
+    {
+        write(content,true,new Callback()
+        {
+            @Override
+            public void succeeded()
+            {
+                closed();
+                callback.succeeded();
+            }
+
+            @Override
+            public void failed(Throwable x)
+            {
+                callback.failed(x);
+            }
+        });
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Asynchronous send of content.
+     * @param in The content to send as a stream.  The stream will be closed
+     * after reading all content.
+     * @param callback The callback to use to notify success or failure
+     */
+    public void sendContent(InputStream in, Callback callback)
+    {
+        new InputStreamWritingCB(in,callback).iterate();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Asynchronous send of content.
+     * @param in The content to send as a channel.  The channel will be closed
+     * after reading all content.
+     * @param callback The callback to use to notify success or failure
+     */
+    public void sendContent(ReadableByteChannel in, Callback callback)
+    {
+        new ReadableByteChannelWritingCB(in,callback).iterate();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Asynchronous send of content.
+     * @param httpContent The content to send
+     * @param callback The callback to use to notify success or failure
+     */
+    public void sendContent(HttpContent httpContent, Callback callback) throws IOException
+    {
+        if (BufferUtil.hasContent(_aggregate))
+            throw new IOException("written");
+        if (_channel.isCommitted())
+            throw new IOException("committed");
+
+        while (true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    if (!_state.compareAndSet(OutputState.OPEN, OutputState.PENDING))
+                        continue;
+                    break;
+                case ERROR:
+                    throw new EofException(_onError);
+                case CLOSED:
+                    throw new EofException("Closed");
+                default:
+                    throw new IllegalStateException();
+            }
+            break;
+        }
+        ByteBuffer buffer= _channel.useDirectBuffers()?httpContent.getDirectBuffer():null;
+        if (buffer == null)
+            buffer = httpContent.getIndirectBuffer();
+
+        if (buffer!=null)
+        {
+            sendContent(buffer,callback);
+            return;
+        }
+
+        ReadableByteChannel rbc=httpContent.getReadableByteChannel();
+        if (rbc!=null)
+        {
+            // Close of the rbc is done by the async sendContent
+            sendContent(rbc,callback);
+            return;
+        }
+
+        InputStream in = httpContent.getInputStream();
+        if ( in!=null )
+        {
+            sendContent(in,callback);
+            return;
+        }
+
+        callback.failed(new IllegalArgumentException("unknown content for "+httpContent));
+    }
+
+    public int getBufferSize()
+    {
+        return _bufferSize;
+    }
+
+    public void setBufferSize(int size)
+    {
+        _bufferSize = size;
+        _commitSize = size;
+    }
+
+    public void resetBuffer()
+    {
+        if (BufferUtil.hasContent(_aggregate))
+            BufferUtil.clear(_aggregate);
+    }
+
+    @Override
+    public void setWriteListener(WriteListener writeListener)
+    {
+        if (!_channel.getState().isAsync())
+            throw new IllegalStateException("!ASYNC");
+
+        if (_state.compareAndSet(OutputState.OPEN, OutputState.READY))
+        {
+            _writeListener = writeListener;
+            _channel.getState().onWritePossible();
+        }
+        else
+            throw new IllegalStateException();
+    }
+
+    /**
+     * @see javax.servlet.ServletOutputStream#isReady()
+     */
+    @Override
+    public boolean isReady()
+    {
+        while (true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    return true;
+                case ASYNC:
+                    if (!_state.compareAndSet(OutputState.ASYNC, OutputState.READY))
+                        continue;
+                    return true;
+                case READY:
+                    return true;
+                case PENDING:
+                    if (!_state.compareAndSet(OutputState.PENDING, OutputState.UNREADY))
+                        continue;
+                    return false;
+                case UNREADY:
+                    return false;
+
+                case ERROR:
+                    return true;
+                    
+                case CLOSED:
+                    return true;
+            }
+        }
+    }
+
+    @Override
+    public void run()
+    {
+        loop: while (true)
+        {
+            OutputState state = _state.get();
+
+            if(_onError!=null)
+            {
+                switch(state)
+                {
+                    case CLOSED:
+                    case ERROR:
+                        _onError=null;
+                        break loop;
+
+                    default:
+                        if (_state.compareAndSet(state, OutputState.ERROR))
+                        {
+                            Throwable th=_onError;
+                            _onError=null;
+                            LOG.debug("onError",th);
+                            _writeListener.onError(th);
+                            close();
+
+                            break loop;
+                        }
+
+                }
+                continue loop;
+            }
+
+            switch(_state.get())
+            {
+                case READY:
+                case CLOSED:
+                    // even though a write is not possible, because a close has 
+                    // occurred, we need to call onWritePossible to tell async
+                    // producer that the last write completed.
+                    try
+                    {
+                        _writeListener.onWritePossible();
+                        break loop;
+                    }
+                    catch (Throwable e)
+                    {
+                        _onError=e;
+                    }
+                    break;
+                default:
+
+            }
+        }
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),_state.get());
+    }
+    
+    private abstract class AsyncICB extends IteratingCallback
+    {
+        @Override
+        protected void completed()
+        {
+            while(true)
+            {
+                OutputState last=_state.get();
+                switch(last)
+                {
+                    case PENDING:
+                        if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
+                            continue;
+                        break;
+
+                    case UNREADY:
+                        if (!_state.compareAndSet(OutputState.UNREADY, OutputState.READY))
+                            continue;
+                        _channel.getState().onWritePossible();
+                        break;
+
+                    case CLOSED:
+                        break;
+
+                    default:
+                        throw new IllegalStateException();
+                }
+                break;
+            }
+        }
+
+        @Override
+        public void failed(Throwable e)
+        {
+            super.failed(e);
+            _onError=e;
+            _channel.getState().onWritePossible();
+        }
+    }
+    
+    
+    private class AsyncFlush extends AsyncICB
+    {
+        protected volatile boolean _flushed;
+
+        public AsyncFlush()
+        {
+        }
+
+        @Override
+        protected Action process()
+        {
+            if (BufferUtil.hasContent(_aggregate))
+            {
+                _flushed=true;
+                write(_aggregate, false, this);
+                return Action.SCHEDULED;
+            }
+
+            if (!_flushed)
+            {
+                _flushed=true;
+                write(BufferUtil.EMPTY_BUFFER,false,this);
+                return Action.SCHEDULED;
+            }
+
+            return Action.SUCCEEDED;
+        }
+    }
+
+
+
+    private class AsyncWrite extends AsyncICB
+    {
+        private final ByteBuffer _buffer;
+        private final ByteBuffer _slice;
+        private final boolean _complete;
+        private final int _len;
+        protected volatile boolean _completed;
+
+        public AsyncWrite(byte[] b, int off, int len, boolean complete)
+        {
+            _buffer=ByteBuffer.wrap(b, off, len);
+            _len=len;
+            // always use a view for large byte arrays to avoid JVM pooling large direct buffers
+            _slice=_len<getBufferSize()?null:_buffer.duplicate();
+            _complete=complete;
+        }
+
+        public AsyncWrite(ByteBuffer buffer, boolean complete)
+        {
+            _buffer=buffer;
+            _len=buffer.remaining();
+            // Use a slice buffer for large indirect to avoid JVM pooling large direct buffers
+            _slice=_buffer.isDirect()||_len<getBufferSize()?null:_buffer.duplicate();
+            _complete=complete;
+        }
+
+        @Override
+        protected Action process()
+        {
+            // flush any content from the aggregate
+            if (BufferUtil.hasContent(_aggregate))
+            {
+                _completed=_len==0;
+                write(_aggregate, _complete && _completed, this);
+                return Action.SCHEDULED;
+            }
+
+            // Can we just aggregate the remainder?
+            if (!_complete && _len<BufferUtil.space(_aggregate) && _len<_commitSize)
+            {
+                int position = BufferUtil.flipToFill(_aggregate);
+                BufferUtil.put(_buffer,_aggregate);
+                BufferUtil.flipToFlush(_aggregate, position);
+                return Action.SUCCEEDED;
+            }
+            
+            // Is there data left to write?
+            if (_buffer.hasRemaining())
+            {
+                // if there is no slice, just write it
+                if (_slice==null)
+                {
+                    _completed=true;
+                    write(_buffer, _complete, this);
+                    return Action.SCHEDULED;
+                }
+                
+                // otherwise take a slice
+                int p=_buffer.position();
+                int l=Math.min(getBufferSize(),_buffer.remaining());
+                int pl=p+l;
+                _slice.limit(pl);
+                _buffer.position(pl);
+                _slice.position(p);
+                _completed=!_buffer.hasRemaining();
+                write(_slice, _complete && _completed, this);
+                return Action.SCHEDULED;
+            }
+            
+            // all content written, but if we have not yet signal completion, we
+            // need to do so
+            if (_complete && !_completed)
+            {
+                _completed=true;
+                write(BufferUtil.EMPTY_BUFFER, _complete, this);
+                return Action.SCHEDULED;
+            }
+
+            return Action.SUCCEEDED;
+        }
+
+        @Override
+        protected void completed()
+        {
+            super.completed();
+            if (_complete)
+                closed();
+        }
+        
+        
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** An iterating callback that will take content from an
+     * InputStream and write it to the associated {@link HttpChannel}.
+     * A non direct buffer of size {@link HttpOutput#getBufferSize()} is used.
+     * This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to
+     * be notified as each buffer is written and only once all the input is consumed will the
+     * wrapped {@link Callback#succeeded()} method be called.
+     */
+    private class InputStreamWritingCB extends IteratingNestedCallback
+    {
+        private final InputStream _in;
+        private final ByteBuffer _buffer;
+        private boolean _eof;
+
+        public InputStreamWritingCB(InputStream in, Callback callback)
+        {
+            super(callback);
+            _in=in;
+            _buffer = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+        }
+
+        @Override
+        protected Action process() throws Exception
+        {
+            // Only return if EOF has previously been read and thus
+            // a write done with EOF=true
+            if (_eof)
+            {
+                // Handle EOF
+                _in.close();
+                closed();
+                _channel.getByteBufferPool().release(_buffer);
+                return Action.SUCCEEDED;
+            }
+            
+            // Read until buffer full or EOF
+            int len=0;
+            while (len<_buffer.capacity() && !_eof)
+            {
+                int r=_in.read(_buffer.array(),_buffer.arrayOffset()+len,_buffer.capacity()-len);
+                if (r<0)
+                    _eof=true;
+                else
+                    len+=r;
+            }
+
+            // write what we have
+            _buffer.position(0);
+            _buffer.limit(len);
+            write(_buffer,_eof,this);
+            return Action.SCHEDULED;
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            super.failed(x);
+            _channel.getByteBufferPool().release(_buffer);
+            try
+            {
+                _in.close();
+            }
+            catch (IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /** An iterating callback that will take content from a
+     * ReadableByteChannel and write it to the {@link HttpChannel}.
+     * A {@link ByteBuffer} of size {@link HttpOutput#getBufferSize()} is used that will be direct if
+     * {@link HttpChannel#useDirectBuffers()} is true.
+     * This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to
+     * be notified as each buffer is written and only once all the input is consumed will the
+     * wrapped {@link Callback#succeeded()} method be called.
+     */
+    private class ReadableByteChannelWritingCB extends IteratingNestedCallback
+    {
+        private final ReadableByteChannel _in;
+        private final ByteBuffer _buffer;
+        private boolean _eof;
+
+        public ReadableByteChannelWritingCB(ReadableByteChannel in, Callback callback)
+        {
+            super(callback);
+            _in=in;
+            _buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.useDirectBuffers());
+        }
+
+        @Override
+        protected Action process() throws Exception
+        {
+            // Only return if EOF has previously been read and thus
+            // a write done with EOF=true
+            if (_eof)
+            {
+                _in.close();
+                closed();
+                _channel.getByteBufferPool().release(_buffer);
+                return Action.SUCCEEDED;
+            }
+            
+            // Read from stream until buffer full or EOF
+            _buffer.clear();
+            while (_buffer.hasRemaining() && !_eof)
+              _eof = (_in.read(_buffer)) <  0;
+
+            // write what we have
+            _buffer.flip();
+            write(_buffer,_eof,this);
+
+            return Action.SCHEDULED;
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            super.failed(x);
+            _channel.getByteBufferPool().release(_buffer);
+            try
+            {
+                _in.close();
+            }
+            catch (IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpTransport.java b/lib/jetty/org/eclipse/jetty/server/HttpTransport.java
new file mode 100644 (file)
index 0000000..7058425
--- /dev/null
@@ -0,0 +1,40 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.util.Callback;
+
+public interface HttpTransport
+{
+    void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback);
+
+    void send(ByteBuffer content, boolean lastContent, Callback callback);
+    
+    void completed();
+    
+    /* ------------------------------------------------------------ */
+    /** Abort transport.
+     * This is called when an error response needs to be sent, but the response is already committed.
+     * Abort to should terminate the transport in a way that can indicate abnormal response to the client. 
+     */
+    void abort();
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpWriter.java b/lib/jetty/org/eclipse/jetty/server/HttpWriter.java
new file mode 100644 (file)
index 0000000..d02a8c4
--- /dev/null
@@ -0,0 +1,80 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.eclipse.jetty.util.ByteArrayOutputStream2;
+
+/**
+ * 
+ */
+public abstract class HttpWriter extends Writer
+{
+    public static final int MAX_OUTPUT_CHARS = 512; 
+    
+    final HttpOutput _out;
+    final ByteArrayOutputStream2 _bytes;
+    final char[] _chars;
+
+    /* ------------------------------------------------------------ */
+    public HttpWriter(HttpOutput out)
+    {
+        _out=out;
+        _chars=new char[MAX_OUTPUT_CHARS];
+        _bytes = new ByteArrayOutputStream2(MAX_OUTPUT_CHARS);   
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void close() throws IOException
+    {
+        _out.close();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void flush() throws IOException
+    {
+        _out.flush();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (String s,int offset, int length) throws IOException
+    {   
+        while (length > MAX_OUTPUT_CHARS)
+        {
+            write(s, offset, MAX_OUTPUT_CHARS);
+            offset += MAX_OUTPUT_CHARS;
+            length -= MAX_OUTPUT_CHARS;
+        }
+
+        s.getChars(offset, offset + length, _chars, 0);
+        write(_chars, 0, length);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (char[] s,int offset, int length) throws IOException
+    {         
+        throw new AbstractMethodError();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/InclusiveByteRange.java b/lib/jetty/org/eclipse/jetty/server/InclusiveByteRange.java
new file mode 100644 (file)
index 0000000..cb81d3c
--- /dev/null
@@ -0,0 +1,230 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Enumeration;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Byte range inclusive of end points.
+ * <PRE>
+ * 
+ *   parses the following types of byte ranges:
+ * 
+ *       bytes=100-499
+ *       bytes=-300
+ *       bytes=100-
+ *       bytes=1-2,2-3,6-,-2
+ *
+ *   given an entity length, converts range to string
+ * 
+ *       bytes 100-499/500
+ * 
+ * </PRE>
+ * 
+ * Based on RFC2616 3.12, 14.16, 14.35.1, 14.35.2
+ * <p>
+ * And yes the spec does strangely say that while 10-20, is bytes 10 to 20 and 10- is bytes 10 until the end that -20 IS NOT bytes 0-20, but the last 20 bytes of the content.
+ * 
+ * @version $version$
+ * 
+ */
+public class InclusiveByteRange 
+{
+    private static final Logger LOG = Log.getLogger(InclusiveByteRange.class);
+
+    long first = 0;
+    long last  = 0;    
+
+    public InclusiveByteRange(long first, long last)
+    {
+        this.first = first;
+        this.last = last;
+    }
+    
+    public long getFirst()
+    {
+        return first;
+    }
+
+    public long getLast()
+    {
+        return last;
+    }    
+
+
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param headers Enumeration of Range header fields.
+     * @param size Size of the resource.
+     * @return LazyList of satisfiable ranges
+     */
+    public static List<InclusiveByteRange> satisfiableRanges(Enumeration headers, long size)
+    {
+        Object satRanges=null;
+        
+        // walk through all Range headers
+    headers:
+        while (headers.hasMoreElements())
+        {
+            String header = (String) headers.nextElement();
+            StringTokenizer tok = new StringTokenizer(header,"=,",false);
+            String t=null;
+            try
+            {
+                // read all byte ranges for this header 
+                while (tok.hasMoreTokens())
+                {
+                    try
+                    {
+                        t = tok.nextToken().trim();
+
+                        long first = -1;
+                        long last = -1;
+                        int d = t.indexOf('-');
+                        if (d < 0 || t.indexOf("-",d + 1) >= 0)
+                        {
+                            if ("bytes".equals(t))
+                                continue;
+                            LOG.warn("Bad range format: {}",t);
+                            continue headers;
+                        }
+                        else if (d == 0)
+                        {
+                            if (d + 1 < t.length())
+                                last = Long.parseLong(t.substring(d + 1).trim());
+                            else
+                            {
+                                LOG.warn("Bad range format: {}",t);
+                                continue;
+                            }
+                        }
+                        else if (d + 1 < t.length())
+                        {
+                            first = Long.parseLong(t.substring(0,d).trim());
+                            last = Long.parseLong(t.substring(d + 1).trim());
+                        }
+                        else
+                            first = Long.parseLong(t.substring(0,d).trim());
+
+                        if (first == -1 && last == -1)
+                            continue headers;
+
+                        if (first != -1 && last != -1 && (first > last))
+                            continue headers;
+
+                        if (first < size)
+                        {
+                            InclusiveByteRange range = new InclusiveByteRange(first,last);
+                            satRanges = LazyList.add(satRanges,range);
+                        }
+                    }
+                    catch (NumberFormatException e)
+                    {
+                        LOG.warn("Bad range format: {}",t);
+                        LOG.ignore(e);
+                        continue;
+                    }
+                }
+            }
+            catch(Exception e)
+            {
+                LOG.warn("Bad range format: {}",t);
+                LOG.ignore(e);
+            }    
+        }
+        return LazyList.getList(satRanges,true);
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getFirst(long size)
+    {
+        if (first<0)
+        {
+            long tf=size-last;
+            if (tf<0)
+                tf=0;
+            return tf;
+        }
+        return first;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public long getLast(long size)
+    {
+        if (first<0)
+            return size-1;
+        
+        if (last<0 ||last>=size)
+            return size-1;
+        return last;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public long getSize(long size)
+    {
+        return getLast(size)-getFirst(size)+1;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public String toHeaderRangeString(long size)
+    {
+        StringBuilder sb = new StringBuilder(40);
+        sb.append("bytes ");
+        sb.append(getFirst(size));
+        sb.append('-');
+        sb.append(getLast(size));
+        sb.append("/");
+        sb.append(size);
+        return sb.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String to416HeaderRangeString(long size)
+    {
+        StringBuilder sb = new StringBuilder(40);
+        sb.append("bytes */");
+        sb.append(size);
+        return sb.toString();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder(60);
+        sb.append(Long.toString(first));
+        sb.append(":");
+        sb.append(Long.toString(last));
+        return sb.toString();
+    }
+
+
+}
+
+
+
diff --git a/lib/jetty/org/eclipse/jetty/server/Iso88591HttpWriter.java b/lib/jetty/org/eclipse/jetty/server/Iso88591HttpWriter.java
new file mode 100644 (file)
index 0000000..b4cdf8f
--- /dev/null
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+/**
+ */
+public class Iso88591HttpWriter extends HttpWriter
+{
+    /* ------------------------------------------------------------ */
+    public Iso88591HttpWriter(HttpOutput out)
+    {
+        super(out);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (char[] s,int offset, int length) throws IOException
+    {
+        HttpOutput out = _out;
+        if (length==0 && out.isAllContentWritten())
+        {
+            close();
+            return;
+        }
+
+        if (length==1)
+        {
+            int c=s[offset];
+            out.write(c<256?c:'?');
+            return;
+        }
+        
+        while (length > 0)
+        {
+            _bytes.reset();
+            int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
+
+            byte[] buffer=_bytes.getBuf();
+            int bytes=_bytes.getCount();
+
+            if (chars>buffer.length-bytes)
+                chars=buffer.length-bytes;
+
+            for (int i = 0; i < chars; i++)
+            {
+                int c = s[offset+i];
+                buffer[bytes++]=(byte)(c<256?c:'?');
+            }
+            if (bytes>=0)
+                _bytes.setCount(bytes);
+
+            _bytes.writeTo(out);
+            length-=chars;
+            offset+=chars;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/LocalConnector.java b/lib/jetty/org/eclipse/jetty/server/LocalConnector.java
new file mode 100644 (file)
index 0000000..5796226
--- /dev/null
@@ -0,0 +1,265 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.ByteArrayEndPoint;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class LocalConnector extends AbstractConnector
+{
+    private final BlockingQueue<LocalEndPoint> _connects = new LinkedBlockingQueue<>();
+
+
+    public LocalConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, ConnectionFactory... factories)
+    {
+        super(server,executor,scheduler,pool,acceptors,factories);
+        setIdleTimeout(30000);
+    }
+
+    public LocalConnector(Server server)
+    {
+        this(server, null, null, null, -1, new HttpConnectionFactory());
+    }
+
+    public LocalConnector(Server server, SslContextFactory sslContextFactory)
+    {
+        this(server, null, null, null, -1,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+    }
+
+    public LocalConnector(Server server, ConnectionFactory connectionFactory)
+    {
+        this(server, null, null, null, -1, connectionFactory);
+    }
+
+    public LocalConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
+    {
+        this(server, null, null, null, -1, AbstractConnectionFactory.getFactories(sslContextFactory,connectionFactory));
+    }
+
+    @Override
+    public Object getTransport()
+    {
+        return this;
+    }
+
+    /** Sends requests and get responses based on thread activity.
+     * Returns all the responses received once the thread activity has
+     * returned to the level it was before the requests.
+     * <p>
+     * This methods waits until the connection is closed or
+     * is idle for 1s before returning the responses.
+     * @param requests the requests
+     * @return the responses
+     * @throws Exception if the requests fail
+     */
+    public String getResponses(String requests) throws Exception
+    {
+        return getResponses(requests, 5, TimeUnit.SECONDS);
+    }
+
+    /** Sends requests and get responses based on thread activity.
+     * Returns all the responses received once the thread activity has
+     * returned to the level it was before the requests.
+     * <p>
+     * This methods waits until the connection is closed or
+     * an idle period before returning the responses.
+     * @param requests the requests
+     * @param idleFor The time the response stream must be idle for before returning
+     * @param units The units of idleFor
+     * @return the responses
+     * @throws Exception if the requests fail
+     */
+    public String getResponses(String requests,long idleFor,TimeUnit units) throws Exception
+    {
+        ByteBuffer result = getResponses(BufferUtil.toBuffer(requests,StandardCharsets.UTF_8),idleFor,units);
+        return result==null?null:BufferUtil.toString(result,StandardCharsets.UTF_8);
+    }
+
+    /** Sends requests and get's responses based on thread activity.
+     * Returns all the responses received once the thread activity has
+     * returned to the level it was before the requests.
+     * <p>
+     * This methods waits until the connection is closed or
+     * is idle for 1s before returning the responses.
+     * @param requestsBuffer the requests
+     * @return the responses
+     * @throws Exception if the requests fail
+     */
+    public ByteBuffer getResponses(ByteBuffer requestsBuffer) throws Exception
+    {
+        return getResponses(requestsBuffer, 5, TimeUnit.SECONDS);
+    }
+
+    /** Sends requests and get's responses based on thread activity.
+     * Returns all the responses received once the thread activity has
+     * returned to the level it was before the requests.
+     * <p>
+     * This methods waits until the connection is closed or
+     * an idle period before returning the responses.
+     * @param requestsBuffer the requests
+     * @param idleFor The time the response stream must be idle for before returning
+     * @param units The units of idleFor
+     * @return the responses
+     * @throws Exception if the requests fail
+     */
+    public ByteBuffer getResponses(ByteBuffer requestsBuffer,long idleFor,TimeUnit units) throws Exception
+    {
+        LOG.debug("requests {}", BufferUtil.toUTF8String(requestsBuffer));
+        LocalEndPoint endp = executeRequest(requestsBuffer);
+        endp.waitUntilClosedOrIdleFor(idleFor,units);
+        ByteBuffer responses = endp.takeOutput();
+        endp.getConnection().close();
+        LOG.debug("responses {}", BufferUtil.toUTF8String(responses));
+        return responses;
+    }
+
+    /**
+     * Execute a request and return the EndPoint through which
+     * responses can be received.
+     * @param rawRequest the request
+     * @return the local endpoint
+     */
+    public LocalEndPoint executeRequest(String rawRequest)
+    {
+        return executeRequest(BufferUtil.toBuffer(rawRequest, StandardCharsets.UTF_8));
+    }
+
+    private LocalEndPoint executeRequest(ByteBuffer rawRequest)
+    {
+        LocalEndPoint endp = new LocalEndPoint();
+        endp.setInput(rawRequest);
+        _connects.add(endp);
+        return endp;
+    }
+
+    @Override
+    protected void accept(int acceptorID) throws IOException, InterruptedException
+    {
+        LOG.debug("accepting {}", acceptorID);
+        LocalEndPoint endPoint = _connects.take();
+        endPoint.onOpen();
+        onEndPointOpened(endPoint);
+
+        Connection connection = getDefaultConnectionFactory().newConnection(this, endPoint);
+        endPoint.setConnection(connection);
+
+        connection.onOpen();
+    }
+
+    public class LocalEndPoint extends ByteArrayEndPoint
+    {
+        private final CountDownLatch _closed = new CountDownLatch(1);
+
+        public LocalEndPoint()
+        {
+            super(getScheduler(), LocalConnector.this.getIdleTimeout());
+            setGrowOutput(true);
+        }
+
+        public void addInput(String s)
+        {
+            // TODO this is a busy wait
+            while(getIn()==null || BufferUtil.hasContent(getIn()))
+                Thread.yield();
+            setInput(BufferUtil.toBuffer(s, StandardCharsets.UTF_8));
+        }
+
+        @Override
+        public void close()
+        {
+            boolean wasOpen=isOpen();
+            super.close();
+            if (wasOpen)
+            {
+//                connectionClosed(getConnection());
+                getConnection().onClose();
+                onClose();
+            }
+        }
+
+        @Override
+        public void onClose()
+        {
+            LocalConnector.this.onEndPointClosed(this);
+            super.onClose();
+            _closed.countDown();
+        }
+
+        @Override
+        public void shutdownOutput()
+        {
+            super.shutdownOutput();
+            close();
+        }
+
+        public void waitUntilClosed()
+        {
+            while (isOpen())
+            {
+                try
+                {
+                    if (!_closed.await(10,TimeUnit.SECONDS))
+                        break;
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+
+        public void waitUntilClosedOrIdleFor(long idleFor,TimeUnit units)
+        {
+            Thread.yield();
+            int size=getOutput().remaining();
+            while (isOpen())
+            {
+                try
+                {
+                    if (!_closed.await(idleFor,units))
+                    {
+                        if (size==getOutput().remaining())
+                        {
+                            LOG.debug("idle for {} {}",idleFor,units);
+                            return;
+                        }
+                        size=getOutput().remaining();
+                    }
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/LowResourceMonitor.java b/lib/jetty/org/eclipse/jetty/server/LowResourceMonitor.java
new file mode 100644 (file)
index 0000000..1aad488
--- /dev/null
@@ -0,0 +1,350 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+
+/* ------------------------------------------------------------ */
+/** A monitor for low resources
+ * <p>An instance of this class will monitor all the connectors of a server (or a set of connectors
+ * configured with {@link #setMonitoredConnectors(Collection)}) for a low resources state.
+ * Low resources can be detected by:<ul>
+ * <li>{@link ThreadPool#isLowOnThreads()} if {@link Connector#getExecutor()} is
+ * an instance of {@link ThreadPool} and {@link #setMonitorThreads(boolean)} is true.<li>
+ * <li>If {@link #setMaxMemory(long)} is non zero then low resources is detected if the JVMs
+ * {@link Runtime} instance has {@link Runtime#totalMemory()} minus {@link Runtime#freeMemory()}
+ * greater than {@link #getMaxMemory()}</li>
+ * <li>If {@link #setMaxConnections(int)} is non zero then low resources is dected if the total number
+ * of connections exceeds {@link #getMaxConnections()}</li>
+ * </ul>
+ * </p>
+ * <p>Once low resources state is detected, the cause is logged and all existing connections returned
+ * by {@link Connector#getConnectedEndPoints()} have {@link EndPoint#setIdleTimeout(long)} set
+ * to {@link #getLowResourcesIdleTimeout()}.  New connections are not affected, however if the low
+ * resources state persists for more than {@link #getMaxLowResourcesTime()}, then the
+ * {@link #getLowResourcesIdleTimeout()} to all connections again.  Once the low resources state is
+ * cleared, the idle timeout is reset to the connector default given by {@link Connector#getIdleTimeout()}.
+ * </p>
+ */
+@ManagedObject ("Monitor for low resource conditions and activate a low resource mode if detected")
+public class LowResourceMonitor extends AbstractLifeCycle
+{
+    private static final Logger LOG = Log.getLogger(LowResourceMonitor.class);
+    private final Server _server;
+    private Scheduler _scheduler;
+    private Connector[] _monitoredConnectors;
+    private int _period=1000;
+    private int _maxConnections;
+    private long _maxMemory;
+    private int _lowResourcesIdleTimeout=1000;
+    private int _maxLowResourcesTime=0;
+    private boolean _monitorThreads=true;
+    private final AtomicBoolean _low = new AtomicBoolean();
+    private String _cause;
+    private String _reasons;
+    private long _lowStarted;
+
+
+    private final Runnable _monitor = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            if (isRunning())
+            {
+                monitor();
+                _scheduler.schedule(_monitor,_period,TimeUnit.MILLISECONDS);
+            }
+        }
+    };
+
+    public LowResourceMonitor(@Name("server") Server server)
+    {
+        _server=server;
+    }
+
+    @ManagedAttribute("Are the monitored connectors low on resources?")
+    public boolean isLowOnResources()
+    {
+        return _low.get();
+    }
+
+    @ManagedAttribute("The reason(s) the monitored connectors are low on resources")
+    public String getLowResourcesReasons()
+    {
+        return _reasons;
+    }
+
+    @ManagedAttribute("Get the timestamp in ms since epoch that low resources state started")
+    public long getLowResourcesStarted()
+    {
+        return _lowStarted;
+    }
+
+    @ManagedAttribute("The monitored connectors. If null then all server connectors are monitored")
+    public Collection<Connector> getMonitoredConnectors()
+    {
+        if (_monitoredConnectors==null)
+            return Collections.emptyList();
+        return Arrays.asList(_monitoredConnectors);
+    }
+
+    /**
+     * @param monitoredConnectors The collections of Connectors that should be monitored for low resources.
+     */
+    public void setMonitoredConnectors(Collection<Connector> monitoredConnectors)
+    {
+        if (monitoredConnectors==null || monitoredConnectors.size()==0)
+            _monitoredConnectors=null;
+        else
+            _monitoredConnectors = monitoredConnectors.toArray(new Connector[monitoredConnectors.size()]);
+    }
+
+    @ManagedAttribute("The monitor period in ms")
+    public int getPeriod()
+    {
+        return _period;
+    }
+
+    /**
+     * @param periodMS The period in ms to monitor for low resources
+     */
+    public void setPeriod(int periodMS)
+    {
+        _period = periodMS;
+    }
+
+    @ManagedAttribute("True if low available threads status is monitored")
+    public boolean getMonitorThreads()
+    {
+        return _monitorThreads;
+    }
+
+    /**
+     * @param monitorThreads If true, check connectors executors to see if they are
+     * {@link ThreadPool} instances that are low on threads.
+     */
+    public void setMonitorThreads(boolean monitorThreads)
+    {
+        _monitorThreads = monitorThreads;
+    }
+
+    @ManagedAttribute("The maximum connections allowed for the monitored connectors before low resource handling is activated")
+    public int getMaxConnections()
+    {
+        return _maxConnections;
+    }
+
+    /**
+     * @param maxConnections The maximum connections before low resources state is triggered
+     */
+    public void setMaxConnections(int maxConnections)
+    {
+        _maxConnections = maxConnections;
+    }
+
+    @ManagedAttribute("The maximum memory (in bytes) that can be used before low resources is triggered.  Memory used is calculated as (totalMemory-freeMemory).")
+    public long getMaxMemory()
+    {
+        return _maxMemory;
+    }
+
+    /**
+     * @param maxMemoryBytes The maximum memory in bytes in use before low resources is triggered.
+     */
+    public void setMaxMemory(long maxMemoryBytes)
+    {
+        _maxMemory = maxMemoryBytes;
+    }
+
+    @ManagedAttribute("The idletimeout in ms to apply to all existing connections when low resources is detected")
+    public int getLowResourcesIdleTimeout()
+    {
+        return _lowResourcesIdleTimeout;
+    }
+
+    /**
+     * @param lowResourcesIdleTimeoutMS The timeout in ms to apply to EndPoints when in the low resources state.
+     */
+    public void setLowResourcesIdleTimeout(int lowResourcesIdleTimeoutMS)
+    {
+        _lowResourcesIdleTimeout = lowResourcesIdleTimeoutMS;
+    }
+
+    @ManagedAttribute("The maximum time in ms that low resources condition can persist before lowResourcesIdleTimeout is applied to new connections as well as existing connections")
+    public int getMaxLowResourcesTime()
+    {
+        return _maxLowResourcesTime;
+    }
+
+    /**
+     * @param maxLowResourcesTimeMS The time in milliseconds that a low resource state can persist before the low resource idle timeout is reapplied to all connections
+     */
+    public void setMaxLowResourcesTime(int maxLowResourcesTimeMS)
+    {
+        _maxLowResourcesTime = maxLowResourcesTimeMS;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        _scheduler = _server.getBean(Scheduler.class);
+
+        if (_scheduler==null)
+        {
+            _scheduler=new LRMScheduler();
+            _scheduler.start();
+        }
+        super.doStart();
+
+        _scheduler.schedule(_monitor,_period,TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        if (_scheduler instanceof LRMScheduler)
+            _scheduler.stop();
+        super.doStop();
+    }
+
+    protected Connector[] getMonitoredOrServerConnectors()
+    {
+        if (_monitoredConnectors!=null && _monitoredConnectors.length>0)
+            return _monitoredConnectors;
+        return _server.getConnectors();
+    }
+
+    protected void monitor()
+    {
+        String reasons=null;
+        String cause="";
+        int connections=0;
+
+        for(Connector connector : getMonitoredOrServerConnectors())
+        {
+            connections+=connector.getConnectedEndPoints().size();
+
+            Executor executor = connector.getExecutor();
+            if (executor instanceof ThreadPool)
+            {
+                ThreadPool threadpool=(ThreadPool) executor;
+                if (_monitorThreads && threadpool.isLowOnThreads())
+                {
+                    reasons=low(reasons,"Low on threads: "+threadpool);
+                    cause+="T";
+                }
+            }
+        }
+
+        if (_maxConnections>0 && connections>_maxConnections)
+        {
+            reasons=low(reasons,"Max Connections exceeded: "+connections+">"+_maxConnections);
+            cause+="C";
+        }
+
+        long memory=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
+        if (_maxMemory>0 && memory>_maxMemory)
+        {
+            reasons=low(reasons,"Max memory exceeded: "+memory+">"+_maxMemory);
+            cause+="M";
+        }
+
+
+        if (reasons!=null)
+        {
+            // Log the reasons if there is any change in the cause
+            if (!cause.equals(_cause))
+            {
+                LOG.warn("Low Resources: {}",reasons);
+                _cause=cause;
+            }
+
+            // Enter low resources state?
+            if (_low.compareAndSet(false,true))
+            {
+                _reasons=reasons;
+                _lowStarted=System.currentTimeMillis();
+                setLowResources();
+            }
+
+            // Too long in low resources state?
+            if (_maxLowResourcesTime>0 && (System.currentTimeMillis()-_lowStarted)>_maxLowResourcesTime)
+                setLowResources();
+        }
+        else
+        {
+            if (_low.compareAndSet(true,false))
+            {
+                LOG.info("Low Resources cleared");
+                _reasons=null;
+                _lowStarted=0;
+                _cause=null;
+                clearLowResources();
+            }
+        }
+    }
+
+    protected void setLowResources()
+    {
+        for(Connector connector : getMonitoredOrServerConnectors())
+        {
+            for (EndPoint endPoint : connector.getConnectedEndPoints())
+                endPoint.setIdleTimeout(_lowResourcesIdleTimeout);
+        }
+    }
+
+    protected void clearLowResources()
+    {
+        for(Connector connector : getMonitoredOrServerConnectors())
+        {
+            for (EndPoint endPoint : connector.getConnectedEndPoints())
+                endPoint.setIdleTimeout(connector.getIdleTimeout());
+        }
+    }
+
+    private String low(String reasons, String newReason)
+    {
+        if (reasons==null)
+            return newReason;
+        return reasons+", "+newReason;
+    }
+
+
+    private static class LRMScheduler extends ScheduledExecutorScheduler
+    {
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/NCSARequestLog.java b/lib/jetty/org/eclipse/jetty/server/NCSARequestLog.java
new file mode 100644 (file)
index 0000000..2c2286e
--- /dev/null
@@ -0,0 +1,281 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.TimeZone;
+
+import org.eclipse.jetty.util.RolloverFileOutputStream;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+/**
+ * This {@link RequestLog} implementation outputs logs in the pseudo-standard
+ * NCSA common log format. Configuration options allow a choice between the
+ * standard Common Log Format (as used in the 3 log format) and the Combined Log
+ * Format (single log format). This log format can be output by most web
+ * servers, and almost all web log analysis software can understand these
+ * formats.
+ */
+@ManagedObject("NCSA standard format request log")
+public class NCSARequestLog extends AbstractNCSARequestLog implements RequestLog
+{
+    private String _filename;
+    private boolean _append;
+    private int _retainDays;
+    private boolean _closeOut;
+    private String _filenameDateFormat = null;
+    private transient OutputStream _out;
+    private transient OutputStream _fileOut;
+    private transient Writer _writer;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create request log object with default settings.
+     */
+    public NCSARequestLog()
+    {
+        setExtended(true);
+        _append = true;
+        _retainDays = 31;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create request log object with specified output file name.
+     *
+     * @param filename the file name for the request log.
+     *                 This may be in the format expected
+     *                 by {@link RolloverFileOutputStream}
+     */
+    public NCSARequestLog(String filename)
+    {
+        setExtended(true);
+        _append = true;
+        _retainDays = 31;
+        setFilename(filename);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the output file name of the request log.
+     * The file name may be in the format expected by
+     * {@link RolloverFileOutputStream}.
+     *
+     * @param filename file name of the request log
+     *
+     */
+    public void setFilename(String filename)
+    {
+        if (filename != null)
+        {
+            filename = filename.trim();
+            if (filename.length() == 0)
+                filename = null;
+        }
+        _filename = filename;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the output file name of the request log.
+     *
+     * @return file name of the request log
+     */
+    @ManagedAttribute("file of log")
+    public String getFilename()
+    {
+        return _filename;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the file name of the request log with the expanded
+     * date wildcard if the output is written to the disk using
+     * {@link RolloverFileOutputStream}.
+     *
+     * @return file name of the request log, or null if not applicable
+     */
+    public String getDatedFilename()
+    {
+        if (_fileOut instanceof RolloverFileOutputStream)
+            return ((RolloverFileOutputStream)_fileOut).getDatedFilename();
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected boolean isEnabled()
+    {
+        return (_fileOut != null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the number of days before rotated log files are deleted.
+     *
+     * @param retainDays number of days to keep a log file
+     */
+    public void setRetainDays(int retainDays)
+    {
+        _retainDays = retainDays;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the number of days before rotated log files are deleted.
+     *
+     * @return number of days to keep a log file
+     */
+    @ManagedAttribute("number of days that log files are kept")
+    public int getRetainDays()
+    {
+        return _retainDays;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set append to log flag.
+     *
+     * @param append true - request log file will be appended after restart,
+     *               false - request log file will be overwritten after restart
+     */
+    public void setAppend(boolean append)
+    {
+        _append = append;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve append to log flag.
+     *
+     * @return value of the flag
+     */
+    @ManagedAttribute("existing log files are appends to the new one")
+    public boolean isAppend()
+    {
+        return _append;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the log file name date format.
+     * @see RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)
+     *
+     * @param logFileDateFormat format string that is passed to {@link RolloverFileOutputStream}
+     */
+    public void setFilenameDateFormat(String logFileDateFormat)
+    {
+        _filenameDateFormat = logFileDateFormat;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the file name date format string.
+     *
+     * @return the log File Date Format
+     */
+    public String getFilenameDateFormat()
+    {
+        return _filenameDateFormat;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(String requestEntry) throws IOException
+    {
+        synchronized(this)
+        {
+            if (_writer==null)
+                return;
+            _writer.write(requestEntry);
+            _writer.write(StringUtil.__LINE_SEPARATOR);
+            _writer.flush();
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Set up request logging and open log file.
+     *
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected synchronized void doStart() throws Exception
+    {
+        if (_filename != null)
+        {
+            _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(getLogTimeZone()),_filenameDateFormat,null);
+            _closeOut = true;
+            LOG.info("Opened " + getDatedFilename());
+        }
+        else
+            _fileOut = System.err;
+
+        _out = _fileOut;
+
+        synchronized(this)
+        {
+            _writer = new OutputStreamWriter(_out);
+        }
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Close the log file and perform cleanup.
+     *
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        synchronized (this)
+        {
+            super.doStop();
+            try
+            {
+                if (_writer != null)
+                    _writer.flush();
+            }
+            catch (IOException e)
+            {
+                LOG.ignore(e);
+            }
+            if (_out != null && _closeOut)
+                try
+                {
+                    _out.close();
+                }
+                catch (IOException e)
+                {
+                    LOG.ignore(e);
+                }
+
+            _out = null;
+            _fileOut = null;
+            _closeOut = false;
+            _writer = null;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnection.java b/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnection.java
new file mode 100644 (file)
index 0000000..3d01069
--- /dev/null
@@ -0,0 +1,163 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class NegotiatingServerConnection extends AbstractConnection
+{
+    private static final Logger LOG = Log.getLogger(NegotiatingServerConnection.class);
+
+    private final Connector connector;
+    private final SSLEngine engine;
+    private final List<String> protocols;
+    private final String defaultProtocol;
+    private String protocol; // No need to be volatile: it is modified and read by the same thread
+
+    protected NegotiatingServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol)
+    {
+        super(endPoint, connector.getExecutor());
+        this.connector = connector;
+        this.protocols = protocols;
+        this.defaultProtocol = defaultProtocol;
+        this.engine = engine;
+    }
+
+    protected List<String> getProtocols()
+    {
+        return protocols;
+    }
+
+    protected String getDefaultProtocol()
+    {
+        return defaultProtocol;
+    }
+
+    protected SSLEngine getSSLEngine()
+    {
+        return engine;
+    }
+
+    protected String getProtocol()
+    {
+        return protocol;
+    }
+
+    protected void setProtocol(String protocol)
+    {
+        this.protocol = protocol;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        fillInterested();
+    }
+
+    @Override
+    public void onFillable()
+    {
+        int filled = fill();
+
+        if (filled == 0)
+        {
+            if (protocol == null)
+            {
+                if (engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
+                {
+                    // Here the SSL handshake is finished, but the protocol has not been negotiated.
+                    LOG.debug("{} could not negotiate protocol, SSLEngine: {}", this, engine);
+                    close();
+                }
+                else
+                {
+                    // Here the SSL handshake is not finished yet but we filled 0 bytes,
+                    // so we need to read more.
+                    fillInterested();
+                }
+            }
+            else
+            {
+                ConnectionFactory connectionFactory = connector.getConnectionFactory(protocol);
+                if (connectionFactory == null)
+                {
+                    LOG.debug("{} application selected protocol '{}', but no correspondent {} has been configured",
+                            this, protocol, ConnectionFactory.class.getName());
+                    close();
+                }
+                else
+                {
+                    EndPoint endPoint = getEndPoint();
+                    Connection oldConnection = endPoint.getConnection();
+                    Connection newConnection = connectionFactory.newConnection(connector, endPoint);
+                    LOG.debug("{} switching from {} to {}", this, oldConnection, newConnection);
+                    oldConnection.onClose();
+                    endPoint.setConnection(newConnection);
+                    getEndPoint().getConnection().onOpen();
+                }
+            }
+        }
+        else if (filled < 0)
+        {
+            // Something went bad, we need to close.
+            LOG.debug("{} closing on client close", this);
+            close();
+        }
+        else
+        {
+            // Must never happen, since we fill using an empty buffer
+            throw new IllegalStateException();
+        }
+    }
+
+    private int fill()
+    {
+        try
+        {
+            return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
+        }
+        catch (IOException x)
+        {
+            LOG.debug(x);
+            close();
+            return -1;
+        }
+    }
+
+    @Override
+    public void close()
+    {
+        // Gentler close for SSL.
+        getEndPoint().shutdownOutput();
+        super.close();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java b/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java
new file mode 100644 (file)
index 0000000..f826d71
--- /dev/null
@@ -0,0 +1,103 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+
+public abstract class NegotiatingServerConnectionFactory extends AbstractConnectionFactory
+{
+    private final List<String> protocols;
+    private String defaultProtocol;
+
+    public NegotiatingServerConnectionFactory(String protocol, String... protocols)
+    {
+        super(protocol);
+        this.protocols = Arrays.asList(protocols);
+    }
+
+    public String getDefaultProtocol()
+    {
+        return defaultProtocol;
+    }
+
+    public void setDefaultProtocol(String defaultProtocol)
+    {
+        this.defaultProtocol = defaultProtocol;
+    }
+
+    public List<String> getProtocols()
+    {
+        return protocols;
+    }
+
+    @Override
+    public Connection newConnection(Connector connector, EndPoint endPoint)
+    {
+        List<String> protocols = this.protocols;
+        if (protocols.isEmpty())
+        {
+            protocols = connector.getProtocols();
+            Iterator<String> i = protocols.iterator();
+            while (i.hasNext())
+            {
+                String protocol = i.next();
+                String prefix = "ssl-";
+                if (protocol.regionMatches(true, 0, prefix, 0, prefix.length()) || protocol.equalsIgnoreCase("alpn"))
+                {
+                    i.remove();
+                }
+            }
+        }
+
+        String dft = defaultProtocol;
+        if (dft == null && !protocols.isEmpty())
+            dft = protocols.get(0);
+
+        SSLEngine engine = null;
+        EndPoint ep = endPoint;
+        while (engine == null && ep != null)
+        {
+            // TODO make more generic
+            if (ep instanceof SslConnection.DecryptedEndPoint)
+                engine = ((SslConnection.DecryptedEndPoint)ep).getSslConnection().getSSLEngine();
+            else
+                ep = null;
+        }
+
+        return configure(newServerConnection(connector, endPoint, engine, protocols, dft), connector, endPoint);
+    }
+
+    protected abstract AbstractConnection newServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol);
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s,%s,%s}", getClass().getSimpleName(), hashCode(), getProtocol(), getDefaultProtocol(), getProtocols());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/NetworkConnector.java b/lib/jetty/org/eclipse/jetty/server/NetworkConnector.java
new file mode 100644 (file)
index 0000000..58fdc98
--- /dev/null
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * <p>A {@link Connector} for TCP/IP network connectors</p>
+ */
+public interface NetworkConnector extends Connector, Closeable
+{
+    /**
+     * <p>Performs the activities needed to open the network communication
+     * (for example, to start accepting incoming network connections).</p>
+     *
+     * @throws IOException if this connector cannot be opened
+     * @see #close()
+     */
+    void open() throws IOException;
+
+    /**
+     * <p>Performs the activities needed to close the network communication
+     * (for example, to stop accepting network connections).</p>
+     * Once a connector has been closed, it cannot be opened again without first
+     * calling {@link #stop()} and it will not be active again until a subsequent call to {@link #start()}
+     */
+    @Override
+    void close();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * A Connector may be opened and not started (to reserve a port)
+     * or closed and running (to allow graceful shutdown of existing connections)
+     * @return True if the connector is Open.
+     */
+    boolean isOpen();
+    
+    /**
+     * @return The hostname representing the interface to which
+     * this connector will bind, or null for all interfaces.
+     */
+    String getHost();
+
+    /**
+     * @return The configured port for the connector or 0 if any available
+     * port may be used.
+     */
+    int getPort();
+
+    /**
+     * @return The actual port the connector is listening on, or
+     * -1 if it has not been opened, or -2 if it has been closed.
+     */
+    int getLocalPort();
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/NetworkTrafficServerConnector.java b/lib/jetty/org/eclipse/jetty/server/NetworkTrafficServerConnector.java
new file mode 100644 (file)
index 0000000..34de615
--- /dev/null
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.NetworkTrafficListener;
+import org.eclipse.jetty.io.NetworkTrafficSelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * <p>A specialized version of {@link ServerConnector} that supports {@link NetworkTrafficListener}s.</p>
+ * <p>{@link NetworkTrafficListener}s can be added and removed dynamically before and after this connector has
+ * been started without causing {@link java.util.ConcurrentModificationException}s.</p>
+ */
+public class NetworkTrafficServerConnector extends ServerConnector
+{
+    private final List<NetworkTrafficListener> listeners = new CopyOnWriteArrayList<>();
+
+    public NetworkTrafficServerConnector(Server server)
+    {
+        this(server, null, null, null, 0, 0, new HttpConnectionFactory());
+    }
+
+    public NetworkTrafficServerConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
+    {
+        super(server, sslContextFactory, connectionFactory);
+    }
+
+    public NetworkTrafficServerConnector(Server server, ConnectionFactory connectionFactory)
+    {
+        super(server, connectionFactory);
+    }
+
+    public NetworkTrafficServerConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, int selectors, ConnectionFactory... factories)
+    {
+        super(server, executor, scheduler, pool, acceptors, selectors, factories);
+    }
+
+    public NetworkTrafficServerConnector(Server server, SslContextFactory sslContextFactory)
+    {
+        super(server, sslContextFactory);
+    }
+
+    /**
+     * @param listener the listener to add
+     */
+    public void addNetworkTrafficListener(NetworkTrafficListener listener)
+    {
+        listeners.add(listener);
+    }
+
+    /**
+     * @param listener the listener to remove
+     */
+    public void removeNetworkTrafficListener(NetworkTrafficListener listener)
+    {
+        listeners.remove(listener);
+    }
+
+    @Override
+    protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key) throws IOException
+    {
+        NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners);
+        return endPoint;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/QueuedHttpInput.java b/lib/jetty/org/eclipse/jetty/server/QueuedHttpInput.java
new file mode 100644 (file)
index 0000000..881fbfa
--- /dev/null
@@ -0,0 +1,143 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+import org.eclipse.jetty.util.ArrayQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * {@link QueuedHttpInput} holds a queue of items passed to it by calls to {@link #content(Object)}.
+ * <p/>
+ * {@link QueuedHttpInput} stores the items directly; if the items contain byte buffers, it does not copy them
+ * but simply holds references to the item, thus the caller must organize for those buffers to valid while
+ * held by this class.
+ * <p/>
+ * To assist the caller, subclasses may override methods {@link #onAsyncRead()}, {@link #onContentConsumed(Object)}
+ * that can be implemented so that the caller will know when buffers are queued and consumed.
+ */
+public abstract class QueuedHttpInput<T> extends HttpInput<T>
+{
+    private final static Logger LOG = Log.getLogger(QueuedHttpInput.class);
+
+    private final ArrayQueue<T> _inputQ = new ArrayQueue<>(lock());
+
+    public QueuedHttpInput()
+    {
+    }
+
+    public void content(T item)
+    {
+        // The buffer is not copied here.  This relies on the caller not recycling the buffer
+        // until the it is consumed.  The onContentConsumed and onAllContentConsumed() callbacks are
+        // the signals to the caller that the buffers can be recycled.
+
+        synchronized (lock())
+        {
+            boolean wasEmpty = _inputQ.isEmpty();
+            _inputQ.add(item);
+            LOG.debug("{} queued {}", this, item);
+            if (wasEmpty)
+            {
+                if (!onAsyncRead())
+                    lock().notify();
+            }
+        }
+    }
+
+    public void recycle()
+    {
+        synchronized (lock())
+        {
+            T item = _inputQ.pollUnsafe();
+            while (item != null)
+            {
+                onContentConsumed(item);
+                item = _inputQ.pollUnsafe();
+            }
+            super.recycle();
+        }
+    }
+
+    @Override
+    protected T nextContent()
+    {
+        synchronized (lock())
+        {
+            // Items are removed only when they are fully consumed.
+            T item = _inputQ.peekUnsafe();
+            // Skip consumed items at the head of the queue.
+            while (item != null && remaining(item) == 0)
+            {
+                _inputQ.pollUnsafe();
+                onContentConsumed(item);
+                LOG.debug("{} consumed {}", this, item);
+                item = _inputQ.peekUnsafe();
+            }
+            return item;
+        }
+    }
+
+    protected void blockForContent() throws IOException
+    {
+        synchronized (lock())
+        {
+            while (_inputQ.isEmpty() && !isFinished() && !isEOF())
+            {
+                try
+                {
+                    LOG.debug("{} waiting for content", this);
+                    lock().wait();
+                }
+                catch (InterruptedException e)
+                {
+                    throw (IOException)new InterruptedIOException().initCause(e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Callback that signals that the given content has been consumed.
+     *
+     * @param item the consumed content
+     */
+    protected abstract void onContentConsumed(T item);
+
+    public void earlyEOF()
+    {
+        synchronized (lock())
+        {
+            super.earlyEOF();
+            lock().notify();
+        }
+    }
+
+    public void messageComplete()
+    {
+        synchronized (lock())
+        {
+            super.messageComplete();
+            lock().notify();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/QuietServletException.java b/lib/jetty/org/eclipse/jetty/server/QuietServletException.java
new file mode 100644 (file)
index 0000000..5221d63
--- /dev/null
@@ -0,0 +1,53 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.ServletException;
+
+
+/* ------------------------------------------------------------ */
+/** A ServletException that is logged less verbosely than
+ * a normal ServletException.
+ * <p>
+ * Used for container generated exceptions that need only a message rather
+ * than a stack trace.
+ * </p>
+ */
+public class QuietServletException extends ServletException
+{
+    public QuietServletException()
+    {
+        super();
+    }
+
+    public QuietServletException(String message, Throwable rootCause)
+    {
+        super(message,rootCause);
+    }
+
+    public QuietServletException(String message)
+    {
+        super(message);
+    }
+
+    public QuietServletException(Throwable rootCause)
+    {
+        super(rootCause);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Request.java b/lib/jetty/org/eclipse/jetty/server/Request.java
new file mode 100644 (file)
index 0000000..77c80ab
--- /dev/null
@@ -0,0 +1,2265 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
+import javax.servlet.http.Part;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.MultiPartInputStreamParser;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Jetty Request.
+ * <p>
+ * Implements {@link javax.servlet.http.HttpServletRequest} from the <code>javax.servlet.http</code> package.
+ * </p>
+ * <p>
+ * The standard interface of mostly getters, is extended with setters so that the request is mutable by the handlers that it is passed to. This allows the
+ * request object to be as lightweight as possible and not actually implement any significant behavior. For example
+ * <ul>
+ *
+ * <li>The {@link Request#getContextPath()} method will return null, until the request has been passed to a {@link ContextHandler} which matches the
+ * {@link Request#getPathInfo()} with a context path and calls {@link Request#setContextPath(String)} as a result.</li>
+ *
+ * <li>the HTTP session methods will all return null sessions until such time as a request has been passed to a
+ * {@link org.eclipse.jetty.server.session.SessionHandler} which checks for session cookies and enables the ability to create new sessions.</li>
+ *
+ * <li>The {@link Request#getServletPath()} method will return null until the request has been passed to a <code>org.eclipse.jetty.servlet.ServletHandler</code>
+ * and the pathInfo matched against the servlet URL patterns and {@link Request#setServletPath(String)} called as a result.</li>
+ * </ul>
+ *
+ * A request instance is created for each connection accepted by the server and recycled for each HTTP request received via that connection.
+ * An effort is made to avoid reparsing headers and cookies that are likely to be the same for requests from the same connection.
+ *
+ * <p>
+ * The form content that a request can process is limited to protect from Denial of Service attacks. The size in bytes is limited by
+ * {@link ContextHandler#getMaxFormContentSize()} or if there is no context then the "org.eclipse.jetty.server.Request.maxFormContentSize" {@link Server}
+ * attribute. The number of parameters keys is limited by {@link ContextHandler#getMaxFormKeys()} or if there is no context then the
+ * "org.eclipse.jetty.server.Request.maxFormKeys" {@link Server} attribute.
+ *
+ *
+ */
+public class Request implements HttpServletRequest
+{
+    public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig";
+    public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.jetty.multiPartInputStream";
+    public static final String __MULTIPART_CONTEXT = "org.eclipse.jetty.multiPartContext";
+
+    private static final Logger LOG = Log.getLogger(Request.class);
+    private static final Collection<Locale> __defaultLocale = Collections.singleton(Locale.getDefault());
+    private static final int __NONE = 0, _STREAM = 1, __READER = 2;
+
+    private final HttpChannel<?> _channel;
+    private final HttpFields _fields=new HttpFields();
+    private final List<ServletRequestAttributeListener>  _requestAttributeListeners=new ArrayList<>();
+    private final HttpInput<?> _input;
+    
+    public static class MultiPartCleanerListener implements ServletRequestListener
+    {
+        @Override
+        public void requestDestroyed(ServletRequestEvent sre)
+        {
+            //Clean up any tmp files created by MultiPartInputStream
+            MultiPartInputStreamParser mpis = (MultiPartInputStreamParser)sre.getServletRequest().getAttribute(__MULTIPART_INPUT_STREAM);
+            if (mpis != null)
+            {
+                ContextHandler.Context context = (ContextHandler.Context)sre.getServletRequest().getAttribute(__MULTIPART_CONTEXT);
+
+                //Only do the cleanup if we are exiting from the context in which a servlet parsed the multipart files
+                if (context == sre.getServletContext())
+                {
+                    try
+                    {
+                        mpis.deleteParts();
+                    }
+                    catch (MultiException e)
+                    {
+                        sre.getServletContext().log("Errors deleting multipart tmp files", e);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void requestInitialized(ServletRequestEvent sre)
+        {
+            //nothing to do, multipart config set up by ServletHolder.handle()
+        }
+        
+    }
+    
+    
+
+    private boolean _secure;
+    private boolean _asyncSupported = true;
+    private boolean _newContext;
+    private boolean _cookiesExtracted = false;
+    private boolean _handled = false;
+    private boolean _paramsExtracted;
+    private boolean _requestedSessionIdFromCookie = false;
+    private volatile Attributes _attributes;
+    private Authentication _authentication;
+    private String _characterEncoding;
+    private ContextHandler.Context _context;
+    private String _contextPath;
+    private CookieCutter _cookies;
+    private DispatcherType _dispatcherType;
+    private int _inputState = __NONE;
+    private HttpMethod _httpMethod;
+    private String _httpMethodString;
+    private MultiMap<String> _queryParameters;
+    private MultiMap<String> _contentParameters;
+    private MultiMap<String> _parameters;
+    private String _pathInfo;
+    private int _port;
+    private HttpVersion _httpVersion = HttpVersion.HTTP_1_1;
+    private String _queryEncoding;
+    private String _queryString;
+    private BufferedReader _reader;
+    private String _readerEncoding;
+    private InetSocketAddress _remote;
+    private String _requestedSessionId;
+    private String _requestURI;
+    private Map<Object, HttpSession> _savedNewSessions;
+    private String _scheme = URIUtil.HTTP;
+    private UserIdentity.Scope _scope;
+    private String _serverName;
+    private String _servletPath;
+    private HttpSession _session;
+    private SessionManager _sessionManager;
+    private long _timeStamp;
+    private HttpURI _uri;
+    private MultiPartInputStreamParser _multiPartInputStream; //if the request is a multi-part mime
+    private AsyncContextState _async;
+    
+    /* ------------------------------------------------------------ */
+    public Request(HttpChannel<?> channel, HttpInput<?> input)
+    {
+        _channel = channel;
+        _input = input;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpFields getHttpFields()
+    {
+        return _fields;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpInput<?> getHttpInput()
+    {
+        return _input;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addEventListener(final EventListener listener)
+    {
+        if (listener instanceof ServletRequestAttributeListener)
+            _requestAttributeListeners.add((ServletRequestAttributeListener)listener);
+        if (listener instanceof AsyncListener)
+            throw new IllegalArgumentException(listener.getClass().toString());
+    }
+
+    public void extractParameters()
+    {
+        if (_paramsExtracted)
+            return;
+
+        _paramsExtracted = true;
+
+        // Extract query string parameters; these may be replaced by a forward()
+        // and may have already been extracted by mergeQueryParameters().
+        if (_queryParameters == null)
+            _queryParameters = extractQueryParameters();
+
+        // Extract content parameters; these cannot be replaced by a forward()
+        // once extracted and may have already been extracted by getParts() or
+        // by a processing happening after a form-based authentication.
+        if (_contentParameters == null)
+            _contentParameters = extractContentParameters();
+
+        _parameters = restoreParameters();
+    }
+
+    private MultiMap<String> extractQueryParameters()
+    {
+        MultiMap<String> result = new MultiMap<>();
+        if (_uri != null && _uri.hasQuery())
+        {
+            if (_queryEncoding == null)
+            {
+                _uri.decodeQueryTo(result);
+            }
+            else
+            {
+                try
+                {
+                    _uri.decodeQueryTo(result, _queryEncoding);
+                }
+                catch (UnsupportedEncodingException e)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.warn(e);
+                    else
+                        LOG.warn(e.toString());
+                }
+            }
+        }
+        return result;
+    }
+
+    private MultiMap<String> extractContentParameters()
+    {
+        MultiMap<String> result = new MultiMap<>();
+
+        String contentType = getContentType();
+        if (contentType != null && !contentType.isEmpty())
+        {
+            contentType = HttpFields.valueParameters(contentType, null);
+            int contentLength = getContentLength();
+            if (contentLength != 0)
+            {
+                if (MimeTypes.Type.FORM_ENCODED.is(contentType) && _inputState == __NONE &&
+                        (HttpMethod.POST.is(getMethod()) || HttpMethod.PUT.is(getMethod())))
+                {
+                    extractFormParameters(result);
+                }
+                else if (contentType.startsWith("multipart/form-data") &&
+                        getAttribute(__MULTIPART_CONFIG_ELEMENT) != null &&
+                        _multiPartInputStream == null)
+                {
+                    extractMultipartParameters(result);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    public void extractFormParameters(MultiMap<String> params)
+    {
+        try
+        {
+            int maxFormContentSize = -1;
+            int maxFormKeys = -1;
+
+            if (_context != null)
+            {
+                maxFormContentSize = _context.getContextHandler().getMaxFormContentSize();
+                maxFormKeys = _context.getContextHandler().getMaxFormKeys();
+            }
+
+            if (maxFormContentSize < 0)
+            {
+                Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
+                if (obj == null)
+                    maxFormContentSize = 200000;
+                else if (obj instanceof Number)
+                {
+                    Number size = (Number)obj;
+                    maxFormContentSize = size.intValue();
+                }
+                else if (obj instanceof String)
+                {
+                    maxFormContentSize = Integer.valueOf((String)obj);
+                }
+            }
+
+            if (maxFormKeys < 0)
+            {
+                Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys");
+                if (obj == null)
+                    maxFormKeys = 1000;
+                else if (obj instanceof Number)
+                {
+                    Number keys = (Number)obj;
+                    maxFormKeys = keys.intValue();
+                }
+                else if (obj instanceof String)
+                {
+                    maxFormKeys = Integer.valueOf((String)obj);
+                }
+            }
+
+            int contentLength = getContentLength();
+            if (contentLength > maxFormContentSize && maxFormContentSize > 0)
+            {
+                throw new IllegalStateException("Form too large: " + contentLength + " > " + maxFormContentSize);
+            }
+            InputStream in = getInputStream();
+            if (_input.isAsync())
+                throw new IllegalStateException("Cannot extract parameters with async IO");
+
+            UrlEncoded.decodeTo(in,params,getCharacterEncoding(),contentLength<0?maxFormContentSize:-1,maxFormKeys);
+        }
+        catch (IOException e)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.warn(e);
+            else
+                LOG.warn(e.toString());
+        }
+    }
+
+    private void extractMultipartParameters(MultiMap<String> result)
+    {
+        try
+        {
+            getParts(result);
+        }
+        catch (IOException | ServletException e)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.warn(e);
+            else
+                LOG.warn(e.toString());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public AsyncContext getAsyncContext()
+    {
+        HttpChannelState state = getHttpChannelState();
+        if (_async==null || state.isInitial() && !state.isAsync())
+            throw new IllegalStateException(state.getStatusString());
+        
+        return _async;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpChannelState getHttpChannelState()
+    {
+        return _channel.getState();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get Request Attribute.
+     * <p>Also supports jetty specific attributes to gain access to Jetty APIs:
+     * <dl>
+     * <dt>org.eclipse.jetty.server.Server</dt><dd>The Jetty Server instance</dd>
+     * <dt>org.eclipse.jetty.server.HttpChannel</dt><dd>The HttpChannel for this request</dd>
+     * <dt>org.eclipse.jetty.server.HttpConnection</dt><dd>The HttpConnection or null if another transport is used</dd>
+     * </dl>
+     * While these attributes may look like security problems, they are exposing nothing that is not already
+     * available via reflection from a Request instance.
+     * </p>
+     * @see javax.servlet.ServletRequest#getAttribute(java.lang.String)
+     */
+    @Override
+    public Object getAttribute(String name)
+    {
+        if (name.startsWith("org.eclipse.jetty"))
+        {
+            if ("org.eclipse.jetty.server.Server".equals(name))
+                return _channel.getServer();
+            if ("org.eclipse.jetty.server.HttpChannel".equals(name))
+                return _channel;
+            if ("org.eclipse.jetty.server.HttpConnection".equals(name) &&
+                _channel.getHttpTransport() instanceof HttpConnection)
+                return _channel.getHttpTransport();
+        }
+        return (_attributes == null)?null:_attributes.getAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getAttributeNames()
+     */
+    @Override
+    public Enumeration<String> getAttributeNames()
+    {
+        if (_attributes == null)
+            return Collections.enumeration(Collections.<String>emptyList());
+
+        return AttributesMap.getAttributeNamesCopy(_attributes);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public Attributes getAttributes()
+    {
+        if (_attributes == null)
+            _attributes = new AttributesMap();
+        return _attributes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the authentication.
+     *
+     * @return the authentication
+     */
+    public Authentication getAuthentication()
+    {
+        return _authentication;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getAuthType()
+     */
+    @Override
+    public String getAuthType()
+    {
+        if (_authentication instanceof Authentication.Deferred)
+            setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+        if (_authentication instanceof Authentication.User)
+            return ((Authentication.User)_authentication).getAuthMethod();
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getCharacterEncoding()
+     */
+    @Override
+    public String getCharacterEncoding()
+    {
+        return _characterEncoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the connection.
+     */
+    public HttpChannel<?> getHttpChannel()
+    {
+        return _channel;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getContentLength()
+     */
+    @Override
+    public int getContentLength()
+    {
+        return (int)_fields.getLongField(HttpHeader.CONTENT_LENGTH.toString());
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest.getContentLengthLong()
+     */
+    @Override
+    public long getContentLengthLong()
+    {
+        return _fields.getLongField(HttpHeader.CONTENT_LENGTH.toString());
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getContentRead()
+    {
+        return _input.getContentRead();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getContentType()
+     */
+    @Override
+    public String getContentType()
+    {
+        return _fields.getStringField(HttpHeader.CONTENT_TYPE);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The current {@link Context context} used for this request, or <code>null</code> if {@link #setContext} has not yet been called.
+     */
+    public Context getContext()
+    {
+        return _context;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getContextPath()
+     */
+    @Override
+    public String getContextPath()
+    {
+        return _contextPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getCookies()
+     */
+    @Override
+    public Cookie[] getCookies()
+    {
+        if (_cookiesExtracted)
+        {
+            if (_cookies == null || _cookies.getCookies().length == 0)
+                return null;
+            
+            return _cookies.getCookies();
+        }
+
+        _cookiesExtracted = true;
+
+        Enumeration<?> enm = _fields.getValues(HttpHeader.COOKIE.toString());
+
+        // Handle no cookies
+        if (enm != null)
+        {
+            if (_cookies == null)
+                _cookies = new CookieCutter();
+
+            while (enm.hasMoreElements())
+            {
+                String c = (String)enm.nextElement();
+                _cookies.addCookieField(c);
+            }
+        }
+
+        //Javadoc for Request.getCookies() stipulates null for no cookies
+        if (_cookies == null || _cookies.getCookies().length == 0)
+            return null;
+        
+        return _cookies.getCookies();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getDateHeader(java.lang.String)
+     */
+    @Override
+    public long getDateHeader(String name)
+    {
+        return _fields.getDateField(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public DispatcherType getDispatcherType()
+    {
+        return _dispatcherType;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getHeader(java.lang.String)
+     */
+    @Override
+    public String getHeader(String name)
+    {
+        return _fields.getStringField(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getHeaderNames()
+     */
+    @Override
+    public Enumeration<String> getHeaderNames()
+    {
+        return _fields.getFieldNames();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getHeaders(java.lang.String)
+     */
+    @Override
+    public Enumeration<String> getHeaders(String name)
+    {
+        Enumeration<String> e = _fields.getValues(name);
+        if (e == null)
+            return Collections.enumeration(Collections.<String>emptyList());
+        return e;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the inputState.
+     */
+    public int getInputState()
+    {
+        return _inputState;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getInputStream()
+     */
+    @Override
+    public ServletInputStream getInputStream() throws IOException
+    {
+        if (_inputState != __NONE && _inputState != _STREAM)
+            throw new IllegalStateException("READER");
+        _inputState = _STREAM;
+
+        if (_channel.isExpecting100Continue())
+            _channel.continue100(_input.available());
+
+        return _input;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getIntHeader(java.lang.String)
+     */
+    @Override
+    public int getIntHeader(String name)
+    {
+        return (int)_fields.getLongField(name);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocale()
+     */
+    @Override
+    public Locale getLocale()
+    {
+        Enumeration<String> enm = _fields.getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators);
+
+        // handle no locale
+        if (enm == null || !enm.hasMoreElements())
+            return Locale.getDefault();
+
+        // sort the list in quality order
+        List<?> acceptLanguage = HttpFields.qualityList(enm);
+        if (acceptLanguage.size() == 0)
+            return Locale.getDefault();
+
+        int size = acceptLanguage.size();
+
+        if (size > 0)
+        {
+            String language = (String)acceptLanguage.get(0);
+            language = HttpFields.valueParameters(language,null);
+            String country = "";
+            int dash = language.indexOf('-');
+            if (dash > -1)
+            {
+                country = language.substring(dash + 1).trim();
+                language = language.substring(0,dash).trim();
+            }
+            return new Locale(language,country);
+        }
+
+        return Locale.getDefault();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocales()
+     */
+    @Override
+    public Enumeration<Locale> getLocales()
+    {
+
+        Enumeration<String> enm = _fields.getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators);
+
+        // handle no locale
+        if (enm == null || !enm.hasMoreElements())
+            return Collections.enumeration(__defaultLocale);
+
+        // sort the list in quality order
+        List<String> acceptLanguage = HttpFields.qualityList(enm);
+
+        if (acceptLanguage.size() == 0)
+            return Collections.enumeration(__defaultLocale);
+
+        List<Locale> langs = new ArrayList<>();
+
+        // convert to locals
+        for (String language : acceptLanguage)
+        {
+            language = HttpFields.valueParameters(language, null);
+            String country = "";
+            int dash = language.indexOf('-');
+            if (dash > -1)
+            {
+                country = language.substring(dash + 1).trim();
+                language = language.substring(0, dash).trim();
+            }
+            langs.add(new Locale(language, country));
+        }
+
+        if (langs.size() == 0)
+            return Collections.enumeration(__defaultLocale);
+
+        return Collections.enumeration(langs);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocalAddr()
+     */
+    @Override
+    public String getLocalAddr()
+    {
+        InetSocketAddress local=_channel.getLocalAddress();
+        if (local==null)
+            return "";
+        InetAddress address = local.getAddress();
+        if (address==null)
+            return local.getHostString();
+        return address.getHostAddress();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocalName()
+     */
+    @Override
+    public String getLocalName()
+    {
+        InetSocketAddress local=_channel.getLocalAddress();
+        return local.getHostString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocalPort()
+     */
+    @Override
+    public int getLocalPort()
+    {
+        InetSocketAddress local=_channel.getLocalAddress();
+        return local.getPort();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getMethod()
+     */
+    @Override
+    public String getMethod()
+    {
+        return _httpMethodString;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
+     */
+    @Override
+    public String getParameter(String name)
+    {
+        if (!_paramsExtracted)
+            extractParameters();
+        if (_parameters == null)
+            _parameters = restoreParameters();
+        return _parameters.getValue(name,0);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getParameterMap()
+     */
+    @Override
+    public Map<String, String[]> getParameterMap()
+    {
+        if (!_paramsExtracted)
+            extractParameters();
+        if (_parameters == null)
+            _parameters = restoreParameters();
+        return Collections.unmodifiableMap(_parameters.toStringArrayMap());
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getParameterNames()
+     */
+    @Override
+    public Enumeration<String> getParameterNames()
+    {
+        if (!_paramsExtracted)
+            extractParameters();
+        if (_parameters == null)
+            _parameters = restoreParameters();
+        return Collections.enumeration(_parameters.keySet());
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
+     */
+    @Override
+    public String[] getParameterValues(String name)
+    {
+        if (!_paramsExtracted)
+            extractParameters();
+        if (_parameters == null)
+            _parameters = restoreParameters();
+        List<String> vals = _parameters.getValues(name);
+        if (vals == null)
+            return null;
+        return vals.toArray(new String[vals.size()]);
+    }
+
+    private MultiMap<String> restoreParameters()
+    {
+        MultiMap<String> result = new MultiMap<>();
+        if (_queryParameters == null)
+            _queryParameters = extractQueryParameters();
+        result.addAllValues(_queryParameters);
+        result.addAllValues(_contentParameters);
+        return result;
+    }
+
+    public MultiMap<String> getQueryParameters()
+    {
+        return _queryParameters;
+    }
+
+    public void setQueryParameters(MultiMap<String> queryParameters)
+    {
+        _queryParameters = queryParameters;
+    }
+
+    public void setContentParameters(MultiMap<String> contentParameters)
+    {
+        _contentParameters = contentParameters;
+    }
+
+    public void resetParameters()
+    {
+        _parameters = null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getPathInfo()
+     */
+    @Override
+    public String getPathInfo()
+    {
+        return _pathInfo;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getPathTranslated()
+     */
+    @Override
+    public String getPathTranslated()
+    {
+        if (_pathInfo == null || _context == null)
+            return null;
+        return _context.getRealPath(_pathInfo);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getProtocol()
+     */
+    @Override
+    public String getProtocol()
+    {
+        return _httpVersion.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getProtocol()
+     */
+    public HttpVersion getHttpVersion()
+    {
+        return _httpVersion;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getQueryEncoding()
+    {
+        return _queryEncoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getQueryString()
+     */
+    @Override
+    public String getQueryString()
+    {
+        if (_queryString == null && _uri != null)
+        {
+            if (_queryEncoding == null)
+                _queryString = _uri.getQuery();
+            else
+                _queryString = _uri.getQuery(_queryEncoding);
+        }
+        return _queryString;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getReader()
+     */
+    @Override
+    public BufferedReader getReader() throws IOException
+    {
+        if (_inputState != __NONE && _inputState != __READER)
+            throw new IllegalStateException("STREAMED");
+
+        if (_inputState == __READER)
+            return _reader;
+
+        String encoding = getCharacterEncoding();
+        if (encoding == null)
+            encoding = StringUtil.__ISO_8859_1;
+
+        if (_reader == null || !encoding.equalsIgnoreCase(_readerEncoding))
+        {
+            final ServletInputStream in = getInputStream();
+            _readerEncoding = encoding;
+            _reader = new BufferedReader(new InputStreamReader(in,encoding))
+            {
+                @Override
+                public void close() throws IOException
+                {
+                    in.close();
+                }
+            };
+        }
+        _inputState = __READER;
+        return _reader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getRealPath(java.lang.String)
+     */
+    @Override
+    public String getRealPath(String path)
+    {
+        if (_context == null)
+            return null;
+        return _context.getRealPath(path);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Access the underlying Remote {@link InetSocketAddress} for this request.
+     * 
+     * @return the remote {@link InetSocketAddress} for this request, or null if the request has no remote (see {@link ServletRequest#getRemoteAddr()} for
+     *         conditions that result in no remote address)
+     */
+    public InetSocketAddress getRemoteInetSocketAddress()
+    {
+        InetSocketAddress remote = _remote;
+        if (remote == null)
+            remote = _channel.getRemoteAddress();
+
+        return remote;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getRemoteAddr()
+     */
+    @Override
+    public String getRemoteAddr()
+    {
+        InetSocketAddress remote=_remote;
+        if (remote==null)
+            remote=_channel.getRemoteAddress();
+        
+        if (remote==null)
+            return "";
+        
+        InetAddress address = remote.getAddress();
+        if (address==null)
+            return remote.getHostString();
+        
+        return address.getHostAddress();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getRemoteHost()
+     */
+    @Override
+    public String getRemoteHost()
+    {
+        InetSocketAddress remote=_remote;
+        if (remote==null)
+            remote=_channel.getRemoteAddress();
+        return remote==null?"":remote.getHostString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getRemotePort()
+     */
+    @Override
+    public int getRemotePort()
+    {
+        InetSocketAddress remote=_remote;
+        if (remote==null)
+            remote=_channel.getRemoteAddress();
+        return remote==null?0:remote.getPort();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getRemoteUser()
+     */
+    @Override
+    public String getRemoteUser()
+    {
+        Principal p = getUserPrincipal();
+        if (p == null)
+            return null;
+        return p.getName();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getRequestDispatcher(java.lang.String)
+     */
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path)
+    {
+        if (path == null || _context == null)
+            return null;
+
+        // handle relative path
+        if (!path.startsWith("/"))
+        {
+            String relTo = URIUtil.addPaths(_servletPath,_pathInfo);
+            int slash = relTo.lastIndexOf("/");
+            if (slash > 1)
+                relTo = relTo.substring(0,slash + 1);
+            else
+                relTo = "/";
+            path = URIUtil.addPaths(relTo,path);
+        }
+
+        return _context.getRequestDispatcher(path);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getRequestedSessionId()
+     */
+    @Override
+    public String getRequestedSessionId()
+    {
+        return _requestedSessionId;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getRequestURI()
+     */
+    @Override
+    public String getRequestURI()
+    {
+        if (_requestURI == null && _uri != null)
+            _requestURI = _uri.getPathAndParam();
+        return _requestURI;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getRequestURL()
+     */
+    @Override
+    public StringBuffer getRequestURL()
+    {
+        final StringBuffer url = new StringBuffer(128);
+        URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort());
+        url.append(getRequestURI());
+        return url;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Response getResponse()
+    {
+        return _channel.getResponse();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Reconstructs the URL the client used to make the request. The returned URL contains a protocol, server name, port number, and, but it does not include a
+     * path.
+     * <p>
+     * Because this method returns a <code>StringBuffer</code>, not a string, you can modify the URL easily, for example, to append path and query parameters.
+     *
+     * This method is useful for creating redirect messages and for reporting errors.
+     *
+     * @return "scheme://host:port"
+     */
+    public StringBuilder getRootURL()
+    {
+        StringBuilder url = new StringBuilder(128);
+        URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort());
+        return url;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getScheme()
+     */
+    @Override
+    public String getScheme()
+    {
+        return _scheme;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getServerName()
+     */
+    @Override
+    public String getServerName()
+    {
+        // Return already determined host
+        if (_serverName != null)
+            return _serverName;
+
+        if (_uri == null)
+            throw new IllegalStateException("No uri");
+
+        // Return host from absolute URI
+        _serverName = _uri.getHost();
+        if (_serverName != null)
+        {
+            _port = _uri.getPort();
+            return _serverName;
+        }
+
+        // Return host from header field
+        String hostPort = _fields.getStringField(HttpHeader.HOST);
+        
+        _port=0;
+        if (hostPort != null)
+        {
+            int len=hostPort.length();
+            loop: for (int i = len; i-- > 0;)
+            {
+                char c2 = (char)(0xff & hostPort.charAt(i));
+                switch (c2)
+                {
+                    case ']':
+                        break loop;
+
+                    case ':':
+                        try
+                        {
+                            len=i;
+                            _port = StringUtil.toInt(hostPort.substring(i+1));
+                        }
+                        catch (NumberFormatException e)
+                        {
+                            LOG.warn(e);
+                            _serverName=hostPort;
+                            _port=0;
+                            return _serverName;
+                        }
+                        break loop;
+                }
+            }
+            if (hostPort.charAt(0)=='[')
+            {
+                if (hostPort.charAt(len-1)!=']') 
+                {
+                    LOG.warn("Bad IPv6 "+hostPort);
+                    _serverName=hostPort;
+                    _port=0;
+                    return _serverName;
+                }
+                _serverName = hostPort.substring(1,len-1);
+            }
+            else if (len==hostPort.length())
+                _serverName=hostPort;
+            else
+                _serverName = hostPort.substring(0,len);
+
+            return _serverName;
+        }
+
+        // Return host from connection
+        if (_channel != null)
+        {
+            _serverName = getLocalName();
+            _port = getLocalPort();
+            if (_serverName != null && !StringUtil.ALL_INTERFACES.equals(_serverName))
+                return _serverName;
+        }
+
+        // Return the local host
+        try
+        {
+            _serverName = InetAddress.getLocalHost().getHostAddress();
+        }
+        catch (java.net.UnknownHostException e)
+        {
+            LOG.ignore(e);
+        }
+        return _serverName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getServerPort()
+     */
+    @Override
+    public int getServerPort()
+    {
+        if (_port <= 0)
+        {
+            if (_serverName == null)
+                getServerName();
+
+            if (_port <= 0)
+            {
+                if (_serverName != null && _uri != null)
+                    _port = _uri.getPort();
+                else
+                {
+                    InetSocketAddress local = _channel.getLocalAddress();
+                    _port = local == null?0:local.getPort();
+                }
+            }
+        }
+
+        if (_port <= 0)
+        {
+            if (getScheme().equalsIgnoreCase(URIUtil.HTTPS))
+                return 443;
+            return 80;
+        }
+        return _port;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public ServletContext getServletContext()
+    {
+        return _context;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public String getServletName()
+    {
+        if (_scope != null)
+            return _scope.getName();
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getServletPath()
+     */
+    @Override
+    public String getServletPath()
+    {
+        if (_servletPath == null)
+            _servletPath = "";
+        return _servletPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletResponse getServletResponse()
+    {
+        return _channel.getResponse();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Add @override when 3.1 api is available
+     */
+    public String changeSessionId()
+    {
+        HttpSession session = getSession(false);
+        if (session == null)
+            throw new IllegalStateException("No session");
+
+        if (session instanceof AbstractSession)
+        {
+            AbstractSession abstractSession =  ((AbstractSession)session);
+            abstractSession.renewId(this);
+            if (getRemoteUser() != null)
+                abstractSession.setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+            if (abstractSession.isIdChanged())
+                _channel.getResponse().addCookie(_sessionManager.getSessionCookie(abstractSession, getContextPath(), isSecure()));
+        }
+
+        return session.getId();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getSession()
+     */
+    @Override
+    public HttpSession getSession()
+    {
+        return getSession(true);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getSession(boolean)
+     */
+    @Override
+    public HttpSession getSession(boolean create)
+    {
+        if (_session != null)
+        {
+            if (_sessionManager != null && !_sessionManager.isValid(_session))
+                _session = null;
+            else
+                return _session;
+        }
+
+        if (!create)
+            return null;
+        
+        if (getResponse().isCommitted())
+            throw new IllegalStateException("Response is committed");
+
+        if (_sessionManager == null)
+            throw new IllegalStateException("No SessionManager");
+
+        _session = _sessionManager.newHttpSession(this);
+        HttpCookie cookie = _sessionManager.getSessionCookie(_session,getContextPath(),isSecure());
+        if (cookie != null)
+            _channel.getResponse().addCookie(cookie);
+
+        return _session;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionManager.
+     */
+    public SessionManager getSessionManager()
+    {
+        return _sessionManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get Request TimeStamp
+     *
+     * @return The time that the request was received.
+     */
+    public long getTimeStamp()
+    {
+        return _timeStamp;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the uri.
+     */
+    public HttpURI getUri()
+    {
+        return _uri;
+    }
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity getUserIdentity()
+    {
+        if (_authentication instanceof Authentication.Deferred)
+            setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+        if (_authentication instanceof Authentication.User)
+            return ((Authentication.User)_authentication).getUserIdentity();
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The resolved user Identity, which may be null if the {@link Authentication} is not {@link Authentication.User} (eg.
+     *         {@link Authentication.Deferred}).
+     */
+    public UserIdentity getResolvedUserIdentity()
+    {
+        if (_authentication instanceof Authentication.User)
+            return ((Authentication.User)_authentication).getUserIdentity();
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity.Scope getUserIdentityScope()
+    {
+        return _scope;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getUserPrincipal()
+     */
+    @Override
+    public Principal getUserPrincipal()
+    {
+        if (_authentication instanceof Authentication.Deferred)
+            setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+        if (_authentication instanceof Authentication.User)
+        {
+            UserIdentity user = ((Authentication.User)_authentication).getUserIdentity();
+            return user.getUserPrincipal();
+        }
+        
+        return null;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public boolean isHandled()
+    {
+        return _handled;
+    }
+
+    @Override
+    public boolean isAsyncStarted()
+    {
+       return getHttpChannelState().isAsyncStarted();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isAsyncSupported()
+    {
+        return _asyncSupported;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie()
+     */
+    @Override
+    public boolean isRequestedSessionIdFromCookie()
+    {
+        return _requestedSessionId != null && _requestedSessionIdFromCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromUrl()
+     */
+    @Override
+    public boolean isRequestedSessionIdFromUrl()
+    {
+        return _requestedSessionId != null && !_requestedSessionIdFromCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL()
+     */
+    @Override
+    public boolean isRequestedSessionIdFromURL()
+    {
+        return _requestedSessionId != null && !_requestedSessionIdFromCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid()
+     */
+    @Override
+    public boolean isRequestedSessionIdValid()
+    {
+        if (_requestedSessionId == null)
+            return false;
+
+        HttpSession session = getSession(false);
+        return (session != null && _sessionManager.getSessionIdManager().getClusterId(_requestedSessionId).equals(_sessionManager.getClusterId(session)));
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#isSecure()
+     */
+    @Override
+    public boolean isSecure()
+    {
+        return _secure;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setSecure(boolean secure)
+    {
+        _secure=secure;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#isUserInRole(java.lang.String)
+     */
+    @Override
+    public boolean isUserInRole(String role)
+    {
+        if (_authentication instanceof Authentication.Deferred)
+            setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+        if (_authentication instanceof Authentication.User)
+            return ((Authentication.User)_authentication).isUserInRole(_scope,role);
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpSession recoverNewSession(Object key)
+    {
+        if (_savedNewSessions == null)
+            return null;
+        return _savedNewSessions.get(key);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void recycle()
+    {
+        if (_context != null)
+            throw new IllegalStateException("Request in context!");
+        
+        if (_inputState == __READER)
+        {
+            try
+            {
+                int r = _reader.read();
+                while (r != -1)
+                    r = _reader.read();
+            }
+            catch (Exception e)
+            {
+                LOG.ignore(e);
+                _reader = null;
+            }
+        }
+
+        _dispatcherType=null;
+        setAuthentication(Authentication.NOT_CHECKED);
+        getHttpChannelState().recycle();
+        if (_async!=null)
+            _async.reset();
+        _async=null;
+        _asyncSupported = true;
+        _handled = false;
+        if (_attributes != null)
+            _attributes.clearAttributes();
+        _characterEncoding = null;
+        _contextPath = null;
+        if (_cookies != null)
+            _cookies.reset();
+        _cookiesExtracted = false;
+        _context = null;
+        _newContext=false;
+        _serverName = null;
+        _httpMethod=null;
+        _httpMethodString = null;
+        _pathInfo = null;
+        _port = 0;
+        _httpVersion = HttpVersion.HTTP_1_1;
+        _queryEncoding = null;
+        _queryString = null;
+        _requestedSessionId = null;
+        _requestedSessionIdFromCookie = false;
+        _secure=false;
+        _session = null;
+        _sessionManager = null;
+        _requestURI = null;
+        _scope = null;
+        _scheme = URIUtil.HTTP;
+        _servletPath = null;
+        _timeStamp = 0;
+        _uri = null;
+        _queryParameters = null;
+        _contentParameters = null;
+        _parameters = null;
+        _paramsExtracted = false;
+        _inputState = __NONE;
+
+        if (_savedNewSessions != null)
+            _savedNewSessions.clear();
+        _savedNewSessions=null;
+        _multiPartInputStream = null;
+        _remote=null;
+        _fields.clear();
+        _input.recycle();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#removeAttribute(java.lang.String)
+     */
+    @Override
+    public void removeAttribute(String name)
+    {
+        Object old_value = _attributes == null?null:_attributes.getAttribute(name);
+
+        if (_attributes != null)
+            _attributes.removeAttribute(name);
+
+        if (old_value != null && !_requestAttributeListeners.isEmpty())
+        {
+            final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value);
+            for (ServletRequestAttributeListener listener : _requestAttributeListeners)
+                listener.attributeRemoved(event);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void removeEventListener(final EventListener listener)
+    {
+        _requestAttributeListeners.remove(listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void saveNewSession(Object key, HttpSession session)
+    {
+        if (_savedNewSessions == null)
+            _savedNewSessions = new HashMap<>();
+        _savedNewSessions.put(key,session);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setAsyncSupported(boolean supported)
+    {
+        _asyncSupported = supported;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Set a request attribute. if the attribute name is "org.eclipse.jetty.server.server.Request.queryEncoding" then the value is also passed in a call to
+     * {@link #setQueryEncoding}.
+     *
+     * @see javax.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object)
+     */
+    @Override
+    public void setAttribute(String name, Object value)
+    {
+        Object old_value = _attributes == null?null:_attributes.getAttribute(name);
+
+        if ("org.eclipse.jetty.server.Request.queryEncoding".equals(name))
+            setQueryEncoding(value == null?null:value.toString());
+        else if ("org.eclipse.jetty.server.sendContent".equals(name))
+            LOG.warn("Deprecated: org.eclipse.jetty.server.sendContent");
+        
+        if (_attributes == null)
+            _attributes = new AttributesMap();
+        _attributes.setAttribute(name,value);
+
+        if (!_requestAttributeListeners.isEmpty())
+        {
+            final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value == null?value:old_value);
+            for (ServletRequestAttributeListener l : _requestAttributeListeners)
+            {
+                if (old_value == null)
+                    l.attributeAdded(event);
+                else if (value == null)
+                    l.attributeRemoved(event);
+                else
+                    l.attributeReplaced(event);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public void setAttributes(Attributes attributes)
+    {
+        _attributes = attributes;
+    }
+
+    /* ------------------------------------------------------------ */
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the authentication.
+     *
+     * @param authentication
+     *            the authentication to set
+     */
+    public void setAuthentication(Authentication authentication)
+    {
+        _authentication = authentication;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
+     */
+    @Override
+    public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException
+    {
+        if (_inputState != __NONE)
+            return;
+
+        _characterEncoding = encoding;
+
+        // check encoding is supported
+        if (!StringUtil.isUTF8(encoding))
+        {
+            try
+            {
+                Charset.forName(encoding);
+            }
+            catch (UnsupportedCharsetException e)
+            {
+                throw new UnsupportedEncodingException(e.getMessage());
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
+     */
+    public void setCharacterEncodingUnchecked(String encoding)
+    {
+        _characterEncoding = encoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getContentType()
+     */
+    public void setContentType(String contentType)
+    {
+        _fields.put(HttpHeader.CONTENT_TYPE,contentType);
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set request context
+     *
+     * @param context
+     *            context object
+     */
+    public void setContext(Context context)
+    {
+        _newContext = _context != context;
+        _context = context;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if this is the first call of {@link #takeNewContext()} since the last
+     *         {@link #setContext(org.eclipse.jetty.server.handler.ContextHandler.Context)} call.
+     */
+    public boolean takeNewContext()
+    {
+        boolean nc = _newContext;
+        _newContext = false;
+        return nc;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the "context path" for this request
+     *
+     * @see HttpServletRequest#getContextPath()
+     */
+    public void setContextPath(String contextPath)
+    {
+        _contextPath = contextPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param cookies
+     *            The cookies to set.
+     */
+    public void setCookies(Cookie[] cookies)
+    {
+        if (_cookies == null)
+            _cookies = new CookieCutter();
+        _cookies.setCookies(cookies);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setDispatcherType(DispatcherType type)
+    {
+        _dispatcherType = type;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setHandled(boolean h)
+    {
+        _handled = h;
+        Response r=getResponse();
+        if (_handled && r.getStatus()==0)
+            r.setStatus(200);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param method
+     *            The method to set.
+     */
+    public void setMethod(HttpMethod httpMethod, String method)
+    {
+        _httpMethod=httpMethod;
+        _httpMethodString = method;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isHead()
+    {
+        return HttpMethod.HEAD==_httpMethod;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathInfo
+     *            The pathInfo to set.
+     */
+    public void setPathInfo(String pathInfo)
+    {
+        _pathInfo = pathInfo;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param version
+     *            The protocol to set.
+     */
+    public void setHttpVersion(HttpVersion version)
+    {
+        _httpVersion = version;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the character encoding used for the query string. This call will effect the return of getQueryString and getParamaters. It must be called before any
+     * getParameter methods.
+     *
+     * The request attribute "org.eclipse.jetty.server.server.Request.queryEncoding" may be set as an alternate method of calling setQueryEncoding.
+     *
+     * @param queryEncoding
+     */
+    public void setQueryEncoding(String queryEncoding)
+    {
+        _queryEncoding = queryEncoding;
+        _queryString = null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param queryString
+     *            The queryString to set.
+     */
+    public void setQueryString(String queryString)
+    {
+        _queryString = queryString;
+        _queryEncoding = null; //assume utf-8
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param addr
+     *            The address to set.
+     */
+    public void setRemoteAddr(InetSocketAddress addr)
+    {
+        _remote = addr;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param requestedSessionId
+     *            The requestedSessionId to set.
+     */
+    public void setRequestedSessionId(String requestedSessionId)
+    {
+        _requestedSessionId = requestedSessionId;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param requestedSessionIdCookie
+     *            The requestedSessionIdCookie to set.
+     */
+    public void setRequestedSessionIdFromCookie(boolean requestedSessionIdCookie)
+    {
+        _requestedSessionIdFromCookie = requestedSessionIdCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param requestURI
+     *            The requestURI to set.
+     */
+    public void setRequestURI(String requestURI)
+    {
+        _requestURI = requestURI;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param scheme
+     *            The scheme to set.
+     */
+    public void setScheme(String scheme)
+    {
+        _scheme = scheme;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param host
+     *            The host to set.
+     */
+    public void setServerName(String host)
+    {
+        _serverName = host;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param port
+     *            The port to set.
+     */
+    public void setServerPort(int port)
+    {
+        _port = port;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletPath
+     *            The servletPath to set.
+     */
+    public void setServletPath(String servletPath)
+    {
+        _servletPath = servletPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param session
+     *            The session to set.
+     */
+    public void setSession(HttpSession session)
+    {
+        _session = session;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionManager
+     *            The sessionManager to set.
+     */
+    public void setSessionManager(SessionManager sessionManager)
+    {
+        _sessionManager = sessionManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setTimeStamp(long ts)
+    {
+        _timeStamp = ts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param uri
+     *            The uri to set.
+     */
+    public void setUri(HttpURI uri)
+    {
+        _uri = uri;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setUserIdentityScope(UserIdentity.Scope scope)
+    {
+        _scope = scope;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public AsyncContext startAsync() throws IllegalStateException
+    {
+        if (!_asyncSupported)
+            throw new IllegalStateException("!asyncSupported");
+        HttpChannelState state = getHttpChannelState();
+        if (_async==null)
+            _async=new AsyncContextState(state);
+        AsyncContextEvent event = new AsyncContextEvent(_context,_async,state,this,this,getResponse());
+        state.startAsync(event);
+        return _async;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException
+    {
+        if (!_asyncSupported)
+            throw new IllegalStateException("!asyncSupported");
+        HttpChannelState state = getHttpChannelState();
+        if (_async==null)
+            _async=new AsyncContextState(state);
+        AsyncContextEvent event = new AsyncContextEvent(_context,_async,state,this,servletRequest,servletResponse);
+        event.setDispatchContext(getServletContext());
+        event.setDispatchPath(URIUtil.addPaths(getServletPath(),getPathInfo()));
+        state.startAsync(event);
+        return _async;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return (_handled?"[":"(") + getMethod() + " " + _uri + (_handled?"]@":")@") + hashCode() + " " + super.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean authenticate(HttpServletResponse response) throws IOException, ServletException
+    {
+        if (_authentication instanceof Authentication.Deferred)
+        {
+            setAuthentication(((Authentication.Deferred)_authentication).authenticate(this,response));
+            return !(_authentication instanceof Authentication.ResponseSent);
+        }
+        response.sendError(HttpStatus.UNAUTHORIZED_401);
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Part getPart(String name) throws IOException, ServletException
+    {
+        getParts();
+
+        return _multiPartInputStream.getPart(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Collection<Part> getParts() throws IOException, ServletException
+    {
+        if (getContentType() == null || !getContentType().startsWith("multipart/form-data"))
+            throw new ServletException("Content-Type != multipart/form-data");
+        return getParts(null);
+    }
+
+    private Collection<Part> getParts(MultiMap<String> params) throws IOException, ServletException
+    {
+        if (_multiPartInputStream == null)
+            _multiPartInputStream = (MultiPartInputStreamParser)getAttribute(__MULTIPART_INPUT_STREAM);
+
+        if (_multiPartInputStream == null)
+        {
+            MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT);
+            
+            if (config == null)
+                throw new IllegalStateException("No multipart config for servlet");
+            
+            _multiPartInputStream = new MultiPartInputStreamParser(getInputStream(),
+                                                             getContentType(), config, 
+                                                             (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null));
+            
+            setAttribute(__MULTIPART_INPUT_STREAM, _multiPartInputStream);
+            setAttribute(__MULTIPART_CONTEXT, _context);
+            Collection<Part> parts = _multiPartInputStream.getParts(); //causes parsing
+            ByteArrayOutputStream os = null;
+            for (Part p:parts)
+            {
+                MultiPartInputStreamParser.MultiPart mp = (MultiPartInputStreamParser.MultiPart)p;
+                if (mp.getContentDispositionFilename() == null)
+                {
+                    // Servlet Spec 3.0 pg 23, parts without filename must be put into params.
+                    String charset = null;
+                    if (mp.getContentType() != null)
+                        charset = MimeTypes.getCharsetFromContentType(mp.getContentType());
+
+                    try (InputStream is = mp.getInputStream())
+                    {
+                        if (os == null)
+                            os = new ByteArrayOutputStream();
+                        IO.copy(is, os);
+                        String content=new String(os.toByteArray(),charset==null?StandardCharsets.UTF_8:Charset.forName(charset));
+                        if (_contentParameters == null)
+                            _contentParameters = params == null ? new MultiMap<String>() : params;
+                        _contentParameters.add(mp.getName(), content);
+                    }
+                    os.reset();
+                }
+            }
+        }
+
+        return _multiPartInputStream.getParts();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void login(String username, String password) throws ServletException
+    {
+        if (_authentication instanceof Authentication.Deferred)
+        {
+            _authentication=((Authentication.Deferred)_authentication).login(username,password,this);
+            if (_authentication == null)
+                throw new Authentication.Failed("Authentication failed for username '"+username+"'");
+        }
+        else
+        {
+            throw new Authentication.Failed("Authenticated failed for username '"+username+"'. Already authenticated as "+_authentication);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void logout() throws ServletException
+    {
+        if (_authentication instanceof Authentication.User)
+            ((Authentication.User)_authentication).logout();
+        _authentication=Authentication.UNAUTHENTICATED;
+    }
+
+    public void mergeQueryParameters(String newQuery, boolean updateQueryString)
+    {
+        MultiMap<String> newQueryParams = new MultiMap<>();
+        // Have to assume ENCODING because we can't know otherwise.
+        UrlEncoded.decodeTo(newQuery, newQueryParams, UrlEncoded.ENCODING, -1);
+
+        MultiMap<String> oldQueryParams = _queryParameters;
+        if (oldQueryParams == null && _queryString != null)
+        {
+            oldQueryParams = new MultiMap<>();
+            UrlEncoded.decodeTo(_queryString, oldQueryParams, getQueryEncoding(), -1);
+        }
+
+        MultiMap<String> mergedQueryParams = newQueryParams;
+        if (oldQueryParams != null)
+        {
+            // Parameters values are accumulated.
+            mergedQueryParams = new MultiMap<>(newQueryParams);
+            mergedQueryParams.addAllValues(oldQueryParams);
+        }
+
+        setQueryParameters(mergedQueryParams);
+        resetParameters();
+
+        if (updateQueryString)
+        {
+            // Build the new merged query string, parameters in the
+            // new query string hide parameters in the old query string.
+            StringBuilder mergedQuery = new StringBuilder(newQuery);
+            for (Map.Entry<String, List<String>> entry : mergedQueryParams.entrySet())
+            {
+                if (newQueryParams.containsKey(entry.getKey()))
+                    continue;
+                for (String value : entry.getValue())
+                    mergedQuery.append("&").append(entry.getKey()).append("=").append(value);
+            }
+
+            setQueryString(mergedQuery.toString());
+        }
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletRequest#upgrade(java.lang.Class)
+     */
+    @Override
+    public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException
+    {
+        if (getContext() == null)
+            throw new ServletException ("Unable to instantiate "+handlerClass);
+
+        try
+        {
+            //Instantiate an instance and inject it
+            T h = getContext().createInstance(handlerClass);
+            
+            //TODO handle the rest of the upgrade process
+            
+            return h;
+        }
+        catch (Exception e)
+        {
+            if (e instanceof ServletException)
+                throw (ServletException)e;
+            throw new ServletException(e);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/RequestLog.java b/lib/jetty/org/eclipse/jetty/server/RequestLog.java
new file mode 100644 (file)
index 0000000..0cb1a53
--- /dev/null
@@ -0,0 +1,30 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server; 
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/** 
+ * A <code>RequestLog</code> can be attached to a {@link org.eclipse.jetty.server.handler.RequestLogHandler} to enable 
+ * logging of requests/responses.
+ */
+public interface RequestLog extends LifeCycle
+{
+    public void log(Request request, Response response);
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ResourceCache.java b/lib/jetty/org/eclipse/jetty/server/ResourceCache.java
new file mode 100644 (file)
index 0000000..e39c4a6
--- /dev/null
@@ -0,0 +1,511 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.util.Comparator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpContent.ResourceAsHttpContent;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * 
+ */
+public class ResourceCache
+{
+    private static final Logger LOG = Log.getLogger(ResourceCache.class);
+
+    private final ConcurrentMap<String,Content> _cache;
+    private final AtomicInteger _cachedSize;
+    private final AtomicInteger _cachedFiles;
+    private final ResourceFactory _factory;
+    private final ResourceCache _parent;
+    private final MimeTypes _mimeTypes;
+    private final boolean _etagSupported;
+    private final boolean  _useFileMappedBuffer;
+    
+    private int _maxCachedFileSize =4*1024*1024;
+    private int _maxCachedFiles=2048;
+    private int _maxCacheSize =32*1024*1024;
+    
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     * @param mimeTypes Mimetype to use for meta data
+     */
+    public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags)
+    {
+        _factory = factory;
+        _cache=new ConcurrentHashMap<String,Content>();
+        _cachedSize=new AtomicInteger();
+        _cachedFiles=new AtomicInteger();
+        _mimeTypes=mimeTypes;
+        _parent=parent;
+        _useFileMappedBuffer=useFileMappedBuffer;
+        _etagSupported=etags;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getCachedSize()
+    {
+        return _cachedSize.get();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int getCachedFiles()
+    {
+        return _cachedFiles.get();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int getMaxCachedFileSize()
+    {
+        return _maxCachedFileSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMaxCachedFileSize(int maxCachedFileSize)
+    {
+        _maxCachedFileSize = maxCachedFileSize;
+        shrinkCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getMaxCacheSize()
+    {
+        return _maxCacheSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMaxCacheSize(int maxCacheSize)
+    {
+        _maxCacheSize = maxCacheSize;
+        shrinkCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the maxCachedFiles.
+     */
+    public int getMaxCachedFiles()
+    {
+        return _maxCachedFiles;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maxCachedFiles The maxCachedFiles to set.
+     */
+    public void setMaxCachedFiles(int maxCachedFiles)
+    {
+        _maxCachedFiles = maxCachedFiles;
+        shrinkCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isUseFileMappedBuffer()
+    {
+        return _useFileMappedBuffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void flushCache()
+    {
+        if (_cache!=null)
+        {
+            while (_cache.size()>0)
+            {
+                for (String path : _cache.keySet())
+                {
+                    Content content = _cache.remove(path);
+                    if (content!=null)
+                        content.invalidate();
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get a Entry from the cache.
+     * Get either a valid entry object or create a new one if possible.
+     *
+     * @param pathInContext The key into the cache
+     * @return The entry matching <code>pathInContext</code>, or a new entry 
+     * if no matching entry was found. If the content exists but is not cachable, 
+     * then a {@link ResourceAsHttpContent} instance is return. If 
+     * the resource does not exist, then null is returned.
+     * @throws IOException Problem loading the resource
+     */
+    public HttpContent lookup(String pathInContext)
+        throws IOException
+    {
+        // Is the content in this cache?
+        Content content =_cache.get(pathInContext);
+        if (content!=null && (content).isValid())
+            return content;
+       
+        // try loading the content from our factory.
+        Resource resource=_factory.getResource(pathInContext);
+        HttpContent loaded = load(pathInContext,resource);
+        if (loaded!=null)
+            return loaded;
+        
+        // Is the content in the parent cache?
+        if (_parent!=null)
+        {
+            HttpContent httpContent=_parent.lookup(pathInContext);
+            if (httpContent!=null)
+                return httpContent;
+        }
+        
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param resource
+     * @return True if the resource is cacheable. The default implementation tests the cache sizes.
+     */
+    protected boolean isCacheable(Resource resource)
+    {
+        long len = resource.length();
+
+        // Will it fit in the cache?
+        return  (len>0 && len<_maxCachedFileSize && len<_maxCacheSize);
+    }
+    
+    /* ------------------------------------------------------------ */
+    private HttpContent load(String pathInContext, Resource resource)
+        throws IOException
+    {
+        Content content=null;
+        
+        if (resource==null || !resource.exists())
+            return null;
+        
+        // Will it fit in the cache?
+        if (!resource.isDirectory() && isCacheable(resource))
+        {   
+            // Create the Content (to increment the cache sizes before adding the content 
+            content = new Content(pathInContext,resource);
+
+            // reduce the cache to an acceptable size.
+            shrinkCache();
+
+            // Add it to the cache.
+            Content added = _cache.putIfAbsent(pathInContext,content);
+            if (added!=null)
+            {
+                content.invalidate();
+                content=added;
+            }
+
+            return content;
+        }
+        
+        return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etagSupported);
+        
+    }
+    
+    /* ------------------------------------------------------------ */
+    private void shrinkCache()
+    {
+        // While we need to shrink
+        while (_cache.size()>0 && (_cachedFiles.get()>_maxCachedFiles || _cachedSize.get()>_maxCacheSize))
+        {
+            // Scan the entire cache and generate an ordered list by last accessed time.
+            SortedSet<Content> sorted= new TreeSet<Content>(
+                    new Comparator<Content>()
+                    {
+                        public int compare(Content c1, Content c2)
+                        {
+                            if (c1._lastAccessed<c2._lastAccessed)
+                                return -1;
+                            
+                            if (c1._lastAccessed>c2._lastAccessed)
+                                return 1;
+
+                            if (c1._length<c2._length)
+                                return -1;
+                            
+                            return c1._key.compareTo(c2._key);
+                        }
+                    });
+            for (Content content : _cache.values())
+                sorted.add(content);
+            
+            // Invalidate least recently used first
+            for (Content content : sorted)
+            {
+                if (_cachedFiles.get()<=_maxCachedFiles && _cachedSize.get()<=_maxCacheSize)
+                    break;
+                if (content==_cache.remove(content.getKey()))
+                    content.invalidate();
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected ByteBuffer getIndirectBuffer(Resource resource)
+    {
+        try
+        {
+            return BufferUtil.toBuffer(resource,true);
+        }
+        catch(IOException|IllegalArgumentException e)
+        {
+            LOG.warn(e);
+            return null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected ByteBuffer getDirectBuffer(Resource resource)
+    {
+        try
+        {
+            if (_useFileMappedBuffer && resource.getFile()!=null) 
+                return BufferUtil.toMappedBuffer(resource.getFile());
+            
+            return BufferUtil.toBuffer(resource,true);
+        }
+        catch(IOException|IllegalArgumentException e)
+        {
+            LOG.warn(e);
+            return null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return "ResourceCache["+_parent+","+_factory+"]@"+hashCode();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /** MetaData associated with a context Resource.
+     */
+    public class Content implements HttpContent
+    {
+        final Resource _resource;
+        final int _length;
+        final String _key;
+        final long _lastModified;
+        final ByteBuffer _lastModifiedBytes;
+        final ByteBuffer _contentType;
+        final String _etag;
+        
+        volatile long _lastAccessed;
+        AtomicReference<ByteBuffer> _indirectBuffer=new AtomicReference<ByteBuffer>();
+        AtomicReference<ByteBuffer> _directBuffer=new AtomicReference<ByteBuffer>();
+
+        /* ------------------------------------------------------------ */
+        Content(String pathInContext,Resource resource)
+        {
+            _key=pathInContext;
+            _resource=resource;
+
+            String mimeType = _mimeTypes.getMimeByExtension(_resource.toString());
+            _contentType=(mimeType==null?null:BufferUtil.toBuffer(mimeType));
+            boolean exists=resource.exists();
+            _lastModified=exists?resource.lastModified():-1;
+            _lastModifiedBytes=_lastModified<0?null:BufferUtil.toBuffer(DateGenerator.formatDate(_lastModified));
+            
+            _length=exists?(int)resource.length():0;
+            _cachedSize.addAndGet(_length);
+            _cachedFiles.incrementAndGet();
+            _lastAccessed=System.currentTimeMillis();
+            
+            _etag=ResourceCache.this._etagSupported?resource.getWeakETag():null;
+        }
+
+
+        /* ------------------------------------------------------------ */
+        public String getKey()
+        {
+            return _key;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isCached()
+        {
+            return _key!=null;
+        }
+        
+        /* ------------------------------------------------------------ */
+        public boolean isMiss()
+        {
+            return false;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public Resource getResource()
+        {
+            return _resource;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String getETag()
+        {
+            return _etag;
+        }
+        
+        /* ------------------------------------------------------------ */
+        boolean isValid()
+        {
+            if (_lastModified==_resource.lastModified() && _length==_resource.length())
+            {
+                _lastAccessed=System.currentTimeMillis();
+                return true;
+            }
+
+            if (this==_cache.remove(_key))
+                invalidate();
+            return false;
+        }
+
+        /* ------------------------------------------------------------ */
+        protected void invalidate()
+        {
+            // Invalidate it
+            _cachedSize.addAndGet(-_length);
+            _cachedFiles.decrementAndGet();
+            _resource.close(); 
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String getLastModified()
+        {
+            return BufferUtil.toString(_lastModifiedBytes);
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String getContentType()
+        {
+            return BufferUtil.toString(_contentType);
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public void release()
+        {
+            // don't release while cached. Release when invalidated.
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public ByteBuffer getIndirectBuffer()
+        {
+            ByteBuffer buffer = _indirectBuffer.get();
+            if (buffer==null)
+            {
+                ByteBuffer buffer2=ResourceCache.this.getIndirectBuffer(_resource);
+                
+                if (buffer2==null)
+                    LOG.warn("Could not load "+this);
+                else if (_indirectBuffer.compareAndSet(null,buffer2))
+                    buffer=buffer2;
+                else
+                    buffer=_indirectBuffer.get();
+            }
+            if (buffer==null)
+                return null;
+            return buffer.slice();
+        }
+        
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public ByteBuffer getDirectBuffer()
+        {
+            ByteBuffer buffer = _directBuffer.get();
+            if (buffer==null)
+            {
+                ByteBuffer buffer2=ResourceCache.this.getDirectBuffer(_resource);
+
+                if (buffer2==null)
+                    LOG.warn("Could not load "+this);
+                else if (_directBuffer.compareAndSet(null,buffer2))
+                    buffer=buffer2;
+                else
+                    buffer=_directBuffer.get();
+            }
+            if (buffer==null)
+                return null;
+            return buffer.asReadOnlyBuffer();
+        }
+        
+        /* ------------------------------------------------------------ */
+        @Override
+        public long getContentLength()
+        {
+            return _length;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public InputStream getInputStream() throws IOException
+        {
+            ByteBuffer indirect = getIndirectBuffer();
+            if (indirect!=null && indirect.hasArray())
+                return new ByteArrayInputStream(indirect.array(),indirect.arrayOffset()+indirect.position(),indirect.remaining());
+           
+            return _resource.getInputStream();
+        }   
+        
+        /* ------------------------------------------------------------ */
+        @Override
+        public ReadableByteChannel getReadableByteChannel() throws IOException
+        {
+            return _resource.getReadableByteChannel();
+        }
+
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString()
+        {
+            return String.format("%s %s %d %s %s",_resource,_resource.exists(),_resource.lastModified(),_contentType,_lastModifiedBytes);
+        }   
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Response.java b/lib/jetty/org/eclipse/jetty/server/Response.java
new file mode 100644 (file)
index 0000000..0408c88
--- /dev/null
@@ -0,0 +1,1384 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import static org.eclipse.jetty.util.QuotedStringTokenizer.isQuoted;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.channels.IllegalSelectorException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>{@link Response} provides the implementation for {@link HttpServletResponse}.</p>
+ */
+public class Response implements HttpServletResponse
+{
+    private static final Logger LOG = Log.getLogger(Response.class);    
+    private static final String __COOKIE_DELIM="\",;\\ \t";
+    private final static String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
+    private final static int __MIN_BUFFER_SIZE = 1;
+    
+
+    // Cookie building buffer. Reduce garbage for cookie using applications
+    private static final ThreadLocal<StringBuilder> __cookieBuilder = new ThreadLocal<StringBuilder>()
+    {
+       @Override
+       protected StringBuilder initialValue()
+       {
+          return new StringBuilder(128);
+       }
+    };
+
+    /* ------------------------------------------------------------ */
+    public static Response getResponse(HttpServletResponse response)
+    {
+        if (response instanceof Response)
+            return (Response)response;
+        return HttpChannel.getCurrentHttpChannel().getResponse();
+    }
+    
+    
+    public enum OutputType
+    {
+        NONE, STREAM, WRITER
+    }
+
+    /**
+     * If a header name starts with this string,  the header (stripped of the prefix)
+     * can be set during include using only {@link #setHeader(String, String)} or
+     * {@link #addHeader(String, String)}.
+     */
+    public final static String SET_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include.";
+
+    /**
+     * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie
+     * will be set as HTTP ONLY.
+     */
+    public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
+
+    private final HttpChannel<?> _channel;
+    private final HttpFields _fields = new HttpFields();
+    private final AtomicInteger _include = new AtomicInteger();
+    private HttpOutput _out;
+    private int _status = HttpStatus.NOT_SET_000;
+    private String _reason;
+    private Locale _locale;
+    private MimeTypes.Type _mimeType;
+    private String _characterEncoding;
+    private boolean _explicitEncoding;
+    private String _contentType;
+    private OutputType _outputType = OutputType.NONE;
+    private ResponseWriter _writer;
+    private long _contentLength = -1;
+    
+
+    public Response(HttpChannel<?> channel, HttpOutput out)
+    {
+        _channel = channel;
+        _out = out;
+    }
+
+    protected HttpChannel<?> getHttpChannel()
+    {
+        return _channel;
+    }
+
+    protected void recycle()
+    {
+        _status = HttpStatus.NOT_SET_000;
+        _reason = null;
+        _locale = null;
+        _mimeType = null;
+        _characterEncoding = null;
+        _contentType = null;
+        _outputType = OutputType.NONE;
+        _contentLength = -1;
+        _out.reset();
+        _fields.clear();
+        _explicitEncoding=false;
+    }
+
+    public void setHeaders(HttpContent httpContent)
+    {
+        Response response = _channel.getResponse();
+        String contentType = httpContent.getContentType();
+        if (contentType != null && !response.getHttpFields().containsKey(HttpHeader.CONTENT_TYPE.asString()))
+            setContentType(contentType);
+        
+        if (httpContent.getContentLength() > 0)
+            setLongContentLength(httpContent.getContentLength());
+
+        String lm = httpContent.getLastModified();
+        if (lm != null)
+            response.getHttpFields().put(HttpHeader.LAST_MODIFIED, lm);
+        else if (httpContent.getResource() != null)
+        {
+            long lml = httpContent.getResource().lastModified();
+            if (lml != -1)
+                response.getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, lml);
+        }
+
+        String etag=httpContent.getETag();
+        if (etag!=null)
+            response.getHttpFields().put(HttpHeader.ETAG,etag);
+    }
+    
+    public HttpOutput getHttpOutput()
+    {
+        return _out;
+    }
+    
+    public void setHttpOutput(HttpOutput out)
+    {
+        _out=out;
+    }
+
+    public boolean isIncluding()
+    {
+        return _include.get() > 0;
+    }
+
+    public void include()
+    {
+        _include.incrementAndGet();
+    }
+
+    public void included()
+    {
+        _include.decrementAndGet();
+        if (_outputType == OutputType.WRITER)
+        {
+            _writer.reopen();
+        }
+        _out.reopen();
+    }
+
+    public void addCookie(HttpCookie cookie)
+    {
+        addSetCookie(
+                cookie.getName(),
+                cookie.getValue(),
+                cookie.getDomain(),
+                cookie.getPath(),
+                cookie.getMaxAge(),
+                cookie.getComment(),
+                cookie.isSecure(),
+                cookie.isHttpOnly(),
+                cookie.getVersion());;
+    }
+
+    @Override
+    public void addCookie(Cookie cookie)
+    {
+        String comment = cookie.getComment();
+        boolean httpOnly = false;
+
+        if (comment != null)
+        {
+            int i = comment.indexOf(HTTP_ONLY_COMMENT);
+            if (i >= 0)
+            {
+                httpOnly = true;
+                comment = comment.replace(HTTP_ONLY_COMMENT, "").trim();
+                if (comment.length() == 0)
+                    comment = null;
+            }
+        }
+        addSetCookie(cookie.getName(),
+                cookie.getValue(),
+                cookie.getDomain(),
+                cookie.getPath(),
+                cookie.getMaxAge(),
+                comment,
+                cookie.getSecure(),
+                httpOnly || cookie.isHttpOnly(),
+                cookie.getVersion());
+    }
+
+
+    /**
+     * Format a set cookie value
+     *
+     * @param name the name
+     * @param value the value
+     * @param domain the domain
+     * @param path the path
+     * @param maxAge the maximum age
+     * @param comment the comment (only present on versions > 0)
+     * @param isSecure true if secure cookie
+     * @param isHttpOnly true if for http only
+     * @param version version of cookie logic to use (0 == default behavior)
+     */
+    public void addSetCookie(
+            final String name,
+            final String value,
+            final String domain,
+            final String path,
+            final long maxAge,
+            final String comment,
+            final boolean isSecure,
+            final boolean isHttpOnly,
+            int version)
+    {
+        // Check arguments
+        if (name == null || name.length() == 0)
+            throw new IllegalArgumentException("Bad cookie name");
+
+        // Format value and params
+        StringBuilder buf = __cookieBuilder.get();
+        buf.setLength(0);
+        
+        // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting
+        boolean quote_name=isQuoteNeededForCookie(name);
+        quoteOnlyOrAppend(buf,name,quote_name);
+        
+        buf.append('=');
+        
+        // Remember name= part to look for other matching set-cookie
+        String name_equals=buf.toString();
+
+        // Append the value
+        boolean quote_value=isQuoteNeededForCookie(value);
+        quoteOnlyOrAppend(buf,value,quote_value);
+
+        // Look for domain and path fields and check if they need to be quoted
+        boolean has_domain = domain!=null && domain.length()>0;
+        boolean quote_domain = has_domain && isQuoteNeededForCookie(domain);
+        boolean has_path = path!=null && path.length()>0;
+        boolean quote_path = has_path && isQuoteNeededForCookie(path);
+        
+        // Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted
+        if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path || isQuoted(name) || isQuoted(value) || isQuoted(path) || isQuoted(domain)))
+            version=1;
+
+        // Append version
+        if (version==1)
+            buf.append (";Version=1");
+        else if (version>1)
+            buf.append (";Version=").append(version);
+        
+        // Append path
+        if (has_path)
+        {
+            buf.append(";Path=");
+            quoteOnlyOrAppend(buf,path,quote_path);
+        }
+        
+        // Append domain
+        if (has_domain)
+        {
+            buf.append(";Domain=");
+            quoteOnlyOrAppend(buf,domain,quote_domain);
+        }
+
+        // Handle max-age and/or expires
+        if (maxAge >= 0)
+        {
+            // Always use expires
+            // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies
+            buf.append(";Expires=");
+            if (maxAge == 0)
+                buf.append(__01Jan1970_COOKIE);
+            else
+                DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
+            
+            // for v1 cookies, also send max-age
+            if (version>=1)
+            {
+                buf.append(";Max-Age=");
+                buf.append(maxAge);
+            }
+        }
+
+        // add the other fields
+        if (isSecure)
+            buf.append(";Secure");
+        if (isHttpOnly)
+            buf.append(";HttpOnly");
+        if (comment != null)
+        {
+            buf.append(";Comment=");
+            quoteOnlyOrAppend(buf,comment,isQuoteNeededForCookie(comment));
+        }
+
+        // remove any existing set-cookie fields of same name
+        Iterator<HttpField> i=_fields.iterator();
+        while (i.hasNext())
+        {
+            HttpField field=i.next();
+            if (field.getHeader()==HttpHeader.SET_COOKIE)
+            {
+                String val = field.getValue();
+                if (val!=null && val.startsWith(name_equals))
+                {
+                    //existing cookie has same name, does it also match domain and path?
+                    if (((!has_domain && !val.contains("Domain")) || (has_domain && val.contains(domain))) &&
+                        ((!has_path && !val.contains("Path")) || (has_path && val.contains(path))))
+                    {
+                        i.remove();
+                    }
+                }
+            }
+        }
+        
+        // add the set cookie
+        _fields.add(HttpHeader.SET_COOKIE.toString(), buf.toString());
+
+        // Expire responses with set-cookie headers so they do not get cached.
+        _fields.put(HttpHeader.EXPIRES.toString(), DateGenerator.__01Jan1970);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Does a cookie value need to be quoted?
+     * @param s value string
+     * @return true if quoted;
+     * @throws IllegalArgumentException If there a control characters in the string
+     */
+    private static boolean isQuoteNeededForCookie(String s)
+    {
+        if (s==null || s.length()==0)
+            return true;
+        
+        if (QuotedStringTokenizer.isQuoted(s))
+            return false;
+
+        for (int i=0;i<s.length();i++)
+        {
+            char c = s.charAt(i);
+            if (__COOKIE_DELIM.indexOf(c)>=0)
+                return true;
+            
+            if (c<0x20 || c>=0x7f)
+                throw new IllegalArgumentException("Illegal character in cookie value");
+        }
+
+        return false;
+    }
+    
+    
+    private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote)
+    {
+        if (quote)
+            QuotedStringTokenizer.quoteOnly(buf,s);
+        else
+            buf.append(s);
+    }
+    
+    @Override
+    public boolean containsHeader(String name)
+    {
+        return _fields.containsKey(name);
+    }
+
+    @Override
+    public String encodeURL(String url)
+    {
+        final Request request = _channel.getRequest();
+        SessionManager sessionManager = request.getSessionManager();
+        if (sessionManager == null)
+            return url;
+
+        HttpURI uri = null;
+        if (sessionManager.isCheckingRemoteSessionIdEncoding() && URIUtil.hasScheme(url))
+        {
+            uri = new HttpURI(url);
+            String path = uri.getPath();
+            path = (path == null ? "" : path);
+            int port = uri.getPort();
+            if (port < 0)
+                port = HttpScheme.HTTPS.asString().equalsIgnoreCase(uri.getScheme()) ? 443 : 80;
+            if (!request.getServerName().equalsIgnoreCase(uri.getHost()) ||
+                    request.getServerPort() != port ||
+                    !path.startsWith(request.getContextPath())) //TODO the root context path is "", with which every non null string starts
+                return url;
+        }
+
+        String sessionURLPrefix = sessionManager.getSessionIdPathParameterNamePrefix();
+        if (sessionURLPrefix == null)
+            return url;
+
+        if (url == null)
+            return null;
+
+        // should not encode if cookies in evidence
+        if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs()) 
+        {
+            int prefix = url.indexOf(sessionURLPrefix);
+            if (prefix != -1)
+            {
+                int suffix = url.indexOf("?", prefix);
+                if (suffix < 0)
+                    suffix = url.indexOf("#", prefix);
+
+                if (suffix <= prefix)
+                    return url.substring(0, prefix);
+                return url.substring(0, prefix) + url.substring(suffix);
+            }
+            return url;
+        }
+
+        // get session;
+        HttpSession session = request.getSession(false);
+
+        // no session
+        if (session == null)
+            return url;
+
+        // invalid session
+        if (!sessionManager.isValid(session))
+            return url;
+
+        String id = sessionManager.getNodeId(session);
+
+        if (uri == null)
+            uri = new HttpURI(url);
+
+
+        // Already encoded
+        int prefix = url.indexOf(sessionURLPrefix);
+        if (prefix != -1)
+        {
+            int suffix = url.indexOf("?", prefix);
+            if (suffix < 0)
+                suffix = url.indexOf("#", prefix);
+
+            if (suffix <= prefix)
+                return url.substring(0, prefix + sessionURLPrefix.length()) + id;
+            return url.substring(0, prefix + sessionURLPrefix.length()) + id +
+                    url.substring(suffix);
+        }
+
+        // edit the session
+        int suffix = url.indexOf('?');
+        if (suffix < 0)
+            suffix = url.indexOf('#');
+        if (suffix < 0)
+        {
+            return url +
+                    ((HttpScheme.HTTPS.is(uri.getScheme()) || HttpScheme.HTTP.is(uri.getScheme())) && uri.getPath() == null ? "/" : "") + //if no path, insert the root path
+                    sessionURLPrefix + id;
+        }
+
+
+        return url.substring(0, suffix) +
+                ((HttpScheme.HTTPS.is(uri.getScheme()) || HttpScheme.HTTP.is(uri.getScheme())) && uri.getPath() == null ? "/" : "") + //if no path so insert the root path
+                sessionURLPrefix + id + url.substring(suffix);
+    }
+
+    @Override
+    public String encodeRedirectURL(String url)
+    {
+        return encodeURL(url);
+    }
+
+    @Override
+    @Deprecated
+    public String encodeUrl(String url)
+    {
+        return encodeURL(url);
+    }
+
+    @Override
+    @Deprecated
+    public String encodeRedirectUrl(String url)
+    {
+        return encodeRedirectURL(url);
+    }
+
+    @Override
+    public void sendError(int sc) throws IOException
+    {
+        if (sc == 102)
+            sendProcessing();
+        else
+            sendError(sc, null);
+    }
+
+    @Override
+    public void sendError(int code, String message) throws IOException
+    {
+        if (isIncluding())
+            return;
+
+        if (isCommitted())
+            LOG.warn("Committed before "+code+" "+message);
+
+        resetBuffer();
+        _characterEncoding=null;
+        setHeader(HttpHeader.EXPIRES,null);
+        setHeader(HttpHeader.LAST_MODIFIED,null);
+        setHeader(HttpHeader.CACHE_CONTROL,null);
+        setHeader(HttpHeader.CONTENT_TYPE,null);
+        setHeader(HttpHeader.CONTENT_LENGTH,null);
+
+        _outputType = OutputType.NONE;
+        setStatus(code);
+        _reason=message;
+
+        Request request = _channel.getRequest();
+        Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
+        if (message==null)
+            message=cause==null?HttpStatus.getMessage(code):cause.toString();
+
+        // If we are allowed to have a body
+        if (code!=SC_NO_CONTENT &&
+            code!=SC_NOT_MODIFIED &&
+            code!=SC_PARTIAL_CONTENT &&
+            code>=SC_OK)
+        {
+            ErrorHandler error_handler = ErrorHandler.getErrorHandler(_channel.getServer(),request.getContext()==null?null:request.getContext().getContextHandler());
+            if (error_handler!=null)
+            {
+                request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(code));
+                request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
+                request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
+                request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,request.getServletName());
+                error_handler.handle(null,_channel.getRequest(),_channel.getRequest(),this );
+            }
+            else
+            {
+                setHeader(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
+                setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString());
+                try (ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);)
+                {
+                    if (message != null)
+                    {
+                        message= StringUtil.replace(message, "&", "&amp;");
+                        message= StringUtil.replace(message, "<", "&lt;");
+                        message= StringUtil.replace(message, ">", "&gt;");
+                    }
+                    String uri= request.getRequestURI();
+                    if (uri!=null)
+                    {
+                        uri= StringUtil.replace(uri, "&", "&amp;");
+                        uri= StringUtil.replace(uri, "<", "&lt;");
+                        uri= StringUtil.replace(uri, ">", "&gt;");
+                    }
+
+                    writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
+                    writer.write("<title>Error ");
+                    writer.write(Integer.toString(code));
+                    writer.write(' ');
+                    if (message==null)
+                        writer.write(message);
+                    writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
+                    writer.write(Integer.toString(code));
+                    writer.write("</h2>\n<p>Problem accessing ");
+                    writer.write(uri);
+                    writer.write(". Reason:\n<pre>    ");
+                    writer.write(message);
+                    writer.write("</pre>");
+                    writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>");
+                    writer.write("\n</body>\n</html>\n");
+
+                    writer.flush();
+                    setContentLength(writer.size());
+                    try (ServletOutputStream outputStream = getOutputStream())
+                    {
+                        writer.writeTo(outputStream);
+                        writer.destroy();
+                    }
+                }
+            }
+        }
+        else if (code!=SC_PARTIAL_CONTENT)
+        {
+            // TODO work out why this is required?
+            _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_TYPE);
+            _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_LENGTH);
+            _characterEncoding=null;
+            _mimeType=null;
+        }
+
+        closeOutput();
+    }
+
+    /**
+     * Sends a 102-Processing response.
+     * If the connection is a HTTP connection, the version is 1.1 and the
+     * request has a Expect header starting with 102, then a 102 response is
+     * sent. This indicates that the request still be processed and real response
+     * can still be sent.   This method is called by sendError if it is passed 102.
+     * @see javax.servlet.http.HttpServletResponse#sendError(int)
+     */
+    public void sendProcessing() throws IOException
+    {
+        if (_channel.isExpecting102Processing() && !isCommitted())
+        {
+            _channel.sendResponse(HttpGenerator.PROGRESS_102_INFO, null, true);
+        }
+    }
+    
+    /**
+     * Sends a response with one of the 300 series redirection codes.
+     * @param code
+     * @param location
+     * @throws IOException
+     */
+    public void sendRedirect(int code, String location) throws IOException
+    {
+        if ((code < HttpServletResponse.SC_MULTIPLE_CHOICES) || (code >= HttpServletResponse.SC_BAD_REQUEST))
+            throw new IllegalArgumentException("Not a 3xx redirect code");
+        
+        if (isIncluding())
+            return;
+
+        if (location == null)
+            throw new IllegalArgumentException();
+
+        if (!URIUtil.hasScheme(location))
+        {
+            StringBuilder buf = _channel.getRequest().getRootURL();
+            if (location.startsWith("//"))
+            {
+                buf.delete(0, buf.length());
+                buf.append(_channel.getRequest().getScheme());
+                buf.append(":");
+                buf.append(location);
+            }
+            else if (location.startsWith("/"))
+                buf.append(location);
+            else
+            {
+                String path = _channel.getRequest().getRequestURI();
+                String parent = (path.endsWith("/")) ? path : URIUtil.parentPath(path);
+                location = URIUtil.addPaths(parent, location);
+                if (location == null)
+                    throw new IllegalStateException("path cannot be above root");
+                if (!location.startsWith("/"))
+                    buf.append('/');
+                buf.append(location);
+            }
+
+            location = buf.toString();
+            HttpURI uri = new HttpURI(location);
+            String path = uri.getDecodedPath();
+            String canonical = URIUtil.canonicalPath(path);
+            if (canonical == null)
+                throw new IllegalArgumentException();
+            if (!canonical.equals(path))
+            {
+                buf = _channel.getRequest().getRootURL();
+                buf.append(URIUtil.encodePath(canonical));
+                String param=uri.getParam();
+                if (param!=null)
+                {
+                    buf.append(';');
+                    buf.append(param);
+                }
+                String query=uri.getQuery();
+                if (query!=null)
+                {
+                    buf.append('?');
+                    buf.append(query);
+                }
+                String fragment=uri.getFragment();
+                if (fragment!=null)
+                {
+                    buf.append('#');
+                    buf.append(fragment);
+                }
+                location = buf.toString();
+            }
+        }
+
+        resetBuffer();
+        setHeader(HttpHeader.LOCATION, location);
+        setStatus(code);
+        closeOutput();
+    }
+
+    @Override
+    public void sendRedirect(String location) throws IOException
+    {
+        sendRedirect(HttpServletResponse.SC_MOVED_TEMPORARILY, location);
+    }
+
+    @Override
+    public void setDateHeader(String name, long date)
+    {
+        if (!isIncluding())
+            _fields.putDateField(name, date);
+    }
+
+    @Override
+    public void addDateHeader(String name, long date)
+    {
+        if (!isIncluding())
+            _fields.addDateField(name, date);
+    }
+
+    public void setHeader(HttpHeader name, String value)
+    {
+        if (HttpHeader.CONTENT_TYPE == name)
+            setContentType(value);
+        else
+        {
+            if (isIncluding())
+                return;
+
+            _fields.put(name, value);
+
+            if (HttpHeader.CONTENT_LENGTH == name)
+            {
+                if (value == null)
+                    _contentLength = -1l;
+                else
+                    _contentLength = Long.parseLong(value);
+            }
+        }
+    }
+
+    @Override
+    public void setHeader(String name, String value)
+    {
+        if (HttpHeader.CONTENT_TYPE.is(name))
+            setContentType(value);
+        else
+        {
+            if (isIncluding())
+            {
+                if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
+                    name = name.substring(SET_INCLUDE_HEADER_PREFIX.length());
+                else
+                    return;
+            }
+            _fields.put(name, value);
+            if (HttpHeader.CONTENT_LENGTH.is(name))
+            {
+                if (value == null)
+                    _contentLength = -1l;
+                else
+                    _contentLength = Long.parseLong(value);
+            }
+        }
+    }
+
+    @Override
+    public Collection<String> getHeaderNames()
+    {
+        final HttpFields fields = _fields;
+        return fields.getFieldNamesCollection();
+    }
+
+    @Override
+    public String getHeader(String name)
+    {
+        return _fields.getStringField(name);
+    }
+
+    @Override
+    public Collection<String> getHeaders(String name)
+    {
+        final HttpFields fields = _fields;
+        Collection<String> i = fields.getValuesList(name);
+        if (i == null)
+            return Collections.emptyList();
+        return i;
+    }
+
+    @Override
+    public void addHeader(String name, String value)
+    {
+        if (isIncluding())
+        {
+            if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
+                name = name.substring(SET_INCLUDE_HEADER_PREFIX.length());
+            else
+                return;
+        }
+
+        if (HttpHeader.CONTENT_TYPE.is(name))
+        {
+            setContentType(value);
+            return;
+        }
+        
+        if (HttpHeader.CONTENT_LENGTH.is(name))
+        {
+            setHeader(name,value);
+            return;
+        }
+        
+        _fields.add(name, value);
+    }
+
+    @Override
+    public void setIntHeader(String name, int value)
+    {
+        if (!isIncluding())
+        {
+            _fields.putLongField(name, value);
+            if (HttpHeader.CONTENT_LENGTH.is(name))
+                _contentLength = value;
+        }
+    }
+
+    @Override
+    public void addIntHeader(String name, int value)
+    {
+        if (!isIncluding())
+        {
+            _fields.add(name, Integer.toString(value));
+            if (HttpHeader.CONTENT_LENGTH.is(name))
+                _contentLength = value;
+        }
+    }
+
+    @Override
+    public void setStatus(int sc)
+    {
+        if (sc <= 0)
+            throw new IllegalArgumentException();
+        if (!isIncluding())
+        {
+            _status = sc;
+            _reason = null;
+        }
+    }
+
+    @Override
+    @Deprecated
+    public void setStatus(int sc, String sm)
+    {
+        setStatusWithReason(sc,sm);
+    }
+    
+    public void setStatusWithReason(int sc, String sm)
+    {
+        if (sc <= 0)
+            throw new IllegalArgumentException();
+        if (!isIncluding())
+        {
+            _status = sc;
+            _reason = sm;
+        }
+    }
+
+    @Override
+    public String getCharacterEncoding()
+    {
+        if (_characterEncoding == null)
+            _characterEncoding = StringUtil.__ISO_8859_1;
+        return _characterEncoding;
+    }
+
+    @Override
+    public String getContentType()
+    {
+        return _contentType;
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException
+    {
+        if (_outputType == OutputType.WRITER)
+            throw new IllegalStateException("WRITER");
+        _outputType = OutputType.STREAM;
+        return _out;
+    }
+
+    public boolean isWriting()
+    {
+        return _outputType == OutputType.WRITER;
+    }
+
+    @Override
+    public PrintWriter getWriter() throws IOException
+    {
+        if (_outputType == OutputType.STREAM)
+            throw new IllegalStateException("STREAM");
+
+        if (_outputType == OutputType.NONE)
+        {
+            /* get encoding from Content-Type header */
+            String encoding = _characterEncoding;
+            if (encoding == null)
+            {
+                if (_mimeType!=null && _mimeType.isCharsetAssumed())
+                    encoding=_mimeType.getCharset().toString();
+                else
+                {
+                    encoding = MimeTypes.inferCharsetFromContentType(_contentType);
+                    if (encoding == null)
+                        encoding = StringUtil.__ISO_8859_1;
+                    setCharacterEncoding(encoding,false);
+                }
+            }
+            
+            if (_writer != null && _writer.isFor(encoding))
+                _writer.reopen();
+            else
+            {
+                if (StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
+                    _writer = new ResponseWriter(new Iso88591HttpWriter(_out),encoding);
+                else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
+                    _writer = new ResponseWriter(new Utf8HttpWriter(_out),encoding);
+                else
+                    _writer = new ResponseWriter(new EncodingHttpWriter(_out, encoding),encoding);
+            }
+            
+            // Set the output type at the end, because setCharacterEncoding() checks for it
+            _outputType = OutputType.WRITER;
+        }
+        return _writer;
+    }
+
+    @Override
+    public void setContentLength(int len)
+    {
+        // Protect from setting after committed as default handling
+        // of a servlet HEAD request ALWAYS sets _content length, even
+        // if the getHandling committed the response!
+        if (isCommitted() || isIncluding())
+            return;
+
+        _contentLength = len;
+        if (_contentLength > 0)
+        {
+            long written = _out.getWritten();
+            if (written > len)
+                throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written);
+            
+            _fields.putLongField(HttpHeader.CONTENT_LENGTH, len);
+            if (isAllContentWritten(written))
+            {
+                try
+                {
+                    closeOutput();
+                }
+                catch(IOException e)
+                {
+                    throw new RuntimeIOException(e);
+                }
+            }
+        }
+        else if (_contentLength==0)
+        {
+            long written = _out.getWritten();
+            if (written > 0)
+                throw new IllegalArgumentException("setContentLength(0) when already written " + written);
+            _fields.put(HttpHeader.CONTENT_LENGTH, "0");
+        }
+        else
+            _fields.remove(HttpHeader.CONTENT_LENGTH);
+    }
+    
+    public long getContentLength()
+    {
+        return _contentLength;
+    }
+
+    public boolean isAllContentWritten(long written)
+    {
+        return (_contentLength >= 0 && written >= _contentLength);
+    }
+
+    public void closeOutput() throws IOException
+    {
+        switch (_outputType)
+        {
+            case WRITER:
+                _writer.close();
+                if (!_out.isClosed())
+                    _out.close();
+                break;
+            case STREAM:
+                getOutputStream().close();
+                break;
+            default:
+                _out.close();
+        }
+    }
+
+    public long getLongContentLength()
+    {
+        return _contentLength;
+    }
+
+    public void setLongContentLength(long len)
+    {
+        // Protect from setting after committed as default handling
+        // of a servlet HEAD request ALWAYS sets _content length, even
+        // if the getHandling committed the response!
+        if (isCommitted() || isIncluding())
+            return;
+        _contentLength = len;
+        _fields.putLongField(HttpHeader.CONTENT_LENGTH.toString(), len);
+    }
+    
+    @Override
+    public void setContentLengthLong(long length)
+    {
+        setLongContentLength(length);
+    }
+
+    @Override
+    public void setCharacterEncoding(String encoding)
+    {
+        setCharacterEncoding(encoding,true);
+    }
+    
+    private void setCharacterEncoding(String encoding, boolean explicit)
+    {
+        if (isIncluding() || isWriting())
+            return;
+
+        if (_outputType == OutputType.NONE && !isCommitted())
+        {
+            if (encoding == null)
+            {
+                _explicitEncoding=false;
+                
+                // Clear any encoding.
+                if (_characterEncoding != null)
+                {
+                    _characterEncoding = null;
+                    
+                    if (_mimeType!=null)
+                    {
+                        _mimeType=_mimeType.getBaseType();
+                        _contentType=_mimeType.asString();
+                        _fields.put(_mimeType.getContentTypeField());
+                    }
+                    else if (_contentType != null)
+                    {
+                        _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
+                        _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
+                    }
+                }
+            }
+            else
+            {
+                // No, so just add this one to the mimetype
+                _explicitEncoding = explicit;
+                _characterEncoding = HttpGenerator.__STRICT?encoding:StringUtil.normalizeCharset(encoding);
+                if (_mimeType!=null)
+                {
+                    _contentType=_mimeType.getBaseType().asString()+ "; charset=" + _characterEncoding;
+                    _mimeType = MimeTypes.CACHE.get(_contentType);
+                    if (_mimeType==null || HttpGenerator.__STRICT)
+                        _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
+                    else
+                        _fields.put(_mimeType.getContentTypeField());
+                }
+                else if (_contentType != null)
+                {
+                    _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType) + "; charset=" + _characterEncoding;
+                    _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setContentType(String contentType)
+    {
+        if (isCommitted() || isIncluding())
+            return;
+
+        if (contentType == null)
+        {
+            if (isWriting() && _characterEncoding != null)
+                throw new IllegalSelectorException();
+
+            if (_locale == null)
+                _characterEncoding = null;
+            _mimeType = null;
+            _contentType = null;
+            _fields.remove(HttpHeader.CONTENT_TYPE);
+        }
+        else
+        {
+            _contentType = contentType;
+            _mimeType = MimeTypes.CACHE.get(contentType);
+            
+            String charset;
+            if (_mimeType!=null && _mimeType.getCharset()!=null && !_mimeType.isCharsetAssumed())
+                charset=_mimeType.getCharset().toString();
+            else
+                charset = MimeTypes.getCharsetFromContentType(contentType);
+
+            if (charset == null)
+            {
+                if (_characterEncoding != null)
+                {
+                    _contentType = contentType + "; charset=" + _characterEncoding;
+                    _mimeType = null;
+                }
+            }
+            else if (isWriting() && !charset.equals(_characterEncoding))
+            {
+                // too late to change the character encoding;
+                _mimeType = null;
+                _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
+                if (_characterEncoding != null)
+                    _contentType = _contentType + "; charset=" + _characterEncoding;
+            }
+            else
+            {
+                _characterEncoding = charset;
+                _explicitEncoding = true;
+            }
+
+            if (HttpGenerator.__STRICT || _mimeType==null)
+                _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
+            else
+            {
+                _contentType=_mimeType.asString();
+                _fields.put(_mimeType.getContentTypeField());
+            }
+        }
+        
+    }
+
+    @Override
+    public void setBufferSize(int size)
+    {
+        if (isCommitted() || getContentCount() > 0)
+            throw new IllegalStateException("Committed or content written");
+        if (size <= 0)
+            size = __MIN_BUFFER_SIZE;
+        _out.setBufferSize(size);
+    }
+
+    @Override
+    public int getBufferSize()
+    {
+        return _out.getBufferSize();
+    }
+
+    @Override
+    public void flushBuffer() throws IOException
+    {
+        if (!_out.isClosed())
+            _out.flush();
+    }
+
+    @Override
+    public void reset()
+    {
+        resetForForward();
+        _status = 200;
+        _reason = null;
+        _contentLength = -1;
+        _fields.clear();
+
+        String connection = _channel.getRequest().getHttpFields().getStringField(HttpHeader.CONNECTION);
+        if (connection != null)
+        {
+            String[] values = connection.split(",");
+            for (int i = 0; values != null && i < values.length; i++)
+            {
+                HttpHeaderValue cb = HttpHeaderValue.CACHE.get(values[0].trim());
+
+                if (cb != null)
+                {
+                    switch (cb)
+                    {
+                        case CLOSE:
+                            _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.toString());
+                            break;
+
+                        case KEEP_ALIVE:
+                            if (HttpVersion.HTTP_1_0.is(_channel.getRequest().getProtocol()))
+                                _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.toString());
+                            break;
+                        case TE:
+                            _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.TE.toString());
+                            break;
+                        default:
+                    }
+                }
+            }
+        }
+    }
+
+    public void reset(boolean preserveCookies)
+    { 
+        if (!preserveCookies)
+            reset();
+        else
+        {
+            ArrayList<String> cookieValues = new ArrayList<String>(5);
+            Enumeration<String> vals = _fields.getValues(HttpHeader.SET_COOKIE.asString());
+            while (vals.hasMoreElements())
+                cookieValues.add(vals.nextElement());
+            reset();
+            for (String v:cookieValues)
+                _fields.add(HttpHeader.SET_COOKIE, v);
+        }
+    }
+
+    public void resetForForward()
+    {
+        resetBuffer();
+        _outputType = OutputType.NONE;
+    }
+
+    @Override
+    public void resetBuffer()
+    {
+        if (isCommitted())
+            throw new IllegalStateException("Committed");
+
+        switch (_outputType)
+        {
+            case STREAM:
+            case WRITER:
+                _out.reset();
+                break;
+            default:
+        }
+
+        _out.resetBuffer();
+    }
+
+    protected ResponseInfo newResponseInfo()
+    {
+        if (_status == HttpStatus.NOT_SET_000)
+            _status = HttpStatus.OK_200;
+        return new ResponseInfo(_channel.getRequest().getHttpVersion(), _fields, getLongContentLength(), getStatus(), getReason(), _channel.getRequest().isHead());
+    }
+
+    @Override
+    public boolean isCommitted()
+    {
+        return _channel.isCommitted();
+    }
+
+    @Override
+    public void setLocale(Locale locale)
+    {
+        if (locale == null || isCommitted() || isIncluding())
+            return;
+
+        _locale = locale;
+        _fields.put(HttpHeader.CONTENT_LANGUAGE, locale.toString().replace('_', '-'));
+
+        if (_outputType != OutputType.NONE)
+            return;
+
+        if (_channel.getRequest().getContext() == null)
+            return;
+
+        String charset = _channel.getRequest().getContext().getContextHandler().getLocaleEncoding(locale);
+
+        if (charset != null && charset.length() > 0 && !_explicitEncoding)
+            setCharacterEncoding(charset,false);
+    }
+
+    @Override
+    public Locale getLocale()
+    {
+        if (_locale == null)
+            return Locale.getDefault();
+        return _locale;
+    }
+
+    @Override
+    public int getStatus()
+    {
+        return _status;
+    }
+
+    public String getReason()
+    {
+        return _reason;
+    }
+
+    public HttpFields getHttpFields()
+    {
+        return _fields;
+    }
+
+    public long getContentCount()
+    {
+        return _out.getWritten();
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s %d %s%n%s", _channel.getRequest().getHttpVersion(), _status, _reason == null ? "" : _reason, _fields);
+    }
+    
+
+    private static class ResponseWriter extends PrintWriter
+    {
+        private final String _encoding;
+        private final HttpWriter _httpWriter;
+        
+        public ResponseWriter(HttpWriter httpWriter,String encoding)
+        {
+            super(httpWriter);
+            _httpWriter=httpWriter;
+            _encoding=encoding;
+        }
+
+        public boolean isFor(String encoding)
+        {
+            return _encoding.equalsIgnoreCase(encoding);
+        }
+        
+        protected void reopen()
+        {
+            super.clearError();
+            out=_httpWriter;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/SecureRequestCustomizer.java b/lib/jetty/org/eclipse/jetty/server/SecureRequestCustomizer.java
new file mode 100644 (file)
index 0000000..b017461
--- /dev/null
@@ -0,0 +1,168 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
+
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class SecureRequestCustomizer implements HttpConfiguration.Customizer
+{
+    private static final Logger LOG = Log.getLogger(SecureRequestCustomizer.class);
+    
+    /**
+     * The name of the SSLSession attribute that will contain any cached information.
+     */
+    public static final String CACHED_INFO_ATTR = CachedInfo.class.getName();
+
+
+    @Override
+    public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
+    {
+        if (request.getHttpChannel().getEndPoint() instanceof DecryptedEndPoint)
+        {
+            request.setScheme(HttpScheme.HTTPS.asString());
+            request.setSecure(true);
+            SslConnection.DecryptedEndPoint ssl_endp = (DecryptedEndPoint)request.getHttpChannel().getEndPoint();
+            SslConnection sslConnection = ssl_endp.getSslConnection();
+            SSLEngine sslEngine=sslConnection.getSSLEngine();
+            customize(sslEngine,request);
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Allow the Listener a chance to customise the request. before the server
+     * does its stuff. <br>
+     * This allows the required attributes to be set for SSL requests. <br>
+     * The requirements of the Servlet specs are:
+     * <ul>
+     * <li> an attribute named "javax.servlet.request.ssl_session_id" of type
+     * String (since Servlet Spec 3.0).</li>
+     * <li> an attribute named "javax.servlet.request.cipher_suite" of type
+     * String.</li>
+     * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
+     * <li> an attribute named "javax.servlet.request.X509Certificate" of type
+     * java.security.cert.X509Certificate[]. This is an array of objects of type
+     * X509Certificate, the order of this array is defined as being in ascending
+     * order of trust. The first certificate in the chain is the one set by the
+     * client, the next is the one used to authenticate the first, and so on.
+     * </li>
+     * </ul>
+     *
+     * @param request
+     *                HttpRequest to be customised.
+     */
+    public void customize(SSLEngine sslEngine, Request request)
+    {
+        request.setScheme(HttpScheme.HTTPS.asString());
+        SSLSession sslSession = sslEngine.getSession();
+
+        try
+        {
+            String cipherSuite=sslSession.getCipherSuite();
+            Integer keySize;
+            X509Certificate[] certs;
+            String idStr;
+
+            CachedInfo cachedInfo=(CachedInfo)sslSession.getValue(CACHED_INFO_ATTR);
+            if (cachedInfo!=null)
+            {
+                keySize=cachedInfo.getKeySize();
+                certs=cachedInfo.getCerts();
+                idStr=cachedInfo.getIdStr();
+            }
+            else 
+            {
+                keySize=new Integer(SslContextFactory.deduceKeyLength(cipherSuite));
+                certs=SslContextFactory.getCertChain(sslSession);
+                byte[] bytes = sslSession.getId();
+                idStr = TypeUtil.toHexString(bytes);
+                cachedInfo=new CachedInfo(keySize,certs,idStr);
+                sslSession.putValue(CACHED_INFO_ATTR,cachedInfo);
+            }
+
+            if (certs!=null)
+                request.setAttribute("javax.servlet.request.X509Certificate",certs);
+
+            request.setAttribute("javax.servlet.request.cipher_suite",cipherSuite);
+            request.setAttribute("javax.servlet.request.key_size",keySize);
+            request.setAttribute("javax.servlet.request.ssl_session_id", idStr);
+        }
+        catch (Exception e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Simple bundle of information that is cached in the SSLSession. Stores the
+     * effective keySize and the client certificate chain.
+     */
+    private static class CachedInfo
+    {
+        private final X509Certificate[] _certs;
+        private final Integer _keySize;
+        private final String _idStr;
+
+        CachedInfo(Integer keySize, X509Certificate[] certs,String idStr)
+        {
+            this._keySize=keySize;
+            this._certs=certs;
+            this._idStr=idStr;
+        }
+
+        X509Certificate[] getCerts()
+        {
+            return _certs;
+        }
+
+        Integer getKeySize()
+        {
+            return _keySize;
+        }
+
+        String getIdStr()
+        {
+            return _idStr;
+        }
+    }
+
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Server.java b/lib/jetty/org/eclipse/jetty/server/Server.java
new file mode 100644 (file)
index 0000000..b1437d6
--- /dev/null
@@ -0,0 +1,680 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.Jetty;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.component.Graceful;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ShutdownThread;
+import org.eclipse.jetty.util.thread.ThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool;
+
+/* ------------------------------------------------------------ */
+/** Jetty HTTP Servlet Server.
+ * This class is the main class for the Jetty HTTP Servlet server.
+ * It aggregates Connectors (HTTP request receivers) and request Handlers.
+ * The server is itself a handler and a ThreadPool.  Connectors use the ThreadPool methods
+ * to run jobs that will eventually call the handle method.
+ */
+@ManagedObject(value="Jetty HTTP Servlet server")
+public class Server extends HandlerWrapper implements Attributes
+{
+    private static final Logger LOG = Log.getLogger(Server.class);
+
+    private final AttributesMap _attributes = new AttributesMap();
+    private final ThreadPool _threadPool;
+    private final List<Connector> _connectors = new CopyOnWriteArrayList<>();
+    private SessionIdManager _sessionIdManager;
+    private boolean _stopAtShutdown;
+    private boolean _dumpAfterStart=false;
+    private boolean _dumpBeforeStop=false;
+    
+    private volatile DateField _dateField;
+    
+    /* ------------------------------------------------------------ */
+    public Server()
+    {
+        this((ThreadPool)null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience constructor
+     * Creates server and a {@link ServerConnector} at the passed port.
+     * @param port The port of a network HTTP connector (or 0 for a randomly allocated port).
+     * @see NetworkConnector#getLocalPort()
+     */
+    public Server(@Name("port")int port)
+    {
+        this((ThreadPool)null);
+        ServerConnector connector=new ServerConnector(this);
+        connector.setPort(port);
+        setConnectors(new Connector[]{connector});
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience constructor
+     * Creates server and a {@link ServerConnector} at the passed address.
+     */
+    public Server(@Name("address")InetSocketAddress addr)
+    {
+        this((ThreadPool)null);
+        ServerConnector connector=new ServerConnector(this);
+        connector.setHost(addr.getHostName());
+        connector.setPort(addr.getPort());
+        setConnectors(new Connector[]{connector});
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    public Server(@Name("threadpool") ThreadPool pool)
+    {
+        _threadPool=pool!=null?pool:new QueuedThreadPool();
+        addBean(_threadPool);
+        setServer(this);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("version of this server")
+    public static String getVersion()
+    {
+        return Jetty.VERSION;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean getStopAtShutdown()
+    {
+        return _stopAtShutdown;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setStopAtShutdown(boolean stop)
+    {
+        //if we now want to stop
+        if (stop)
+        {
+            //and we weren't stopping before
+            if (!_stopAtShutdown)
+            {
+                //only register to stop if we're already started (otherwise we'll do it in doStart())
+                if (isStarted())
+                    ShutdownThread.register(this);
+            }
+        }
+        else
+            ShutdownThread.deregister(this);
+
+        _stopAtShutdown=stop;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the connectors.
+     */
+    @ManagedAttribute(value="connectors for this server", readonly=true)
+    public Connector[] getConnectors()
+    {
+        List<Connector> connectors = new ArrayList<>(_connectors);
+        return connectors.toArray(new Connector[connectors.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addConnector(Connector connector)
+    {
+        if (connector.getServer() != this)
+            throw new IllegalArgumentException("Connector " + connector +
+                    " cannot be shared among server " + connector.getServer() + " and server " + this);
+        if (_connectors.add(connector))
+            addBean(connector);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Convenience method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to
+     * remove a connector.
+     * @param connector The connector to remove.
+     */
+    public void removeConnector(Connector connector)
+    {
+        if (_connectors.remove(connector))
+            removeBean(connector);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the connectors for this server.
+     * Each connector has this server set as it's ThreadPool and its Handler.
+     * @param connectors The connectors to set.
+     */
+    public void setConnectors(Connector[] connectors)
+    {
+        if (connectors != null)
+        {
+            for (Connector connector : connectors)
+            {
+                if (connector.getServer() != this)
+                    throw new IllegalArgumentException("Connector " + connector +
+                            " cannot be shared among server " + connector.getServer() + " and server " + this);
+            }
+        }
+
+        Connector[] oldConnectors = getConnectors();
+        updateBeans(oldConnectors, connectors);
+        _connectors.removeAll(Arrays.asList(oldConnectors));
+        if (connectors != null)
+            _connectors.addAll(Arrays.asList(connectors));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the threadPool.
+     */
+    @ManagedAttribute("the server thread pool")
+    public ThreadPool getThreadPool()
+    {
+        return _threadPool;
+    }
+
+    /**
+     * @return true if {@link #dumpStdErr()} is called after starting
+     */
+    @ManagedAttribute("dump state to stderr after start")
+    public boolean isDumpAfterStart()
+    {
+        return _dumpAfterStart;
+    }
+
+    /**
+     * @param dumpAfterStart true if {@link #dumpStdErr()} is called after starting
+     */
+    public void setDumpAfterStart(boolean dumpAfterStart)
+    {
+        _dumpAfterStart = dumpAfterStart;
+    }
+
+    /**
+     * @return true if {@link #dumpStdErr()} is called before stopping
+     */
+    @ManagedAttribute("dump state to stderr before stop")
+    public boolean isDumpBeforeStop()
+    {
+        return _dumpBeforeStop;
+    }
+
+    /**
+     * @param dumpBeforeStop true if {@link #dumpStdErr()} is called before stopping
+     */
+    public void setDumpBeforeStop(boolean dumpBeforeStop)
+    {
+        _dumpBeforeStop = dumpBeforeStop;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpField getDateField()
+    {
+        long now=System.currentTimeMillis();
+        long seconds = now/1000;
+        DateField df = _dateField;
+        
+        if (df==null || df._seconds!=seconds)
+        {
+            synchronized (this) // Trade some contention for less garbage
+            {
+                df = _dateField;
+                if (df==null || df._seconds!=seconds)
+                {
+                    HttpField field=new HttpGenerator.CachedHttpField(HttpHeader.DATE,DateGenerator.formatDate(now));
+                    _dateField=new DateField(seconds,field);
+                    return field;
+                }
+            }
+        }
+        return df._dateField;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (getStopAtShutdown())
+        {
+            ShutdownThread.register(this);
+        }
+
+        ShutdownMonitor.getInstance().start(); // initialize
+
+        LOG.info("jetty-" + getVersion());
+        HttpGenerator.setJettyVersion(HttpConfiguration.SERVER_VERSION);
+        MultiException mex=new MultiException();
+
+        // check size of thread pool
+        SizedThreadPool pool = getBean(SizedThreadPool.class);
+        int max=pool==null?-1:pool.getMaxThreads();
+        int needed=1;
+        if (mex.size()==0)
+        {
+            for (Connector connector : _connectors)
+            {
+                if (connector instanceof AbstractConnector)
+                    needed+=((AbstractConnector)connector).getAcceptors();
+                if (connector instanceof ServerConnector)
+                    needed+=((ServerConnector)connector).getSelectorManager().getSelectorCount();
+            }
+        }
+
+        if (max>0 && needed>max)
+            throw new IllegalStateException("Insufficient max threads in ThreadPool: max="+max+" < needed="+needed);
+        
+        try
+        {
+            super.doStart();
+        }
+        catch(Throwable e)
+        {
+            mex.add(e);
+        }
+
+        // start connectors last
+        for (Connector connector : _connectors)
+        {
+            try
+            {   
+                connector.start();
+            }
+            catch(Throwable e)
+            {
+                mex.add(e);
+            }
+        }
+        
+        if (isDumpAfterStart())
+            dumpStdErr();
+
+        mex.ifExceptionThrow();
+
+        LOG.info(String.format("Started @%dms",ManagementFactory.getRuntimeMXBean().getUptime()));
+    }
+
+    @Override
+    protected void start(LifeCycle l) throws Exception
+    {
+        // start connectors last
+        if (!(l instanceof Connector))
+            super.start(l);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        if (isDumpBeforeStop())
+            dumpStdErr();
+
+        MultiException mex=new MultiException();
+
+        // list if graceful futures
+        List<Future<Void>> futures = new ArrayList<>();
+
+        // First close the network connectors to stop accepting new connections
+        for (Connector connector : _connectors)
+            futures.add(connector.shutdown());
+
+        // Then tell the contexts that we are shutting down
+        
+        Handler[] gracefuls = getChildHandlersByClass(Graceful.class);
+        for (Handler graceful : gracefuls)
+            futures.add(((Graceful)graceful).shutdown());
+
+        // Shall we gracefully wait for zero connections?
+        long stopTimeout = getStopTimeout();
+        if (stopTimeout>0)
+        {
+            long stop_by=System.currentTimeMillis()+stopTimeout;
+            LOG.debug("Graceful shutdown {} by ",this,new Date(stop_by));
+
+            // Wait for shutdowns
+            for (Future<Void> future: futures)
+            {
+                try
+                {
+                    if (!future.isDone())
+                        future.get(Math.max(1L,stop_by-System.currentTimeMillis()),TimeUnit.MILLISECONDS);
+                }
+                catch (Exception e)
+                {
+                    mex.add(e.getCause());
+                }
+            }
+        }
+
+        // Cancel any shutdowns not done
+        for (Future<Void> future: futures)
+            if (!future.isDone())
+                future.cancel(true);
+
+        // Now stop the connectors (this will close existing connections)
+        for (Connector connector : _connectors)
+        {
+            try
+            {
+                connector.stop();
+            }
+            catch (Throwable e)
+            {
+                mex.add(e);
+            }
+        }
+
+        // And finally stop everything else
+        try
+        {
+            super.doStop();
+        }
+        catch (Throwable e)
+        {
+            mex.add(e);
+        }
+
+        if (getStopAtShutdown())
+            ShutdownThread.deregister(this);
+
+        mex.ifExceptionThrow();
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Handle a request from a connection.
+     * Called to handle a request on the connection when either the header has been received,
+     * or after the entire request has been received (for short requests of known length), or
+     * on the dispatch of an async request.
+     */
+    public void handle(HttpChannel<?> connection) throws IOException, ServletException
+    {
+        final String target=connection.getRequest().getPathInfo();
+        final Request request=connection.getRequest();
+        final Response response=connection.getResponse();
+
+        if (LOG.isDebugEnabled())
+            LOG.debug(request.getDispatcherType()+" "+target+" on "+connection);
+
+        if ("*".equals(target))
+        {
+            handleOptions(request,response);
+            if (!request.isHandled())
+                handle(target, request, request, response);
+        }
+        else
+            handle(target, request, request, response);
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus()+" handled="+request.isHandled());
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Handle Options request to server
+     */
+    protected void handleOptions(Request request,Response response) throws IOException
+    {
+        if (!HttpMethod.OPTIONS.is(request.getMethod()))
+            response.sendError(HttpStatus.BAD_REQUEST_400);
+        request.setHandled(true);
+        response.setStatus(200);
+        response.getHttpFields().put(HttpHeader.ALLOW,"GET,POST,HEAD,OPTIONS");
+        response.setContentLength(0);
+        response.closeOutput();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Handle a request from a connection.
+     * Called to handle a request on the connection when either the header has been received,
+     * or after the entire request has been received (for short requests of known length), or
+     * on the dispatch of an async request.
+     */
+    public void handleAsync(HttpChannel<?> connection) throws IOException, ServletException
+    {
+        final HttpChannelState state = connection.getRequest().getHttpChannelState();
+        final AsyncContextEvent event = state.getAsyncContextEvent();
+
+        final Request baseRequest=connection.getRequest();
+        final String path=event.getPath();
+
+        if (path!=null)
+        {
+            // this is a dispatch with a path
+            ServletContext context=event.getServletContext();
+            HttpURI uri = new HttpURI(context==null?path:URIUtil.addPaths(context.getContextPath(),path));
+            baseRequest.setUri(uri);
+            baseRequest.setRequestURI(null);
+            baseRequest.setPathInfo(baseRequest.getRequestURI());
+            if (uri.getQuery()!=null)
+                baseRequest.mergeQueryParameters(uri.getQuery(), true); //we have to assume dispatch path and query are UTF8
+        }
+
+        final String target=baseRequest.getPathInfo();
+        final HttpServletRequest request=(HttpServletRequest)event.getSuppliedRequest();
+        final HttpServletResponse response=(HttpServletResponse)event.getSuppliedResponse();
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug(request.getDispatcherType()+" "+target+" on "+connection);
+            handle(target, baseRequest, request, response);
+            LOG.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus());
+        }
+        else
+            handle(target, baseRequest, request, response);
+
+    }
+
+    /* ------------------------------------------------------------ */
+    public void join() throws InterruptedException
+    {
+        getThreadPool().join();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionIdManager.
+     */
+    public SessionIdManager getSessionIdManager()
+    {
+        return _sessionIdManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionIdManager The sessionIdManager to set.
+     */
+    public void setSessionIdManager(SessionIdManager sessionIdManager)
+    {
+        updateBean(_sessionIdManager,sessionIdManager);
+        _sessionIdManager=sessionIdManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.util.AttributesMap#clearAttributes()
+     */
+    @Override
+    public void clearAttributes()
+    {
+        Enumeration<String> names = _attributes.getAttributeNames();
+        while (names.hasMoreElements())
+            removeBean(_attributes.getAttribute(names.nextElement()));
+        _attributes.clearAttributes();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.util.AttributesMap#getAttribute(java.lang.String)
+     */
+    @Override
+    public Object getAttribute(String name)
+    {
+        return _attributes.getAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.util.AttributesMap#getAttributeNames()
+     */
+    @Override
+    public Enumeration<String> getAttributeNames()
+    {
+        return AttributesMap.getAttributeNamesCopy(_attributes);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.util.AttributesMap#removeAttribute(java.lang.String)
+     */
+    @Override
+    public void removeAttribute(String name)
+    {
+        Object bean=_attributes.getAttribute(name);
+        if (bean!=null)
+            removeBean(bean);
+        _attributes.removeAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.util.AttributesMap#setAttribute(java.lang.String, java.lang.Object)
+     */
+    @Override
+    public void setAttribute(String name, Object attribute)
+    {
+        addBean(attribute);
+        _attributes.setAttribute(name, attribute);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The URI of the first {@link NetworkConnector} and first {@link ContextHandler}, or null
+     */
+    public URI getURI()
+    {
+        NetworkConnector connector=null;
+        for (Connector c: _connectors)
+        {
+            if (c instanceof NetworkConnector)
+            {
+                connector=(NetworkConnector)c;
+                break;
+            }
+        }
+
+        if (connector==null)
+            return null;
+
+        ContextHandler context = getChildHandlerByClass(ContextHandler.class);
+
+        try
+        {
+            String scheme=connector.getDefaultConnectionFactory().getProtocol().startsWith("SSL-")?"https":"http";
+
+            String host=connector.getHost();
+            if (context!=null && context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+                host=context.getVirtualHosts()[0];
+            if (host==null)
+                host=InetAddress.getLocalHost().getHostAddress();
+
+            String path=context==null?null:context.getContextPath();
+            if (path==null)
+                path="/";
+            return new URI(scheme,null,host,connector.getLocalPort(),path,null,null);
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+            return null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return this.getClass().getName()+"@"+Integer.toHexString(hashCode());
+    }
+
+    @Override
+    public void dump(Appendable out,String indent) throws IOException
+    {
+        dumpBeans(out,indent,Collections.singleton(new ClassLoaderDump(this.getClass().getClassLoader())));
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void main(String...args) throws Exception
+    {
+        System.err.println(getVersion());
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class DateField
+    {
+        final long _seconds;
+        final HttpField _dateField;
+        public DateField(long seconds, HttpField dateField)
+        {
+            super();
+            _seconds = seconds;
+            _dateField = dateField;
+        }
+        
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ServerConnector.java b/lib/jetty/org/eclipse/jetty/server/ServerConnector.java
new file mode 100644 (file)
index 0000000..a7d5fe0
--- /dev/null
@@ -0,0 +1,483 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.nio.channels.Channel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.io.SelectorManager.ManagedSelector;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * This {@link Connector} implementation is the primary connector for the
+ * Jetty server over TCP/IP.  By the use of various {@link ConnectionFactory} instances it is able
+ * to accept connections for HTTP, SPDY and WebSocket, either directly or over SSL.
+ * <p>
+ * The connector is a fully asynchronous NIO based implementation that by default will
+ * use all the commons services (eg {@link Executor}, {@link Scheduler})  of the
+ * passed {@link Server} instance, but all services may also be constructor injected
+ * into the connector so that it may operate with dedicated or otherwise shared services.
+ * <p>
+ * <h2>Connection Factories</h2>
+ * Various convenience constructors are provided to assist with common configurations of
+ * ConnectionFactories, whose generic use is described in {@link AbstractConnector}.
+ * If no connection factories are passed, then the connector will
+ * default to use a {@link HttpConnectionFactory}.  If an non null {@link SslContextFactory}
+ * instance is passed, then this used to instantiate a {@link SslConnectionFactory} which is
+ * prepended to the other passed or default factories.
+ * <p>
+ * <h2>Selectors</h2>
+ * The connector will use the {@link Executor} service to execute a number of Selector Tasks,
+ * which are implemented to each use a NIO {@link Selector} instance to asynchronously
+ * schedule a set of accepted connections.  It is the selector thread that will call the
+ * {@link Callback} instances passed in the {@link EndPoint#fillInterested(Callback)} or
+ * {@link EndPoint#write(Callback, java.nio.ByteBuffer...)} methods.  It is expected
+ * that these callbacks may do some non-blocking IO work, but will always dispatch to the
+ * {@link Executor} service any blocking, long running or application tasks.
+ * <p>
+ * The default number of selectors is equal to the number of processors available to the JVM,
+ * which should allow optimal performance even if all the connections used are performing
+ * significant non-blocking work in the callback tasks.
+ *
+ */
+@ManagedObject("HTTP connector using NIO ByteChannels and Selectors")
+public class ServerConnector extends AbstractNetworkConnector
+{
+    private final SelectorManager _manager;
+    private volatile ServerSocketChannel _acceptChannel;
+    private volatile boolean _inheritChannel = false;
+    private volatile int _localPort = -1;
+    private volatile int _acceptQueueSize = 0;
+    private volatile boolean _reuseAddress = true;
+    private volatile int _lingerTime = -1;
+
+
+    /* ------------------------------------------------------------ */
+    /** HTTP Server Connection.
+     * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.</p>
+     * @param server The {@link Server} this connector will accept connection for. 
+     */
+    public ServerConnector(
+        @Name("server") Server server)
+    {
+        this(server,null,null,null,-1,-1,new HttpConnectionFactory());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** HTTP Server Connection.
+     * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.</p>
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param acceptors 
+     *          the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections.  If 0, then 
+     *          the selector threads are used to accept connections.
+     * @param selectors
+     *          the number of selector threads, or -1 for a default value. Selectors notice and schedule established connection that can make IO progress.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("acceptors") int acceptors,
+        @Name("selectors") int selectors)
+    {
+        this(server,null,null,null,acceptors,selectors,new HttpConnectionFactory());
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Generic Server Connection with default configuration.
+     * <p>Construct a Server Connector with the passed Connection factories.</p>
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param factories Zero or more {@link ConnectionFactory} instances used to create and configure connections.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("factories") ConnectionFactory... factories)
+    {
+        this(server,null,null,null,-1,-1,factories);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** HTTP Server Connection.
+     * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the primary protocol</p>.
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the 
+     * list of HTTP Connection Factory.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("sslContextFactory") SslContextFactory sslContextFactory)
+    {
+        this(server,null,null,null,-1,-1,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+    }
+
+    /* ------------------------------------------------------------ */
+    /** HTTP Server Connection.
+     * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the primary protocol</p>.
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the 
+     * list of HTTP Connection Factory.
+     * @param acceptors 
+     *          the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections.  If 0, then 
+     *          the selector threads are used to accept connections.
+     * @param selectors
+     *          the number of selector threads, or -1 for a default value. Selectors notice and schedule established connection that can make IO progress.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("acceptors") int acceptors,
+        @Name("selectors") int selectors,
+        @Name("sslContextFactory") SslContextFactory sslContextFactory)
+    {
+        this(server,null,null,null,acceptors,selectors,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Generic SSL Server Connection.
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the 
+     * list of ConnectionFactories, with the first factory being the default protocol for the SslConnectionFactory.
+     * @param factories Zero or more {@link ConnectionFactory} instances used to create and configure connections.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("sslContextFactory") SslContextFactory sslContextFactory,
+        @Name("factories") ConnectionFactory... factories)
+    {
+        this(server,null,null,null,-1,-1,AbstractConnectionFactory.getFactories(sslContextFactory,factories));
+    }
+
+    /** Generic Server Connection.
+     * @param server    
+     *          The server this connector will be accept connection for.  
+     * @param executor  
+     *          An executor used to run tasks for handling requests, acceptors and selectors. I
+     *          If null then use the servers executor
+     * @param scheduler 
+     *          A scheduler used to schedule timeouts. If null then use the servers scheduler
+     * @param bufferPool
+     *          A ByteBuffer pool used to allocate buffers.  If null then create a private pool with default configuration.
+     * @param acceptors 
+     *          the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections.  If 0, then 
+     *          the selector threads are used to accept connections.
+     * @param selectors
+     *          the number of selector threads, or -1 for a default value. Selectors notice and schedule established connection that can make IO progress.
+     * @param factories 
+     *          Zero or more {@link ConnectionFactory} instances used to create and configure connections.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("executor") Executor executor,
+        @Name("scheduler") Scheduler scheduler,
+        @Name("bufferPool") ByteBufferPool bufferPool,
+        @Name("acceptors") int acceptors,
+        @Name("selectors") int selectors,
+        @Name("factories") ConnectionFactory... factories)
+    {
+        super(server,executor,scheduler,bufferPool,acceptors,factories);
+        _manager = new ServerConnectorManager(getExecutor(), getScheduler(), selectors > 0 ? selectors : Runtime.getRuntime().availableProcessors());
+        addBean(_manager, true);
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+
+        if (getAcceptors()==0)
+        {
+            _acceptChannel.configureBlocking(false);
+            _manager.acceptor(_acceptChannel);
+        }
+    }
+
+    @Override
+    public boolean isOpen()
+    {
+        ServerSocketChannel channel = _acceptChannel;
+        return channel!=null && channel.isOpen();
+    }
+
+    /**
+     * @return whether this connector uses a channel inherited from the JVM.
+     * @see System#inheritedChannel()
+     */
+    public boolean isInheritChannel()
+    {
+        return _inheritChannel;
+    }
+
+    /**
+     * <p>Sets whether this connector uses a channel inherited from the JVM.</p>
+     * <p>If true, the connector first tries to inherit from a channel provided by the system.
+     * If there is no inherited channel available, or if the inherited channel is not usable,
+     * then it will fall back using {@link ServerSocketChannel}.</p>
+     * <p>Use it with xinetd/inetd, to launch an instance of Jetty on demand. The port
+     * used to access pages on the Jetty instance is the same as the port used to
+     * launch Jetty.</p>
+     *
+     * @param inheritChannel whether this connector uses a channel inherited from the JVM.
+     */
+    public void setInheritChannel(boolean inheritChannel)
+    {
+        _inheritChannel = inheritChannel;
+    }
+
+    @Override
+    public void open() throws IOException
+    {
+        if (_acceptChannel == null)
+        {
+            ServerSocketChannel serverChannel = null;
+            if (isInheritChannel())
+            {
+                Channel channel = System.inheritedChannel();
+                if (channel instanceof ServerSocketChannel)
+                    serverChannel = (ServerSocketChannel)channel;
+                else
+                    LOG.warn("Unable to use System.inheritedChannel() [{}]. Trying a new ServerSocketChannel at {}:{}", channel, getHost(), getPort());
+            }
+
+            if (serverChannel == null)
+            {
+                serverChannel = ServerSocketChannel.open();
+
+                InetSocketAddress bindAddress = getHost() == null ? new InetSocketAddress(getPort()) : new InetSocketAddress(getHost(), getPort());
+                serverChannel.socket().bind(bindAddress, getAcceptQueueSize());
+                serverChannel.socket().setReuseAddress(getReuseAddress());
+
+                _localPort = serverChannel.socket().getLocalPort();
+                if (_localPort <= 0)
+                    throw new IOException("Server channel not bound");
+
+                addBean(serverChannel);
+            }
+
+            serverChannel.configureBlocking(true);
+            addBean(serverChannel);
+
+            _acceptChannel = serverChannel;
+        }
+    }
+
+    @Override
+    public Future<Void> shutdown()
+    {
+        // TODO shutdown all the connections
+        return super.shutdown();
+    }
+
+    @Override
+    public void close()
+    {
+        ServerSocketChannel serverChannel = _acceptChannel;
+        _acceptChannel = null;
+
+        if (serverChannel != null)
+        {
+            removeBean(serverChannel);
+
+            // If the interrupt did not close it, we should close it
+            if (serverChannel.isOpen())
+            {
+                try
+                {
+                    serverChannel.close();
+                }
+                catch (IOException e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+        // super.close();
+        _localPort = -2;
+    }
+
+    @Override
+    public void accept(int acceptorID) throws IOException
+    {
+        ServerSocketChannel serverChannel = _acceptChannel;
+        if (serverChannel != null && serverChannel.isOpen())
+        {
+            SocketChannel channel = serverChannel.accept();
+            accepted(channel);
+        }
+    }
+    
+    private void accepted(SocketChannel channel) throws IOException
+    {
+        channel.configureBlocking(false);
+        Socket socket = channel.socket();
+        configure(socket);
+        _manager.accept(channel);
+    }
+
+    protected void configure(Socket socket)
+    {
+        try
+        {
+            socket.setTcpNoDelay(true);
+            if (_lingerTime >= 0)
+                socket.setSoLinger(true, _lingerTime / 1000);
+            else
+                socket.setSoLinger(false, 0);
+        }
+        catch (SocketException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+    public SelectorManager getSelectorManager()
+    {
+        return _manager;
+    }
+
+    @Override
+    public Object getTransport()
+    {
+        return _acceptChannel;
+    }
+
+    @Override
+    @ManagedAttribute("local port")
+    public int getLocalPort()
+    {
+        return _localPort;
+    }
+
+    protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+    {
+        return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout());
+    }
+
+    /**
+     * @return the linger time
+     * @see Socket#getSoLinger()
+     */
+    @ManagedAttribute("TCP/IP solinger time or -1 to disable")
+    public int getSoLingerTime()
+    {
+        return _lingerTime;
+    }
+
+    /**
+     * @param lingerTime the linger time. Use -1 to disable.
+     * @see Socket#setSoLinger(boolean, int)
+     */
+    public void setSoLingerTime(int lingerTime)
+    {
+        _lingerTime = lingerTime;
+    }
+
+    /**
+     * @return the accept queue size
+     */
+    @ManagedAttribute("Accept Queue size")
+    public int getAcceptQueueSize()
+    {
+        return _acceptQueueSize;
+    }
+
+    /**
+     * @param acceptQueueSize the accept queue size (also known as accept backlog)
+     */
+    public void setAcceptQueueSize(int acceptQueueSize)
+    {
+        _acceptQueueSize = acceptQueueSize;
+    }
+
+    /**
+     * @return whether the server socket reuses addresses
+     * @see ServerSocket#getReuseAddress()
+     */
+    public boolean getReuseAddress()
+    {
+        return _reuseAddress;
+    }
+
+    /**
+     * @param reuseAddress whether the server socket reuses addresses
+     * @see ServerSocket#setReuseAddress(boolean)
+     */
+    public void setReuseAddress(boolean reuseAddress)
+    {
+        _reuseAddress = reuseAddress;
+    }
+
+    private final class ServerConnectorManager extends SelectorManager
+    {
+        private ServerConnectorManager(Executor executor, Scheduler scheduler, int selectors)
+        {
+            super(executor, scheduler, selectors);
+        }
+
+        @Override
+        protected void accepted(SocketChannel channel) throws IOException
+        {
+            ServerConnector.this.accepted(channel);
+        }
+
+        @Override
+        protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
+        {
+            return ServerConnector.this.newEndPoint(channel, selectSet, selectionKey);
+        }
+
+        @Override
+        public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
+        {
+            return getDefaultConnectionFactory().newConnection(ServerConnector.this, endpoint);
+        }
+
+        @Override
+        protected void endPointOpened(EndPoint endpoint)
+        {
+            super.endPointOpened(endpoint);
+            onEndPointOpened(endpoint);
+        }
+
+        @Override
+        protected void endPointClosed(EndPoint endpoint)
+        {
+            onEndPointClosed(endpoint);
+            super.endPointClosed(endpoint);
+        }
+        
+        
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ServletRequestHttpWrapper.java b/lib/jetty/org/eclipse/jetty/server/ServletRequestHttpWrapper.java
new file mode 100644 (file)
index 0000000..11f249e
--- /dev/null
@@ -0,0 +1,238 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Enumeration;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestWrapper;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
+import javax.servlet.http.Part;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * ServletRequestHttpWrapper
+ * 
+ * Class to tunnel a ServletRequest via a HttpServletRequest
+ */
+public class ServletRequestHttpWrapper extends ServletRequestWrapper implements HttpServletRequest
+{
+    public ServletRequestHttpWrapper(ServletRequest request)
+    {
+        super(request);
+    }
+
+    public String getAuthType()
+    {
+        return null;
+    }
+
+    public Cookie[] getCookies()
+    {
+        return null;
+    }
+
+    public long getDateHeader(String name)
+    {
+        return 0;
+    }
+
+    public String getHeader(String name)
+    {
+        return null;
+    }
+
+    public Enumeration getHeaders(String name)
+    {
+        return null;
+    }
+
+    public Enumeration getHeaderNames()
+    {
+        return null;
+    }
+
+    public int getIntHeader(String name)
+    {
+        return 0;
+    }
+
+    public String getMethod()
+    {
+        return null;
+    }
+
+    public String getPathInfo()
+    {
+        return null;
+    }
+
+    public String getPathTranslated()
+    {
+        return null;
+    }
+
+    public String getContextPath()
+    {
+        return null;
+    }
+
+    public String getQueryString()
+    {
+        return null;
+    }
+
+    public String getRemoteUser()
+    {
+        return null;
+    }
+
+    public boolean isUserInRole(String role)
+    {
+        return false;
+    }
+
+    public Principal getUserPrincipal()
+    {
+        return null;
+    }
+
+    public String getRequestedSessionId()
+    {
+        return null;
+    }
+
+    public String getRequestURI()
+    {
+        return null;
+    }
+
+    public StringBuffer getRequestURL()
+    {
+        return null;
+    }
+
+    public String getServletPath()
+    {
+        return null;
+    }
+
+    public HttpSession getSession(boolean create)
+    {
+        return null;
+    }
+
+    public HttpSession getSession()
+    {
+        return null;
+    }
+
+    public boolean isRequestedSessionIdValid()
+    {
+        return false;
+    }
+
+    public boolean isRequestedSessionIdFromCookie()
+    {
+        return false;
+    }
+
+    public boolean isRequestedSessionIdFromURL()
+    {
+        return false;
+    }
+
+    public boolean isRequestedSessionIdFromUrl()
+    {
+        return false;
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletRequest#authenticate(javax.servlet.http.HttpServletResponse)
+     */
+    public boolean authenticate(HttpServletResponse response) throws IOException, ServletException
+    {
+        return false;
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletRequest#getPart(java.lang.String)
+     */
+    public Part getPart(String name) throws IOException, ServletException
+    {
+        return null;
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletRequest#getParts()
+     */
+    public Collection<Part> getParts() throws IOException, ServletException
+    {
+        return null;
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletRequest#login(java.lang.String, java.lang.String)
+     */
+    public void login(String username, String password) throws ServletException
+    {
+
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletRequest#logout()
+     */
+    public void logout() throws ServletException
+    {
+
+    }
+
+
+    /** 
+     * @see javax.servlet.http.HttpServletRequest#changeSessionId()
+     */
+    @Override
+    public String changeSessionId()
+    {
+        // TODO 3.1 Auto-generated method stub
+        return null;
+    }
+
+    /** 
+     * @see javax.servlet.http.HttpServletRequest#upgrade(java.lang.Class)
+     */
+    @Override
+    public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException
+    {
+        // TODO 3.1 Auto-generated method stub
+        return null;
+    }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ServletResponseHttpWrapper.java b/lib/jetty/org/eclipse/jetty/server/ServletResponseHttpWrapper.java
new file mode 100644 (file)
index 0000000..076ab1b
--- /dev/null
@@ -0,0 +1,148 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletResponseWrapper;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * ServletResponseHttpWrapper
+ * 
+ * Wrapper to tunnel a ServletResponse via a HttpServletResponse
+ */
+public class ServletResponseHttpWrapper extends ServletResponseWrapper implements HttpServletResponse
+{
+    public ServletResponseHttpWrapper(ServletResponse response)
+    {
+        super(response);
+    }
+
+    public void addCookie(Cookie cookie)
+    {
+    }
+
+    public boolean containsHeader(String name)
+    {
+        return false;
+    }
+
+    public String encodeURL(String url)
+    {
+        return null;
+    }
+
+    public String encodeRedirectURL(String url)
+    {
+        return null;
+    }
+
+    public String encodeUrl(String url)
+    {
+        return null;
+    }
+
+    public String encodeRedirectUrl(String url)
+    {
+        return null;
+    }
+
+    public void sendError(int sc, String msg) throws IOException
+    {
+    }
+
+    public void sendError(int sc) throws IOException
+    {
+    }
+
+    public void sendRedirect(String location) throws IOException
+    {
+    }
+
+    public void setDateHeader(String name, long date)
+    {
+    }
+
+    public void addDateHeader(String name, long date)
+    {
+    }
+
+    public void setHeader(String name, String value)
+    {
+    }
+
+    public void addHeader(String name, String value)
+    {
+    }
+
+    public void setIntHeader(String name, int value)
+    {
+    }
+
+    public void addIntHeader(String name, int value)
+    {
+    }
+
+    public void setStatus(int sc)
+    {
+    }
+
+    public void setStatus(int sc, String sm)
+    {
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletResponse#getHeader(java.lang.String)
+     */
+    public String getHeader(String name)
+    {
+        return null;
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletResponse#getHeaderNames()
+     */
+    public Collection<String> getHeaderNames()
+    {
+        return null;
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletResponse#getHeaders(java.lang.String)
+     */
+    public Collection<String> getHeaders(String name)
+    {
+        return null;
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletResponse#getStatus()
+     */
+    public int getStatus()
+    {
+        return 0;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/SessionIdManager.java b/lib/jetty/org/eclipse/jetty/server/SessionIdManager.java
new file mode 100644 (file)
index 0000000..ef7ee62
--- /dev/null
@@ -0,0 +1,94 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/** Session ID Manager.
+ * Manages session IDs across multiple contexts.
+ */
+public interface SessionIdManager extends LifeCycle
+{
+    /**
+     * @param id The session ID without any cluster node extension
+     * @return True if the session ID is in use by at least one context.
+     */
+    public boolean idInUse(String id);
+    
+    /**
+     * Add a session to the list of known sessions for a given ID.
+     * @param session The session
+     */
+    public void addSession(HttpSession session);
+    
+    /**
+     * Remove session from the list of known sessions for a given ID.
+     * @param session
+     */
+    public void removeSession(HttpSession session);
+    
+    /**
+     * Call {@link HttpSession#invalidate()} on all known sessions for the given id.
+     * @param id The session ID without any cluster node extension
+     */
+    public void invalidateAll(String id);
+    
+    /**
+     * @param request
+     * @param created
+     * @return the new session id
+     */
+    public String newSessionId(HttpServletRequest request,long created);
+    
+    
+    
+    public String getWorkerName();
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Get a cluster ID from a node ID.
+     * Strip node identifier from a located session ID.
+     * @param nodeId
+     * @return the cluster id
+     */
+    public String getClusterId(String nodeId);
+    
+    /* ------------------------------------------------------------ */
+    /** Get a node ID from a cluster ID and a request
+     * @param clusterId The ID of the session
+     * @param request The request that for the session (or null)
+     * @return The session ID qualified with the node ID.
+     */
+    public String getNodeId(String clusterId,HttpServletRequest request);
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Change the existing session id.
+    * 
+    * @param oldClusterId
+    * @param oldNodeId
+    * @param request
+    */
+    public void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request);    
+
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/SessionManager.java b/lib/jetty/org/eclipse/jetty/server/SessionManager.java
new file mode 100644 (file)
index 0000000..267391b
--- /dev/null
@@ -0,0 +1,317 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.EventListener;
+import java.util.Set;
+
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* --------------------------------------------------------------------- */
+/**
+ * Session Manager.
+ * The API required to manage sessions for a servlet context.
+ *
+ */
+
+/* ------------------------------------------------------------ */
+/**
+ */
+public interface SessionManager extends LifeCycle
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Session cookie name.
+     * Defaults to <code>JSESSIONID</code>, but can be set with the
+     * <code>org.eclipse.jetty.servlet.SessionCookie</code> context init parameter.
+     */
+    public final static String __SessionCookieProperty = "org.eclipse.jetty.servlet.SessionCookie";
+    public final static String __DefaultSessionCookie = "JSESSIONID";
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Session id path parameter name.
+     * Defaults to <code>jsessionid</code>, but can be set with the
+     * <code>org.eclipse.jetty.servlet.SessionIdPathParameterName</code> context init parameter.
+     * If set to null or "none" no URL rewriting will be done.
+     */
+    public final static String __SessionIdPathParameterNameProperty = "org.eclipse.jetty.servlet.SessionIdPathParameterName";
+    public final static String __DefaultSessionIdPathParameterName = "jsessionid";
+    public final static String __CheckRemoteSessionEncoding = "org.eclipse.jetty.servlet.CheckingRemoteSessionIdEncoding";
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Session Domain.
+     * If this property is set as a ServletContext InitParam, then it is
+     * used as the domain for session cookies. If it is not set, then
+     * no domain is specified for the session cookie.
+     */
+    public final static String __SessionDomainProperty = "org.eclipse.jetty.servlet.SessionDomain";
+    public final static String __DefaultSessionDomain = null;
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Session Path.
+     * If this property is set as a ServletContext InitParam, then it is
+     * used as the path for the session cookie.  If it is not set, then
+     * the context path is used as the path for the cookie.
+     */
+    public final static String __SessionPathProperty = "org.eclipse.jetty.servlet.SessionPath";
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Session Max Age.
+     * If this property is set as a ServletContext InitParam, then it is
+     * used as the max age for the session cookie.  If it is not set, then
+     * a max age of -1 is used.
+     */
+    public final static String __MaxAgeProperty = "org.eclipse.jetty.servlet.MaxAge";
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the <code>HttpSession</code> with the given session id
+     *
+     * @param id the session id
+     * @return the <code>HttpSession</code> with the corresponding id or null if no session with the given id exists
+     */
+    public HttpSession getHttpSession(String id);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Creates a new <code>HttpSession</code>.
+     *
+     * @param request the HttpServletRequest containing the requested session id
+     * @return the new <code>HttpSession</code>
+     */
+    public HttpSession newHttpSession(HttpServletRequest request);
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if session cookies should be HTTP-only (Microsoft extension)
+     * @see org.eclipse.jetty.http.HttpCookie#isHttpOnly()
+     */
+    public boolean getHttpOnly();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the max period of inactivity, after which the session is invalidated, in seconds.
+     * @see #setMaxInactiveInterval(int)
+     */
+    public int getMaxInactiveInterval();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the max period of inactivity, after which the session is invalidated, in seconds.
+     *
+     * @param seconds the max inactivity period, in seconds.
+     * @see #getMaxInactiveInterval()
+     */
+    public void setMaxInactiveInterval(int seconds);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the {@link SessionHandler}.
+     *
+     * @param handler the <code>SessionHandler</code> object
+     */
+    public void setSessionHandler(SessionHandler handler);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Adds an event listener for session-related events.
+     *
+     * @param listener the session event listener to add
+     *                 Individual SessionManagers implementations may accept arbitrary listener types,
+     *                 but they are expected to at least handle HttpSessionActivationListener,
+     *                 HttpSessionAttributeListener, HttpSessionBindingListener and HttpSessionListener.
+     * @see #removeEventListener(EventListener)
+     */
+    public void addEventListener(EventListener listener);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Removes an event listener for for session-related events.
+     *
+     * @param listener the session event listener to remove
+     * @see #addEventListener(EventListener)
+     */
+    public void removeEventListener(EventListener listener);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Removes all event listeners for session-related events.
+     *
+     * @see #removeEventListener(EventListener)
+     */
+    public void clearEventListeners();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Gets a Cookie for a session.
+     *
+     * @param session         the session to which the cookie should refer.
+     * @param contextPath     the context to which the cookie should be linked.
+     *                        The client will only send the cookie value when requesting resources under this path.
+     * @param requestIsSecure whether the client is accessing the server over a secure protocol (i.e. HTTPS).
+     * @return if this <code>SessionManager</code> uses cookies, then this method will return a new
+     *         {@link Cookie cookie object} that should be set on the client in order to link future HTTP requests
+     *         with the <code>session</code>. If cookies are not in use, this method returns <code>null</code>.
+     */
+    public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the cross context session id manager.
+     * @see #setSessionIdManager(SessionIdManager)
+     */
+    public SessionIdManager getSessionIdManager();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the cross context session id manager.
+     * @deprecated use {@link #getSessionIdManager()}
+     */
+    @Deprecated
+    public SessionIdManager getMetaManager();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the cross context session id manager
+     *
+     * @param idManager the cross context session id manager.
+     * @see #getSessionIdManager()
+     */
+    public void setSessionIdManager(SessionIdManager idManager);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param session the session to test for validity
+     * @return whether the given session is valid, that is, it has not been invalidated.
+     */
+    public boolean isValid(HttpSession session);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param session the session object
+     * @return the unique id of the session within the cluster, extended with an optional node id.
+     * @see #getClusterId(HttpSession)
+     */
+    public String getNodeId(HttpSession session);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param session the session object
+     * @return the unique id of the session within the cluster (without a node id extension)
+     * @see #getNodeId(HttpSession)
+     */
+    public String getClusterId(HttpSession session);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Called by the {@link SessionHandler} when a session is first accessed by a request.
+     *
+     * @param session the session object
+     * @param secure  whether the request is secure or not
+     * @return the session cookie. If not null, this cookie should be set on the response to either migrate
+     *         the session or to refresh a session cookie that may expire.
+     * @see #complete(HttpSession)
+     */
+    public HttpCookie access(HttpSession session, boolean secure);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Called by the {@link SessionHandler} when a session is last accessed by a request.
+     *
+     * @param session the session object
+     * @see #access(HttpSession, boolean)
+     */
+    public void complete(HttpSession session);
+
+    /**
+     * Sets the session id URL path parameter name.
+     *
+     * @param parameterName the URL path parameter name for session id URL rewriting (null or "none" for no rewriting).
+     * @see #getSessionIdPathParameterName()
+     * @see #getSessionIdPathParameterNamePrefix()
+     */
+    public void setSessionIdPathParameterName(String parameterName);
+
+    /**
+     * @return the URL path parameter name for session id URL rewriting, by default "jsessionid".
+     * @see #setSessionIdPathParameterName(String)
+     */
+    public String getSessionIdPathParameterName();
+
+    /**
+     * @return a formatted version of {@link #getSessionIdPathParameterName()}, by default
+     *         ";" + sessionIdParameterName + "=", for easier lookup in URL strings.
+     * @see #getSessionIdPathParameterName()
+     */
+    public String getSessionIdPathParameterNamePrefix();
+
+    /**
+     * @return whether the session management is handled via cookies.
+     */
+    public boolean isUsingCookies();
+
+    /**
+     * @return whether the session management is handled via URLs.
+     */
+    public boolean isUsingURLs();
+
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes();
+
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes();
+
+    public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes);
+
+    public SessionCookieConfig getSessionCookieConfig();
+
+    /**
+     * @return True if absolute URLs are check for remoteness before being session encoded.
+     */
+    public boolean isCheckingRemoteSessionIdEncoding();
+
+    /**
+     * @param remote True if absolute URLs are check for remoteness before being session encoded.
+     */
+    public void setCheckingRemoteSessionIdEncoding(boolean remote);
+    
+    /* ------------------------------------------------------------ */
+    /** Change the existing session id.
+    * 
+    * @param oldClusterId
+    * @param oldNodeId
+    * @param newClusterId
+    * @param newNodeId
+    */
+    public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId);  
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ShutdownMonitor.java b/lib/jetty/org/eclipse/jetty/server/ShutdownMonitor.java
new file mode 100644 (file)
index 0000000..abc6d61
--- /dev/null
@@ -0,0 +1,411 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+
+import org.eclipse.jetty.util.thread.ShutdownThread;
+
+/**
+ * Shutdown/Stop Monitor thread.
+ * <p>
+ * This thread listens on the port specified by the STOP.PORT system parameter (defaults to -1 for not listening) for request authenticated with the key given
+ * by the STOP.KEY system parameter (defaults to "eclipse") for admin requests.
+ * <p>
+ * If the stop port is set to zero, then a random port is assigned and the port number is printed to stdout.
+ * <p>
+ * Commands "stop" and "status" are currently supported.
+ */
+public class ShutdownMonitor 
+{
+    // Implementation of safe lazy init, using Initialization on Demand Holder technique.
+    static class Holder
+    {
+        static ShutdownMonitor instance = new ShutdownMonitor();
+    }
+
+    public static ShutdownMonitor getInstance()
+    {
+        return Holder.instance;
+    }
+
+    /**
+     * ShutdownMonitorThread
+     *
+     * Thread for listening to STOP.PORT for command to stop Jetty.
+     * If ShowndownMonitor.exitVm is true, then Sytem.exit will also be
+     * called after the stop.
+     *
+     */
+    public class ShutdownMonitorThread extends Thread
+    {
+
+        public ShutdownMonitorThread ()
+        {
+            setDaemon(true);
+            setName("ShutdownMonitor");
+        }
+        
+        @Override
+        public void run()
+        {
+            if (serverSocket == null)
+            {
+                return;
+            }
+
+            while (serverSocket != null)
+            {
+                Socket socket = null;
+                try
+                {
+                    socket = serverSocket.accept();
+
+                    LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
+                    String receivedKey = lin.readLine();
+                    if (!key.equals(receivedKey))
+                    {
+                        System.err.println("Ignoring command with incorrect key");
+                        continue;
+                    }
+
+                    OutputStream out = socket.getOutputStream();
+
+                    String cmd = lin.readLine();
+                    debug("command=%s",cmd);
+                    if ("stop".equals(cmd))
+                    {
+                        // Graceful Shutdown
+                        debug("Issuing graceful shutdown..");
+                        ShutdownThread.getInstance().run();
+                        
+                        //Stop accepting any more
+                        close(serverSocket);
+                        serverSocket = null;
+                        
+                        //Shutdown input from client
+                        shutdownInput(socket);
+
+                        // Reply to client
+                        debug("Informing client that we are stopped.");
+                        out.write("Stopped\r\n".getBytes(StandardCharsets.UTF_8));
+                        out.flush();
+
+                        // Shutdown Monitor
+                        socket.shutdownOutput();
+                        close(socket);
+                        socket = null;                        
+                        debug("Shutting down monitor");
+
+                        if (exitVm)
+                        {
+                            // Kill JVM
+                            debug("Killing JVM");
+                            System.exit(0);
+                        }
+                    }
+                    else if ("status".equals(cmd))
+                    {
+                        // Reply to client
+                        out.write("OK\r\n".getBytes(StandardCharsets.UTF_8));
+                        out.flush();
+                    }
+                }
+                catch (Exception e)
+                {
+                    debug(e);
+                    System.err.println(e.toString());
+                }
+                finally
+                {
+                    close(socket);
+                    socket = null;
+                }
+            }
+        }
+        
+        public void start()
+        {
+            if (isAlive())
+            {
+                // TODO why are we reentrant here?
+                if (DEBUG)
+                    System.err.printf("ShutdownMonitorThread already started");
+                return; // cannot start it again
+            }
+
+            startListenSocket();
+            
+            if (serverSocket == null)
+            {
+                return;
+            }
+            if (DEBUG)
+                System.err.println("Starting ShutdownMonitorThread");
+            super.start();
+        }
+        
+        private void startListenSocket()
+        {
+            if (port < 0)
+            {            
+                if (DEBUG)
+                    System.err.println("ShutdownMonitor not in use (port < 0): " + port);
+                return;
+            }
+
+            try
+            {
+                serverSocket = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1"));
+                if (port == 0)
+                {
+                    // server assigned port in use
+                    port = serverSocket.getLocalPort();
+                    System.out.printf("STOP.PORT=%d%n",port);
+                }
+
+                if (key == null)
+                {
+                    // create random key
+                    key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36);
+                    System.out.printf("STOP.KEY=%s%n",key);
+                }
+            }
+            catch (Exception e)
+            {
+                debug(e);
+                System.err.println("Error binding monitor port " + port + ": " + e.toString());
+                serverSocket = null;
+            }
+            finally
+            {
+                // establish the port and key that are in use
+                debug("STOP.PORT=%d",port);
+                debug("STOP.KEY=%s",key);
+                debug("%s",serverSocket);
+            }
+        }
+
+    }
+    
+    private boolean DEBUG;
+    private int port;
+    private String key;
+    private boolean exitVm;
+    private ServerSocket serverSocket;
+    private ShutdownMonitorThread thread;
+    
+    
+
+    /**
+     * Create a ShutdownMonitor using configuration from the System properties.
+     * <p>
+     * <code>STOP.PORT</code> = the port to listen on (empty, null, or values less than 0 disable the stop ability)<br>
+     * <code>STOP.KEY</code> = the magic key/passphrase to allow the stop (defaults to "eclipse")<br>
+     * <p>
+     * Note: server socket will only listen on localhost, and a successful stop will issue a System.exit() call.
+     */
+    private ShutdownMonitor()
+    {
+        Properties props = System.getProperties();
+
+        this.DEBUG = props.containsKey("DEBUG");
+
+        // Use values passed thru via /jetty-start/
+        this.port = Integer.parseInt(props.getProperty("STOP.PORT","-1"));
+        this.key = props.getProperty("STOP.KEY",null);
+        this.exitVm = true;
+    }
+
+    private void close(ServerSocket server)
+    {
+        if (server == null)
+        {
+            return;
+        }
+
+        try
+        {
+            server.close();
+        }
+        catch (IOException ignore)
+        {
+            debug(ignore);
+        }
+    }
+
+    private void close(Socket socket)
+    {
+        if (socket == null)
+        {
+            return;
+        }
+
+        try
+        {
+            socket.close();
+        }
+        catch (IOException ignore)
+        {
+            debug(ignore);
+        }
+    }
+
+    
+    private void shutdownInput(Socket socket)
+    {
+        if (socket == null)
+            return;
+        
+        try
+        {
+            socket.shutdownInput();
+        }   
+        catch (IOException ignore)
+        {
+            debug(ignore);
+        }
+    }
+    
+    
+    private void debug(String format, Object... args)
+    {
+        if (DEBUG)
+        {
+            System.err.printf("[ShutdownMonitor] " + format + "%n",args);
+        }
+    }
+
+    private void debug(Throwable t)
+    {
+        if (DEBUG)
+        {
+            t.printStackTrace(System.err);
+        }
+    }
+
+    public String getKey()
+    {
+        return key;
+    }
+
+    public int getPort()
+    {
+        return port;
+    }
+
+    public ServerSocket getServerSocket()
+    {
+        return serverSocket;
+    }
+
+    public boolean isExitVm()
+    {
+        return exitVm;
+    }
+
+
+    public void setDebug(boolean flag)
+    {
+        this.DEBUG = flag;
+    }
+
+    public void setExitVm(boolean exitVm)
+    {
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                throw new IllegalStateException("ShutdownMonitorThread already started");
+            }
+            this.exitVm = exitVm;
+        }
+    }
+
+    public void setKey(String key)
+    {
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                throw new IllegalStateException("ShutdownMonitorThread already started");
+            }
+            this.key = key;
+        }
+    }
+
+    public void setPort(int port)
+    {
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                throw new IllegalStateException("ShutdownMonitorThread already started");
+            }
+            this.port = port;
+        }
+    }
+
+    protected void start() throws Exception
+    {
+        ShutdownMonitorThread t = null;
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                // TODO why are we reentrant here?
+                if (DEBUG)
+                    System.err.printf("ShutdownMonitorThread already started");
+                return; // cannot start it again
+            }
+         
+            thread = new ShutdownMonitorThread();
+            t = thread;
+        }
+         
+        if (t != null)
+            t.start();
+    }
+
+
+    protected boolean isAlive ()
+    {
+        boolean result = false;
+        synchronized (this)
+        {
+            result = (thread != null && thread.isAlive());
+        }
+        return result;
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("%s[port=%d]",this.getClass().getName(),port);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/SslConnectionFactory.java b/lib/jetty/org/eclipse/jetty/server/SslConnectionFactory.java
new file mode 100644 (file)
index 0000000..5fcc103
--- /dev/null
@@ -0,0 +1,102 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server;
+
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class SslConnectionFactory extends AbstractConnectionFactory
+{
+    private final SslContextFactory _sslContextFactory;
+    private final String _nextProtocol;
+
+    public SslConnectionFactory()
+    {
+        this(HttpVersion.HTTP_1_1.asString());
+    }
+
+    public SslConnectionFactory(@Name("next") String nextProtocol)
+    {
+        this(null,nextProtocol);
+    }
+
+    public SslConnectionFactory(@Name("sslContextFactory") SslContextFactory factory, @Name("next") String nextProtocol)
+    {
+        super("SSL-"+nextProtocol);
+        _sslContextFactory=factory==null?new SslContextFactory():factory;
+        _nextProtocol=nextProtocol;
+        addBean(_sslContextFactory);
+    }
+
+    public SslContextFactory getSslContextFactory()
+    {
+        return _sslContextFactory;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+
+        SSLEngine engine = _sslContextFactory.newSSLEngine();
+        engine.setUseClientMode(false);
+        SSLSession session=engine.getSession();
+
+        if (session.getPacketBufferSize()>getInputBufferSize())
+            setInputBufferSize(session.getPacketBufferSize());
+    }
+
+    @Override
+    public Connection newConnection(Connector connector, EndPoint endPoint)
+    {
+        SSLEngine engine = _sslContextFactory.newSSLEngine(endPoint.getRemoteAddress());
+        engine.setUseClientMode(false);
+
+        SslConnection sslConnection = newSslConnection(connector, endPoint, engine);
+        sslConnection.setRenegotiationAllowed(_sslContextFactory.isRenegotiationAllowed());
+        configure(sslConnection, connector, endPoint);
+
+        ConnectionFactory next = connector.getConnectionFactory(_nextProtocol);
+        EndPoint decryptedEndPoint = sslConnection.getDecryptedEndPoint();
+        Connection connection = next.newConnection(connector, decryptedEndPoint);
+        decryptedEndPoint.setConnection(connection);
+
+        return sslConnection;
+    }
+
+    protected SslConnection newSslConnection(Connector connector, EndPoint endPoint, SSLEngine engine)
+    {
+        return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),getProtocol());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/UserIdentity.java b/lib/jetty/org/eclipse/jetty/server/UserIdentity.java
new file mode 100644 (file)
index 0000000..4e20331
--- /dev/null
@@ -0,0 +1,117 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.security.Principal;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+
+/* ------------------------------------------------------------ */
+/** User object that encapsulates user identity and operations such as run-as-role actions,
+ * checking isUserInRole and getUserPrincipal.
+ *
+ * Implementations of UserIdentity should be immutable so that they may be
+ * cached by Authenticators and LoginServices.
+ *
+ */
+public interface UserIdentity
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The user subject
+     */
+    Subject getSubject();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The user principal
+     */
+    Principal getUserPrincipal();
+
+    /* ------------------------------------------------------------ */
+    /** Check if the user is in a role.
+     * This call is used to satisfy authorization calls from
+     * container code which will be using translated role names.
+     * @param role A role name.
+     * @param scope
+     * @return True if the user can act in that role.
+     */
+    boolean isUserInRole(String role, Scope scope);
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * A UserIdentity Scope.
+     * A scope is the environment in which a User Identity is to
+     * be interpreted. Typically it is set by the target servlet of
+     * a request.
+     */
+    interface Scope
+    {
+        /* ------------------------------------------------------------ */
+        /**
+         * @return The context path that the identity is being considered within
+         */
+        String getContextPath();
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @return The name of the identity context. Typically this is the servlet name.
+         */
+        String getName();
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @return A map of role reference names that converts from names used by application code
+         * to names used by the context deployment.
+         */
+        Map<String,String> getRoleRefMap();
+    }
+
+    /* ------------------------------------------------------------ */
+    public interface UnauthenticatedUserIdentity extends UserIdentity
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public static final UserIdentity UNAUTHENTICATED_IDENTITY = new UnauthenticatedUserIdentity()
+    {
+        public Subject getSubject()
+        {
+            return null;
+        }
+
+        public Principal getUserPrincipal()
+        {
+            return null;
+        }
+
+        public boolean isUserInRole(String role, Scope scope)
+        {
+            return false;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "UNAUTHENTICATED";
+        }
+    };
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Utf8HttpWriter.java b/lib/jetty/org/eclipse/jetty/server/Utf8HttpWriter.java
new file mode 100644 (file)
index 0000000..cbd1adb
--- /dev/null
@@ -0,0 +1,188 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+/** OutputWriter.
+ * A writer that can wrap a {@link HttpOutput} stream and provide
+ * character encodings.
+ *
+ * The UTF-8 encoding is done by this class and no additional
+ * buffers or Writers are used.
+ * The UTF-8 code was inspired by http://javolution.org
+ */
+public class Utf8HttpWriter extends HttpWriter
+{
+    int _surrogate=0;
+
+    /* ------------------------------------------------------------ */
+    public Utf8HttpWriter(HttpOutput out)
+    {
+        super(out);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (char[] s,int offset, int length) throws IOException
+    {
+        HttpOutput out = _out;
+        if (length==0 && out.isAllContentWritten())
+        {
+            close();
+            return;
+        }
+        
+        while (length > 0)
+        {
+            _bytes.reset();
+            int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
+
+            byte[] buffer=_bytes.getBuf();
+            int bytes=_bytes.getCount();
+
+            if (bytes+chars>buffer.length)
+                chars=buffer.length-bytes;
+
+            for (int i = 0; i < chars; i++)
+            {
+                int code = s[offset+i];
+
+                // Do we already have a surrogate?
+                if(_surrogate==0)
+                {
+                    // No - is this char code a surrogate?
+                    if(Character.isHighSurrogate((char)code))
+                    {
+                        _surrogate=code; // UCS-?
+                        continue;
+                    }
+                }
+                // else handle a low surrogate
+                else if(Character.isLowSurrogate((char)code))
+                {
+                    code = Character.toCodePoint((char)_surrogate, (char)code); // UCS-4
+                }
+                // else UCS-2
+                else
+                {
+                    code=_surrogate; // UCS-2
+                    _surrogate=0; // USED
+                    i--;
+                }
+
+                if ((code & 0xffffff80) == 0)
+                {
+                    // 1b
+                    if (bytes>=buffer.length)
+                    {
+                        chars=i;
+                        break;
+                    }
+                    buffer[bytes++]=(byte)(code);
+                }
+                else
+                {
+                    if((code&0xfffff800)==0)
+                    {
+                        // 2b
+                        if (bytes+2>buffer.length)
+                        {
+                            chars=i;
+                            break;
+                        }
+                        buffer[bytes++]=(byte)(0xc0|(code>>6));
+                        buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                    }
+                    else if((code&0xffff0000)==0)
+                    {
+                        // 3b
+                        if (bytes+3>buffer.length)
+                        {
+                            chars=i;
+                            break;
+                        }
+                        buffer[bytes++]=(byte)(0xe0|(code>>12));
+                        buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                    }
+                    else if((code&0xff200000)==0)
+                    {
+                        // 4b
+                        if (bytes+4>buffer.length)
+                        {
+                            chars=i;
+                            break;
+                        }
+                        buffer[bytes++]=(byte)(0xf0|(code>>18));
+                        buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                    }
+                    else if((code&0xf4000000)==0)
+                    {
+                        // 5b
+                        if (bytes+5>buffer.length)
+                        {
+                            chars=i;
+                            break;
+                        }
+                        buffer[bytes++]=(byte)(0xf8|(code>>24));
+                        buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                    }
+                    else if((code&0x80000000)==0)
+                    {
+                        // 6b
+                        if (bytes+6>buffer.length)
+                        {
+                            chars=i;
+                            break;
+                        }
+                        buffer[bytes++]=(byte)(0xfc|(code>>30));
+                        buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                    }
+                    else
+                    {
+                        buffer[bytes++]=(byte)('?');
+                    }
+
+                    _surrogate=0; // USED
+
+                    if (bytes==buffer.length)
+                    {
+                        chars=i+1;
+                        break;
+                    }
+                }
+            }
+            _bytes.setCount(bytes);
+
+            _bytes.writeTo(out);
+            length-=chars;
+            offset+=chars;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/AbstractHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/AbstractHandler.java
new file mode 100644 (file)
index 0000000..3d92512
--- /dev/null
@@ -0,0 +1,108 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+
+import java.io.IOException;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** AbstractHandler.
+ */
+@ManagedObject("Jetty Handler")
+public abstract class AbstractHandler extends ContainerLifeCycle implements Handler
+{
+    private static final Logger LOG = Log.getLogger(AbstractHandler.class);
+
+    private Server _server;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * 
+     */
+    public AbstractHandler()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.thread.LifeCycle#start()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        LOG.debug("starting {}",this);
+        if (_server==null)
+            LOG.warn("No Server set for {}",this);
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.thread.LifeCycle#stop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        LOG.debug("stopping {}",this);
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        if (_server==server)
+            return;
+        if (isStarted())
+            throw new IllegalStateException(STARTED);
+        _server=server;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Server getServer()
+    {
+        return _server;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroy()
+    {
+        if (!isStopped())
+            throw new IllegalStateException("!STOPPED");
+        super.destroy();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dumpThis(Appendable out) throws IOException
+    {
+        out.append(toString()).append(" - ").append(getState()).append('\n');
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java b/lib/jetty/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java
new file mode 100644 (file)
index 0000000..0d54e53
--- /dev/null
@@ -0,0 +1,118 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+
+
+/* ------------------------------------------------------------ */
+/** Abstract Handler Container.
+ * This is the base class for handlers that may contain other handlers.
+ *
+ */
+public abstract class AbstractHandlerContainer extends AbstractHandler implements HandlerContainer
+{
+    /* ------------------------------------------------------------ */
+    public AbstractHandlerContainer()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Handler[] getChildHandlers()
+    {
+        List<Handler> list=new ArrayList<>();
+        expandChildren(list,null);
+        return list.toArray(new Handler[list.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Handler[] getChildHandlersByClass(Class<?> byclass)
+    {
+        List<Handler> list=new ArrayList<>();
+        expandChildren(list,byclass);
+        return list.toArray(new Handler[list.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public <T extends Handler> T getChildHandlerByClass(Class<T> byclass)
+    {
+        List<Handler> list=new ArrayList<>();
+        expandChildren(list,byclass);
+        if (list.isEmpty())
+            return null;
+        return (T)list.get(0);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void expandChildren(List<Handler> list, Class<?> byClass)
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void expandHandler(Handler handler, List<Handler> list, Class<?> byClass)
+    {
+        if (handler==null)
+            return;
+
+        if (byClass==null || byClass.isAssignableFrom(handler.getClass()))
+            list.add(handler);
+
+        if (handler instanceof AbstractHandlerContainer)
+            ((AbstractHandlerContainer)handler).expandChildren(list, byClass);
+        else if (handler instanceof HandlerContainer)
+        {
+            HandlerContainer container = (HandlerContainer)handler;
+            Handler[] handlers=byClass==null?container.getChildHandlers():container.getChildHandlersByClass(byClass);
+            list.addAll(Arrays.asList(handlers));
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static <T extends HandlerContainer> T findContainerOf(HandlerContainer root,Class<T>type, Handler handler)
+    {
+        if (root==null || handler==null)
+            return null;
+
+        Handler[] branches=root.getChildHandlersByClass(type);
+        if (branches!=null)
+        {
+            for (Handler h:branches)
+            {
+                T container = (T)h;
+                Handler[] candidates = container.getChildHandlersByClass(handler.getClass());
+                if (candidates!=null)
+                {
+                    for (Handler c:candidates)
+                        if (c==handler)
+                            return container;
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java b/lib/jetty/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java
new file mode 100644 (file)
index 0000000..c3d8b59
--- /dev/null
@@ -0,0 +1,95 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.File;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.jetty.server.handler.ContextHandler.AliasCheck;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Symbolic Link AliasChecker.
+ * <p>An instance of this class can be registered with {@link ContextHandler#addAliasCheck(AliasCheck)}
+ * to check resources that are aliased to other locations.   The checker uses the 
+ * Java {@link Files#readSymbolicLink(Path)} and {@link Path#toRealPath(java.nio.file.LinkOption...)}
+ * APIs to check if a file is aliased with symbolic links.</p>
+ */
+public class AllowSymLinkAliasChecker implements AliasCheck
+{
+    private static final Logger LOG = Log.getLogger(AllowSymLinkAliasChecker.class);
+    
+    @Override
+    public boolean check(String path, Resource resource)
+    {
+        try
+        {
+            File file =resource.getFile();
+            if (file==null)
+                return false;
+            
+            // If the file exists
+            if (file.exists())
+            {
+                // we can use the real path method to check the symlinks resolve to the alias
+                URI real = file.toPath().toRealPath().toUri();
+                if (real.equals(resource.getAlias()))
+                {
+                    LOG.debug("Allow symlink {} --> {}",resource,real);
+                    return true;
+                }
+            }
+            else
+            {
+                // file does not exists, so we have to walk the path and links ourselves.
+                Path p = file.toPath().toAbsolutePath();
+                File d = p.getRoot().toFile();
+                for (Path e:p)
+                {
+                    d=new File(d,e.toString());
+                    
+                    while (d.exists() && Files.isSymbolicLink(d.toPath()))
+                    {
+                        Path link=Files.readSymbolicLink(d.toPath());
+                        if (!link.isAbsolute())
+                            link=link.resolve(d.toPath());
+                        d=link.toFile().getAbsoluteFile().getCanonicalFile();
+                    }
+                }
+                if (resource.getAlias().equals(d.toURI()))
+                {
+                    LOG.debug("Allow symlink {} --> {}",resource,d);
+                    return true;
+                }
+            }
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+            LOG.ignore(e);
+        }
+        return false;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/ContextHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/ContextHandler.java
new file mode 100644 (file)
index 0000000..397cde1
--- /dev/null
@@ -0,0 +1,2807 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Future;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.FilterRegistration.Dynamic;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextAttributeEvent;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.ClassLoaderDump;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.Graceful;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/**
+ * ContextHandler.
+ *
+ * This handler wraps a call to handle by setting the context and servlet path, plus setting the context classloader.
+ *
+ * <p>
+ * If the context init parameter "org.eclipse.jetty.server.context.ManagedAttributes" is set to a comma separated list of names, then they are treated as
+ * context attribute names, which if set as attributes are passed to the servers Container so that they may be managed with JMX.
+ * <p>
+ * The maximum size of a form that can be processed by this context is controlled by the system properties org.eclipse.jetty.server.Request.maxFormKeys
+ * and org.eclipse.jetty.server.Request.maxFormContentSize.  These can also be configured with {@link #setMaxFormContentSize(int)} and {@link #setMaxFormKeys(int)}
+ * <p>
+ * This servers executore is made available via a context attributed "org.eclipse.jetty.server.Executor".
+ *
+ * @org.apache.xbean.XBean description="Creates a basic HTTP context"
+ */
+@ManagedObject("URI Context")
+public class ContextHandler extends ScopedHandler implements Attributes, Graceful
+{
+    public final static int SERVLET_MAJOR_VERSION=3;
+    public final static int SERVLET_MINOR_VERSION=0;
+    public static final Class[] SERVLET_LISTENER_TYPES = new Class[] {ServletContextListener.class,
+                                                                      ServletContextAttributeListener.class,
+                                                                      ServletRequestListener.class,
+                                                                      ServletRequestAttributeListener.class};
+
+    public static final int DEFAULT_LISTENER_TYPE_INDEX = 1;
+    public static final int EXTENDED_LISTENER_TYPE_INDEX = 0;
+
+
+    final private static String __unimplmented="Unimplemented - use org.eclipse.jetty.servlet.ServletContextHandler";
+
+    private static final Logger LOG = Log.getLogger(ContextHandler.class);
+
+    private static final ThreadLocal<Context> __context = new ThreadLocal<Context>();
+
+    /**
+     * If a context attribute with this name is set, it is interpreted as a comma separated list of attribute name. Any other context attributes that are set
+     * with a name from this list will result in a call to {@link #setManagedAttribute(String, Object)}, which typically initiates the creation of a JMX MBean
+     * for the attribute value.
+     */
+    public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes";
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the current ServletContext implementation.
+     *
+     * @return ServletContext implementation
+     */
+    public static Context getCurrentContext()
+    {
+        return __context.get();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static ContextHandler getContextHandler(ServletContext context)
+    {
+        if(context instanceof ContextHandler.Context)
+            return ((ContextHandler.Context)context).getContextHandler();
+        Context c=  getCurrentContext();
+        if (c!=null)
+            return c.getContextHandler();
+        return null;
+    }
+
+
+    protected Context _scontext;
+    private final AttributesMap _attributes;
+    private final Map<String, String> _initParams;
+    private ClassLoader _classLoader;
+    private String _contextPath = "/";
+
+    private String _displayName;
+
+    private Resource _baseResource;
+    private MimeTypes _mimeTypes;
+    private Map<String, String> _localeEncodingMap;
+    private String[] _welcomeFiles;
+    private ErrorHandler _errorHandler;
+    private String[] _vhosts;
+
+    private Logger _logger;
+    private boolean _allowNullPathInfo;
+    private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",-1).intValue();
+    private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",-1).intValue();
+    private boolean _compactPath = false;
+
+    private final List<EventListener> _eventListeners=new CopyOnWriteArrayList<>();
+    private final List<EventListener> _programmaticListeners=new CopyOnWriteArrayList<>();
+    private final List<ServletContextListener> _contextListeners=new CopyOnWriteArrayList<>();
+    private final List<ServletContextAttributeListener> _contextAttributeListeners=new CopyOnWriteArrayList<>();
+    private final List<ServletRequestListener> _requestListeners=new CopyOnWriteArrayList<>();
+    private final List<ServletRequestAttributeListener> _requestAttributeListeners=new CopyOnWriteArrayList<>();
+    private final List<EventListener> _durableListeners = new CopyOnWriteArrayList<>();
+    private Map<String, Object> _managedAttributes;
+    private String[] _protectedTargets;
+    private final CopyOnWriteArrayList<AliasCheck> _aliasChecks = new CopyOnWriteArrayList<ContextHandler.AliasCheck>();
+
+    public enum Availability { UNAVAILABLE,STARTING,AVAILABLE,SHUTDOWN,};
+    private volatile Availability _availability;
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ContextHandler()
+    {
+        super();
+        _scontext = new Context();
+        _attributes = new AttributesMap();
+        _initParams = new HashMap<String, String>();
+        addAliasCheck(new ApproveNonExistentDirectoryAliases());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    protected ContextHandler(Context context)
+    {
+        super();
+        _scontext = context;
+        _attributes = new AttributesMap();
+        _initParams = new HashMap<String, String>();
+        addAliasCheck(new ApproveNonExistentDirectoryAliases());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ContextHandler(String contextPath)
+    {
+        this();
+        setContextPath(contextPath);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ContextHandler(HandlerContainer parent, String contextPath)
+    {
+        this();
+        setContextPath(contextPath);
+        if (parent instanceof HandlerWrapper)
+            ((HandlerWrapper)parent).setHandler(this);
+        else if (parent instanceof HandlerCollection)
+            ((HandlerCollection)parent).addHandler(this);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        dumpBeans(out,indent,
+            Collections.singletonList(new ClassLoaderDump(getClassLoader())),
+            _initParams.entrySet(),
+            _attributes.getAttributeEntrySet(),
+            _scontext.getAttributeEntrySet());
+    }
+
+    /* ------------------------------------------------------------ */
+    public Context getServletContext()
+    {
+        return _scontext;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the allowNullPathInfo true if /context is not redirected to /context/
+     */
+    @ManagedAttribute("Checks if the /context is not redirected to /context/")
+    public boolean getAllowNullPathInfo()
+    {
+        return _allowNullPathInfo;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param allowNullPathInfo
+     *            true if /context is not redirected to /context/
+     */
+    public void setAllowNullPathInfo(boolean allowNullPathInfo)
+    {
+        _allowNullPathInfo = allowNullPathInfo;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        super.setServer(server);
+        if (_errorHandler != null)
+            _errorHandler.setServer(server);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a
+     * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a
+     * matching virtual host name.
+     *
+     * @param vhosts
+     *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
+     *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.  Hosts may start with '@', in which case they
+     *            will match the {@link Connector#getName()} for the request.
+     */
+    public void setVirtualHosts(String[] vhosts)
+    {
+        if (vhosts == null)
+        {
+            _vhosts = vhosts;
+        }
+        else
+        {
+            _vhosts = new String[vhosts.length];
+            for (int i = 0; i < vhosts.length; i++)
+                _vhosts[i] = normalizeHostname(vhosts[i]);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Either set virtual hosts or add to an existing set of virtual hosts.
+     *
+     * @param virtualHosts
+     *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
+     *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names. Host names may start with '@', in which case they
+     *            will match the {@link Connector#getName()} for the request.
+     */
+    public void addVirtualHosts(String[] virtualHosts)
+    {
+        if (virtualHosts == null)  // since this is add, we don't null the old ones
+        {
+            return;
+        }
+        else
+        {
+            List<String> currentVirtualHosts = null;
+            if (_vhosts != null)
+            {
+                currentVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
+            }
+            else
+            {
+                currentVirtualHosts = new ArrayList<String>();
+            }
+
+            for (int i = 0; i < virtualHosts.length; i++)
+            {
+                String normVhost = normalizeHostname(virtualHosts[i]);
+                if (!currentVirtualHosts.contains(normVhost))
+                {
+                    currentVirtualHosts.add(normVhost);
+                }
+            }
+            _vhosts = currentVirtualHosts.toArray(new String[0]);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Removes an array of virtual host entries, if this removes all entries the _vhosts will be set to null
+     *
+     *  @param virtualHosts
+     *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
+     *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
+     */
+    public void removeVirtualHosts(String[] virtualHosts)
+    {
+        if (virtualHosts == null)
+        {
+            return; // do nothing
+        }
+        else if ( _vhosts == null || _vhosts.length == 0)
+        {
+            return; // do nothing
+        }
+        else
+        {
+            List<String> existingVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
+
+            for (int i = 0; i < virtualHosts.length; i++)
+            {
+                String toRemoveVirtualHost = normalizeHostname(virtualHosts[i]);
+                if (existingVirtualHosts.contains(toRemoveVirtualHost))
+                {
+                    existingVirtualHosts.remove(toRemoveVirtualHost);
+                }
+            }
+
+            if (existingVirtualHosts.isEmpty())
+            {
+                _vhosts = null; // if we ended up removing them all, just null out _vhosts
+            }
+            else
+            {
+                _vhosts = existingVirtualHosts.toArray(new String[0]);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a
+     * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a
+     * matching virtual host name.
+     *
+     * @return Array of virtual hosts that this context responds to. A null host name or empty array means any hostname is acceptable. Host names may be String
+     *         representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
+     */
+    @ManagedAttribute(value="Virtual hosts accepted by the context", readonly=true)
+    public String[] getVirtualHosts()
+    {
+        return _vhosts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
+     */
+    @Override
+    public Object getAttribute(String name)
+    {
+        return _attributes.getAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#getAttributeNames()
+     */
+    @Override
+    public Enumeration<String> getAttributeNames()
+    {
+        return AttributesMap.getAttributeNamesCopy(_attributes);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the attributes.
+     */
+    public Attributes getAttributes()
+    {
+        return _attributes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the classLoader.
+     */
+    public ClassLoader getClassLoader()
+    {
+        return _classLoader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Make best effort to extract a file classpath from the context classloader
+     *
+     * @return Returns the classLoader.
+     */
+    @ManagedAttribute("The file classpath")
+    public String getClassPath()
+    {
+        if (_classLoader == null || !(_classLoader instanceof URLClassLoader))
+            return null;
+        URLClassLoader loader = (URLClassLoader)_classLoader;
+        URL[] urls = loader.getURLs();
+        StringBuilder classpath = new StringBuilder();
+        for (int i = 0; i < urls.length; i++)
+        {
+            try
+            {
+                Resource resource = newResource(urls[i]);
+                File file = resource.getFile();
+                if (file != null && file.exists())
+                {
+                    if (classpath.length() > 0)
+                        classpath.append(File.pathSeparatorChar);
+                    classpath.append(file.getAbsolutePath());
+                }
+            }
+            catch (IOException e)
+            {
+                LOG.debug(e);
+            }
+        }
+        if (classpath.length() == 0)
+            return null;
+        return classpath.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the _contextPath.
+     */
+    @ManagedAttribute("True if URLs are compacted to replace the multiple '/'s with a single '/'")
+    public String getContextPath()
+    {
+        return _contextPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
+     */
+    public String getInitParameter(String name)
+    {
+        return _initParams.get(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public String setInitParameter(String name, String value)
+    {
+        return _initParams.put(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#getInitParameterNames()
+     */
+    @SuppressWarnings("rawtypes")
+    public Enumeration getInitParameterNames()
+    {
+        return Collections.enumeration(_initParams.keySet());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the initParams.
+     */
+    @ManagedAttribute("Initial Parameter map for the context")
+    public Map<String, String> getInitParams()
+    {
+        return _initParams;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#getServletContextName()
+     */
+    @ManagedAttribute(value="Display name of the Context", readonly=true)
+    public String getDisplayName()
+    {
+        return _displayName;
+    }
+
+    /* ------------------------------------------------------------ */
+    public EventListener[] getEventListeners()
+    {
+        return _eventListeners.toArray(new EventListener[_eventListeners.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the context event listeners.
+     *
+     * @param eventListeners
+     *            the event listeners
+     * @see ServletContextListener
+     * @see ServletContextAttributeListener
+     * @see ServletRequestListener
+     * @see ServletRequestAttributeListener
+     */
+    public void setEventListeners(EventListener[] eventListeners)
+    {
+        _contextListeners.clear();
+        _contextAttributeListeners.clear();
+        _requestListeners.clear();
+        _requestAttributeListeners.clear();
+        _eventListeners.clear();
+
+        if (eventListeners!=null)
+            for (EventListener listener : eventListeners)
+                addEventListener(listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add a context event listeners.
+     *
+     * @see ServletContextListener
+     * @see ServletContextAttributeListener
+     * @see ServletRequestListener
+     * @see ServletRequestAttributeListener
+     */
+    public void addEventListener(EventListener listener)
+    {
+        _eventListeners.add(listener);
+
+        if (!(isStarted() || isStarting()))
+            _durableListeners.add(listener);
+
+        if (listener instanceof ServletContextListener)
+            _contextListeners.add((ServletContextListener)listener);
+
+        if (listener instanceof ServletContextAttributeListener)
+            _contextAttributeListeners.add((ServletContextAttributeListener)listener);
+
+        if (listener instanceof ServletRequestListener)
+            _requestListeners.add((ServletRequestListener)listener);
+
+        if (listener instanceof ServletRequestAttributeListener)
+            _requestAttributeListeners.add((ServletRequestAttributeListener)listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Remove a context event listeners.
+     *
+     * @see ServletContextListener
+     * @see ServletContextAttributeListener
+     * @see ServletRequestListener
+     * @see ServletRequestAttributeListener
+     */
+    public void removeEventListener(EventListener listener)
+    {
+        _eventListeners.remove(listener);
+
+        if (listener instanceof ServletContextListener)
+            _contextListeners.remove(listener);
+
+        if (listener instanceof ServletContextAttributeListener)
+            _contextAttributeListeners.remove(listener);
+
+        if (listener instanceof ServletRequestListener)
+            _requestListeners.remove(listener);
+
+        if (listener instanceof ServletRequestAttributeListener)
+            _requestAttributeListeners.remove(listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Apply any necessary restrictions on a programmatic added listener.
+     *
+     * @param listener
+     */
+    protected void addProgrammaticListener (EventListener listener)
+    {
+        _programmaticListeners.add(listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected boolean isProgrammaticListener(EventListener listener)
+    {
+        return _programmaticListeners.contains(listener);
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if this context is accepting new requests
+     */
+    @ManagedAttribute("true for graceful shutdown, which allows existing requests to complete")
+    public boolean isShutdown()
+    {
+        switch(_availability)
+        {
+            case SHUTDOWN:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set shutdown status. This field allows for graceful shutdown of a context. A started context may be put into non accepting state so that existing
+     * requests can complete, but no new requests are accepted.
+     *
+     */
+    @Override
+    public Future<Void> shutdown()
+    {
+        _availability = isRunning() ? Availability.SHUTDOWN : Availability.UNAVAILABLE;
+        return new FutureCallback(true);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return false if this context is unavailable (sends 503)
+     */
+    public boolean isAvailable()
+    {
+        return _availability==Availability.AVAILABLE;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set Available status.
+     */
+    public void setAvailable(boolean available)
+    {
+        synchronized (this)
+        {
+            if (available && isRunning())
+                _availability = Availability.AVAILABLE;
+            else if (!available || !isRunning())
+                _availability = Availability.UNAVAILABLE;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public Logger getLogger()
+    {
+        return _logger;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setLogger(Logger logger)
+    {
+        _logger = logger;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        _availability = Availability.STARTING;
+
+        if (_contextPath == null)
+            throw new IllegalStateException("Null contextPath");
+
+        _logger = Log.getLogger(getDisplayName() == null?getContextPath():getDisplayName());
+        ClassLoader old_classloader = null;
+        Thread current_thread = null;
+        Context old_context = null;
+
+        _attributes.setAttribute("org.eclipse.jetty.server.Executor",getServer().getThreadPool());
+        
+        try
+        {
+            // Set the classloader
+            if (_classLoader != null)
+            {
+                current_thread = Thread.currentThread();
+                old_classloader = current_thread.getContextClassLoader();
+                current_thread.setContextClassLoader(_classLoader);
+            }
+
+            if (_mimeTypes == null)
+                _mimeTypes = new MimeTypes();
+
+            old_context = __context.get();
+            __context.set(_scontext);
+
+            // defers the calling of super.doStart()
+            startContext();
+
+            _availability = Availability.AVAILABLE;
+            LOG.info("Started {}", this);
+        }
+        finally
+        {
+            __context.set(old_context);
+
+            // reset the classloader
+            if (_classLoader != null && current_thread!=null)
+                current_thread.setContextClassLoader(old_classloader);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Extensible startContext. this method is called from {@link ContextHandler#doStart()} instead of a call to super.doStart(). This allows derived classes to
+     * insert additional handling (Eg configuration) before the call to super.doStart by this method will start contained handlers.
+     *
+     * @see org.eclipse.jetty.server.handler.ContextHandler.Context
+     */
+    protected void startContext() throws Exception
+    {
+        String managedAttributes = _initParams.get(MANAGED_ATTRIBUTES);
+        if (managedAttributes != null)
+        {
+            _managedAttributes = new HashMap<String, Object>();
+            String[] attributes = managedAttributes.split(",");
+            for (String attribute : attributes)
+                _managedAttributes.put(attribute,null);
+
+            Enumeration<String> e = _scontext.getAttributeNames();
+            while (e.hasMoreElements())
+            {
+                String name = e.nextElement();
+                Object value = _scontext.getAttribute(name);
+                checkManagedAttribute(name,value);
+            }
+        }
+
+        super.doStart();
+
+        // Call context listeners
+        if (!_contextListeners.isEmpty())
+        {
+            ServletContextEvent event = new ServletContextEvent(_scontext);
+            for (ServletContextListener listener:_contextListeners)
+                callContextInitialized(listener, event);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void callContextInitialized (ServletContextListener l, ServletContextEvent e)
+    {
+        LOG.debug("contextInitialized: {}->{}",e,l);
+        l.contextInitialized(e);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void callContextDestroyed (ServletContextListener l, ServletContextEvent e)
+    {
+        LOG.debug("contextDestroyed: {}->{}",e,l);
+        l.contextDestroyed(e);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        _availability = Availability.UNAVAILABLE;
+
+        ClassLoader old_classloader = null;
+        Thread current_thread = null;
+
+        Context old_context = __context.get();
+        __context.set(_scontext);
+        try
+        {
+            // Set the classloader
+            if (_classLoader != null)
+            {
+                current_thread = Thread.currentThread();
+                old_classloader = current_thread.getContextClassLoader();
+                current_thread.setContextClassLoader(_classLoader);
+            }
+
+            super.doStop();
+
+            // Context listeners
+            if (!_contextListeners.isEmpty())
+            {
+                ServletContextEvent event = new ServletContextEvent(_scontext);
+                for (int i = _contextListeners.size(); i-->0;)
+                    callContextDestroyed(_contextListeners.get(i),event);
+            }
+
+            //retain only durable listeners
+            setEventListeners(_durableListeners.toArray(new EventListener[_durableListeners.size()]));
+            _durableListeners.clear();
+
+            if (_errorHandler != null)
+                _errorHandler.stop();
+
+            Enumeration<String> e = _scontext.getAttributeNames();
+            while (e.hasMoreElements())
+            {
+                String name = e.nextElement();
+                checkManagedAttribute(name,null);
+            }
+
+            for (EventListener l : _programmaticListeners)
+                removeEventListener(l);
+            _programmaticListeners.clear();
+        }
+        finally
+        {
+            LOG.info("Stopped {}", this);
+            __context.set(old_context);
+            // reset the classloader
+            if (_classLoader != null && current_thread!=null)
+                current_thread.setContextClassLoader(old_classloader);
+        }
+
+        _scontext.clearAttributes();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    public boolean checkContext(final String target, final Request baseRequest, final HttpServletResponse response) throws IOException
+    {
+        DispatcherType dispatch = baseRequest.getDispatcherType();
+
+        switch (_availability)
+        {
+            case SHUTDOWN:
+            case UNAVAILABLE:
+                baseRequest.setHandled(true);
+                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+                return false;
+            default:
+                if ((DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled()))
+                    return false;
+        }
+
+        // Check the vhosts
+        if (_vhosts != null && _vhosts.length > 0)
+        {
+            String vhost = normalizeHostname(baseRequest.getServerName());
+
+            boolean match = false;
+            boolean connectorName = false;
+            boolean connectorMatch = false;
+
+            for (String contextVhost:_vhosts)
+            {
+                if (contextVhost == null || contextVhost.length()==0)
+                    continue;
+                char c=contextVhost.charAt(0);
+                switch (c)
+                {
+                    case '*':
+                        if (contextVhost.startsWith("*."))
+                            // wildcard only at the beginning, and only for one additional subdomain level
+                            match = match || contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".") + 1,contextVhost.length() - 2);
+                        break;
+                    case '@':
+                        connectorName=true;
+                        String name=baseRequest.getHttpChannel().getConnector().getName();
+                        boolean m=name!=null && contextVhost.length()==name.length()+1 && contextVhost.endsWith(name);
+                        match = match || m;
+                        connectorMatch = connectorMatch || m;
+                        break;
+                    default:
+                        match = match || contextVhost.equalsIgnoreCase(vhost);
+                }
+
+            }
+            if (!match || connectorName && !connectorMatch)
+                return false;
+        }
+
+        // Are we not the root context?
+        if (_contextPath.length() > 1)
+        {
+            // reject requests that are not for us
+            if (!target.startsWith(_contextPath))
+                return false;
+            if (target.length() > _contextPath.length() && target.charAt(_contextPath.length()) != '/')
+                return false;
+
+            // redirect null path infos
+            if (!_allowNullPathInfo && _contextPath.length() == target.length())
+            {
+                // context request must end with /
+                baseRequest.setHandled(true);
+                if (baseRequest.getQueryString() != null)
+                    response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH) + "?" + baseRequest.getQueryString());
+                else
+                    response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH));
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.handler.ScopedHandler#doScope(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("scope {}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),this);
+
+        Context old_context = null;
+        String old_context_path = null;
+        String old_servlet_path = null;
+        String old_path_info = null;
+        ClassLoader old_classloader = null;
+        Thread current_thread = null;
+        String pathInfo = target;
+
+        DispatcherType dispatch = baseRequest.getDispatcherType();
+
+        old_context = baseRequest.getContext();
+
+        // Are we already in this context?
+        if (old_context != _scontext)
+        {
+            // check the target.
+            if (DispatcherType.REQUEST.equals(dispatch) ||
+                DispatcherType.ASYNC.equals(dispatch) ||
+                DispatcherType.ERROR.equals(dispatch) && baseRequest.getHttpChannelState().isAsync())
+            {
+                if (_compactPath)
+                    target = URIUtil.compactPath(target);
+                if (!checkContext(target,baseRequest,response))
+                    return;
+
+                if (target.length() > _contextPath.length())
+                {
+                    if (_contextPath.length() > 1)
+                        target = target.substring(_contextPath.length());
+                    pathInfo = target;
+                }
+                else if (_contextPath.length() == 1)
+                {
+                    target = URIUtil.SLASH;
+                    pathInfo = URIUtil.SLASH;
+                }
+                else
+                {
+                    target = URIUtil.SLASH;
+                    pathInfo = null;
+                }
+            }
+
+            // Set the classloader
+            if (_classLoader != null)
+            {
+                current_thread = Thread.currentThread();
+                old_classloader = current_thread.getContextClassLoader();
+                current_thread.setContextClassLoader(_classLoader);
+            }
+        }
+
+        try
+        {
+            old_context_path = baseRequest.getContextPath();
+            old_servlet_path = baseRequest.getServletPath();
+            old_path_info = baseRequest.getPathInfo();
+
+            // Update the paths
+            baseRequest.setContext(_scontext);
+            __context.set(_scontext);
+            if (!DispatcherType.INCLUDE.equals(dispatch) && target.startsWith("/"))
+            {
+                if (_contextPath.length() == 1)
+                    baseRequest.setContextPath("");
+                else
+                    baseRequest.setContextPath(_contextPath);
+                baseRequest.setServletPath(null);
+                baseRequest.setPathInfo(pathInfo);
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("context={}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(), baseRequest.getPathInfo(),this);
+
+            // start manual inline of nextScope(target,baseRequest,request,response);
+            if (never())
+                nextScope(target,baseRequest,request,response);
+            else if (_nextScope != null)
+                _nextScope.doScope(target,baseRequest,request,response);
+            else if (_outerScope != null)
+                _outerScope.doHandle(target,baseRequest,request,response);
+            else
+                doHandle(target,baseRequest,request,response);
+            // end manual inline (pathentic attempt to reduce stack depth)
+        }
+        finally
+        {
+            if (old_context != _scontext)
+            {
+                // reset the classloader
+                if (_classLoader != null && current_thread!=null)
+                {
+                    current_thread.setContextClassLoader(old_classloader);
+                }
+
+                // reset the context and servlet path.
+                baseRequest.setContext(old_context);
+                __context.set(old_context);
+                baseRequest.setContextPath(old_context_path);
+                baseRequest.setServletPath(old_servlet_path);
+                baseRequest.setPathInfo(old_path_info);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.handler.ScopedHandler#doHandle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        final DispatcherType dispatch = baseRequest.getDispatcherType();
+        final boolean new_context = baseRequest.takeNewContext();
+        try
+        {
+            if (new_context)
+            {
+                // Handle the REALLY SILLY request events!
+                if (!_requestAttributeListeners.isEmpty())
+                    for (ServletRequestAttributeListener l :_requestAttributeListeners)
+                        baseRequest.addEventListener(l);
+
+                if (!_requestListeners.isEmpty())
+                {
+                    final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
+                    for (ServletRequestListener l : _requestListeners)
+                        l.requestInitialized(sre);
+                }
+            }
+
+            if (DispatcherType.REQUEST.equals(dispatch) && isProtectedTarget(target))
+            {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND);
+                baseRequest.setHandled(true);
+                return;
+            }
+
+            // start manual inline of nextHandle(target,baseRequest,request,response);
+            // noinspection ConstantIfStatement
+            if (never())
+                nextHandle(target,baseRequest,request,response);
+            else if (_nextScope != null && _nextScope == _handler)
+                _nextScope.doHandle(target,baseRequest,request,response);
+            else if (_handler != null)
+                _handler.handle(target,baseRequest,request,response);
+            // end manual inline
+        }
+        finally
+        {
+            // Handle more REALLY SILLY request events!
+            if (new_context)
+            {
+                if (!_requestListeners.isEmpty())
+                {
+                    final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
+                    for (int i=_requestListeners.size();i-->0;)
+                        _requestListeners.get(i).requestDestroyed(sre);
+                }
+
+                if (!_requestAttributeListeners.isEmpty())
+                {
+                    ListIterator<ServletRequestAttributeListener> iter = _requestAttributeListeners.listIterator(_requestAttributeListeners.size());
+                    for (int i=_requestAttributeListeners.size();i-->0;)
+                        baseRequest.removeEventListener(_requestAttributeListeners.get(i));
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Handle a runnable in this context
+     */
+    public void handle(Runnable runnable)
+    {
+        ClassLoader old_classloader = null;
+        Thread current_thread = null;
+        Context old_context = null;
+        try
+        {
+            old_context = __context.get();
+            __context.set(_scontext);
+
+            // Set the classloader
+            if (_classLoader != null)
+            {
+                current_thread = Thread.currentThread();
+                old_classloader = current_thread.getContextClassLoader();
+                current_thread.setContextClassLoader(_classLoader);
+            }
+
+            runnable.run();
+        }
+        finally
+        {
+            __context.set(old_context);
+            if (old_classloader != null && current_thread!=null)
+            {
+                current_thread.setContextClassLoader(old_classloader);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Check the target. Called by {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} when a target within a context is determined. If
+     * the target is protected, 404 is returned.
+     */
+    /* ------------------------------------------------------------ */
+    public boolean isProtectedTarget(String target)
+    {
+        if (target == null || _protectedTargets == null)
+            return false;
+
+        while (target.startsWith("//"))
+            target=URIUtil.compactPath(target);
+
+        for (int i=0; i<_protectedTargets.length; i++)
+        {
+            String t=_protectedTargets[i];
+            if (StringUtil.startsWithIgnoreCase(target,t))
+            {
+                if (target.length()==t.length())
+                    return true;
+                
+                // Check that the target prefix really is a path segment, thus
+                // it can end with /, a query, a target or a parameter
+                char c=target.charAt(t.length());
+                if (c=='/'||c=='?'||c=='#'||c==';')
+                    return true;
+            }
+        }
+        return false;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param targets Array of URL prefix. Each prefix is in the form /path and will match
+     * either /path exactly or /path/anything
+     */
+    public void setProtectedTargets (String[] targets)
+    {
+        if (targets == null)
+        {
+            _protectedTargets = null;
+            return;
+        }
+        
+        _protectedTargets = Arrays.copyOf(targets, targets.length);
+    }
+
+    /* ------------------------------------------------------------ */
+    public String[] getProtectedTargets()
+    {
+        if (_protectedTargets == null)
+            return null;
+
+        return Arrays.copyOf(_protectedTargets, _protectedTargets.length);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
+     */
+    @Override
+    public void removeAttribute(String name)
+    {
+        checkManagedAttribute(name,null);
+        _attributes.removeAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Set a context attribute. Attributes set via this API cannot be overriden by the ServletContext.setAttribute API. Their lifecycle spans the stop/start of
+     * a context. No attribute listener events are triggered by this API.
+     *
+     * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
+     */
+    @Override
+    public void setAttribute( String name, Object value)
+    {
+        checkManagedAttribute(name,value);
+        _attributes.setAttribute(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param attributes
+     *            The attributes to set.
+     */
+    public void setAttributes(Attributes attributes)
+    {
+        _attributes.clearAttributes();
+        _attributes.addAll(attributes);
+        Enumeration<String> e = _attributes.getAttributeNames();
+        while (e.hasMoreElements())
+        {
+            String name = e.nextElement();
+            checkManagedAttribute(name,attributes.getAttribute(name));
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void clearAttributes()
+    {
+        Enumeration<String> e = _attributes.getAttributeNames();
+        while (e.hasMoreElements())
+        {
+            String name = e.nextElement();
+            checkManagedAttribute(name,null);
+        }
+        _attributes.clearAttributes();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void checkManagedAttribute(String name, Object value)
+    {
+        if (_managedAttributes != null && _managedAttributes.containsKey(name))
+        {
+            setManagedAttribute(name,value);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setManagedAttribute(String name, Object value)
+    {
+        Object old = _managedAttributes.put(name,value);
+        updateBean(old,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param classLoader
+     *            The classLoader to set.
+     */
+    public void setClassLoader(ClassLoader classLoader)
+    {
+        _classLoader = classLoader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param contextPath
+     *            The _contextPath to set.
+     */
+    public void setContextPath(String contextPath)
+    {
+        if (contextPath == null)
+            throw new IllegalArgumentException("null contextPath");
+
+        if (contextPath.endsWith("/*"))
+        {
+            LOG.warn(this+" contextPath ends with /*");
+            contextPath=contextPath.substring(0,contextPath.length()-2);
+        }
+        else if (contextPath.length()>1 && contextPath.endsWith("/"))
+        {
+            LOG.warn(this+" contextPath ends with /");
+            contextPath=contextPath.substring(0,contextPath.length()-1);
+        }
+
+        if (contextPath.length()==0)
+        {
+            LOG.warn("Empty contextPath");
+            contextPath="/";
+        }
+
+        _contextPath = contextPath;
+
+        if (getServer() != null && (getServer().isStarting() || getServer().isStarted()))
+        {
+            Handler[] contextCollections = getServer().getChildHandlersByClass(ContextHandlerCollection.class);
+            for (int h = 0; contextCollections != null && h < contextCollections.length; h++)
+                ((ContextHandlerCollection)contextCollections[h]).mapContexts();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletContextName
+     *            The servletContextName to set.
+     */
+    public void setDisplayName(String servletContextName)
+    {
+        _displayName = servletContextName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the resourceBase.
+     */
+    public Resource getBaseResource()
+    {
+        if (_baseResource == null)
+            return null;
+        return _baseResource;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the base resource as a string.
+     */
+    @ManagedAttribute("document root for context")
+    public String getResourceBase()
+    {
+        if (_baseResource == null)
+            return null;
+        return _baseResource.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the base resource for this context.
+     * @param base The resource used as the base for all static content of this context.
+     * @see #setResourceBase(String)
+     */
+    public void setBaseResource(Resource base)
+    {
+        _baseResource = base;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * Set the base resource for this context.
+     * @param resourceBase A string representing the base resource for the context. Any string accepted 
+     * by {@link Resource#newResource(String)} may be passed and the call is equivalent to 
+     * <code>setBaseResource(newResource(resourceBase));</code>
+     */
+    public void setResourceBase(String resourceBase)
+    {
+        try
+        {
+            setBaseResource(newResource(resourceBase));
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+            throw new IllegalArgumentException(resourceBase);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the mimeTypes.
+     */
+    public MimeTypes getMimeTypes()
+    {
+        if (_mimeTypes == null)
+            _mimeTypes = new MimeTypes();
+        return _mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param mimeTypes
+     *            The mimeTypes to set.
+     */
+    public void setMimeTypes(MimeTypes mimeTypes)
+    {
+        _mimeTypes = mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public void setWelcomeFiles(String[] files)
+    {
+        _welcomeFiles = files;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The names of the files which the server should consider to be welcome files in this context.
+     * @see <a href="http://jcp.org/aboutJava/communityprocess/final/jsr154/index.html">The Servlet Specification</a>
+     * @see #setWelcomeFiles
+     */
+    @ManagedAttribute(value="Partial URIs of directory welcome files", readonly=true)
+    public String[] getWelcomeFiles()
+    {
+        return _welcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the errorHandler.
+     */
+    @ManagedAttribute("The error handler to use for the context")
+    public ErrorHandler getErrorHandler()
+    {
+        return _errorHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param errorHandler
+     *            The errorHandler to set.
+     */
+    public void setErrorHandler(ErrorHandler errorHandler)
+    {
+        if (errorHandler != null)
+            errorHandler.setServer(getServer());
+        updateBean(_errorHandler,errorHandler);
+        _errorHandler = errorHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("The maximum content size")
+    public int getMaxFormContentSize()
+    {
+        return _maxFormContentSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the maximum size of a form post, to protect against DOS attacks from large forms.
+     * @param maxSize
+     */
+    public void setMaxFormContentSize(int maxSize)
+    {
+        _maxFormContentSize = maxSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getMaxFormKeys()
+    {
+        return _maxFormKeys;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the maximum number of form Keys to protect against DOS attack from crafted hash keys.
+     * @param max
+     */
+    public void setMaxFormKeys(int max)
+    {
+        _maxFormKeys = max;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if URLs are compacted to replace multiple '/'s with a single '/'
+     */
+    public boolean isCompactPath()
+    {
+        return _compactPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param compactPath
+     *            True if URLs are compacted to replace multiple '/'s with a single '/'
+     */
+    public void setCompactPath(boolean compactPath)
+    {
+        _compactPath = compactPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        String[] vhosts = getVirtualHosts();
+
+        StringBuilder b = new StringBuilder();
+
+        Package pkg = getClass().getPackage();
+        if (pkg != null)
+        {
+            String p = pkg.getName();
+            if (p != null && p.length() > 0)
+            {
+                String[] ss = p.split("\\.");
+                for (String s : ss)
+                    b.append(s.charAt(0)).append('.');
+            }
+        }
+        b.append(getClass().getSimpleName()).append('@').append(Integer.toString(hashCode(),16));
+        b.append('{').append(getContextPath()).append(',').append(getBaseResource()).append(',').append(_availability);
+
+        if (vhosts != null && vhosts.length > 0)
+            b.append(',').append(vhosts[0]);
+        b.append('}');
+
+        return b.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized Class<?> loadClass(String className) throws ClassNotFoundException
+    {
+        if (className == null)
+            return null;
+
+        if (_classLoader == null)
+            return Loader.loadClass(this.getClass(),className);
+
+        return _classLoader.loadClass(className);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addLocaleEncoding(String locale, String encoding)
+    {
+        if (_localeEncodingMap == null)
+            _localeEncodingMap = new HashMap<String, String>();
+        _localeEncodingMap.put(locale,encoding);
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getLocaleEncoding(String locale)
+    {
+        if (_localeEncodingMap == null)
+            return null;
+        String encoding = _localeEncodingMap.get(locale);
+        return encoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the character encoding for a locale. The full locale name is first looked up in the map of encodings. If no encoding is found, then the locale
+     * language is looked up.
+     *
+     * @param locale
+     *            a <code>Locale</code> value
+     * @return a <code>String</code> representing the character encoding for the locale or null if none found.
+     */
+    public String getLocaleEncoding(Locale locale)
+    {
+        if (_localeEncodingMap == null)
+            return null;
+        String encoding = _localeEncodingMap.get(locale.toString());
+        if (encoding == null)
+            encoding = _localeEncodingMap.get(locale.getLanguage());
+        return encoding;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * Get all of the locale encodings
+     * 
+     * @return a map of all the locale encodings: key is name of the locale and value is the char encoding
+     */
+    public Map<String,String> getLocaleEncodings()
+    {
+        if (_localeEncodingMap == null)
+            return null;
+        return Collections.unmodifiableMap(_localeEncodingMap);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public Resource getResource(String path) throws MalformedURLException
+    {
+        if (path == null || !path.startsWith(URIUtil.SLASH))
+            throw new MalformedURLException(path);
+
+        if (_baseResource == null)
+            return null;
+
+        try
+        {
+            path = URIUtil.canonicalPath(path);
+            Resource resource = _baseResource.addPath(path);
+            
+            if (checkAlias(path,resource))
+                return resource;
+            return null;
+        }
+        catch (Exception e)
+        {
+            LOG.ignore(e);
+        }
+
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean checkAlias(String path, Resource resource)
+    {
+        // Is the resource aliased?
+            if (resource.getAlias() != null)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias());
+
+            // alias checks
+            for (Iterator<AliasCheck> i=_aliasChecks.iterator();i.hasNext();)
+            {
+                AliasCheck check = i.next();
+                if (check.check(path,resource))
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Aliased resource: " + resource + " approved by " + check);
+                    return true;
+                }
+            }
+            return false;
+        }
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations.
+     */
+    public Resource newResource(URL url) throws IOException
+    {
+        return Resource.newResource(url);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations.
+     */
+    public Resource newResource(URI uri) throws IOException
+    {
+        return Resource.newResource(uri);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Convert a URL or path to a Resource. The default implementation is a wrapper for {@link Resource#newResource(String)}.
+     *
+     * @param urlOrPath
+     *            The URL or path to convert
+     * @return The Resource for the URL/path
+     * @throws IOException
+     *             The Resource could not be created.
+     */
+    public Resource newResource(String urlOrPath) throws IOException
+    {
+        return Resource.newResource(urlOrPath);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public Set<String> getResourcePaths(String path)
+    {
+        try
+        {
+            path = URIUtil.canonicalPath(path);
+            Resource resource = getResource(path);
+
+            if (resource != null && resource.exists())
+            {
+                if (!path.endsWith(URIUtil.SLASH))
+                    path = path + URIUtil.SLASH;
+
+                String[] l = resource.list();
+                if (l != null)
+                {
+                    HashSet<String> set = new HashSet<String>();
+                    for (int i = 0; i < l.length; i++)
+                        set.add(path + l[i]);
+                    return set;
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.ignore(e);
+        }
+        return Collections.emptySet();
+    }
+
+    /* ------------------------------------------------------------ */
+    private String normalizeHostname(String host)
+    {
+        if (host == null)
+            return null;
+
+        if (host.endsWith("."))
+            return host.substring(0,host.length() - 1);
+
+        return host;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add an AliasCheck instance to possibly permit aliased resources
+     * @param check The alias checker
+     */
+    public void addAliasCheck(AliasCheck check)
+    {
+        _aliasChecks.add(check);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Mutable list of Alias checks
+     */
+    public List<AliasCheck> getAliasChecks()
+    {
+        return _aliasChecks;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param checks list of AliasCheck instances
+     */
+    public void setAliasChecks(List<AliasCheck> checks)
+    {
+        _aliasChecks.clear();
+        _aliasChecks.addAll(checks);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Context.
+     * <p>
+     * A partial implementation of {@link javax.servlet.ServletContext}. A complete implementation is provided by the derived {@link ContextHandler}.
+     * </p>
+     *
+     *
+     */
+    public class Context extends NoContext
+    {
+        protected boolean _enabled = true; //whether or not the dynamic API is enabled for callers
+        protected boolean _extendedListenerTypes = false;
+
+
+        /* ------------------------------------------------------------ */
+        protected Context()
+        {
+        }
+
+        /* ------------------------------------------------------------ */
+        public ContextHandler getContextHandler()
+        {
+            return ContextHandler.this;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getContext(java.lang.String)
+         */
+        @Override
+        public ServletContext getContext(String uripath)
+        {
+            List<ContextHandler> contexts = new ArrayList<ContextHandler>();
+            Handler[] handlers = getServer().getChildHandlersByClass(ContextHandler.class);
+            String matched_path = null;
+
+            for (Handler handler : handlers)
+            {
+                if (handler == null)
+                    continue;
+                ContextHandler ch = (ContextHandler)handler;
+                String context_path = ch.getContextPath();
+
+                if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
+                        || "/".equals(context_path))
+                {
+                    // look first for vhost matching context only
+                    if (getVirtualHosts() != null && getVirtualHosts().length > 0)
+                    {
+                        if (ch.getVirtualHosts() != null && ch.getVirtualHosts().length > 0)
+                        {
+                            for (String h1 : getVirtualHosts())
+                                for (String h2 : ch.getVirtualHosts())
+                                    if (h1.equals(h2))
+                                    {
+                                        if (matched_path == null || context_path.length() > matched_path.length())
+                                        {
+                                            contexts.clear();
+                                            matched_path = context_path;
+                                        }
+
+                                        if (matched_path.equals(context_path))
+                                            contexts.add(ch);
+                                    }
+                        }
+                    }
+                    else
+                    {
+                        if (matched_path == null || context_path.length() > matched_path.length())
+                        {
+                            contexts.clear();
+                            matched_path = context_path;
+                        }
+
+                        if (matched_path.equals(context_path))
+                            contexts.add(ch);
+                    }
+                }
+            }
+
+            if (contexts.size() > 0)
+                return contexts.get(0)._scontext;
+
+            // try again ignoring virtual hosts
+            matched_path = null;
+            for (Handler handler : handlers)
+            {
+                if (handler == null)
+                    continue;
+                ContextHandler ch = (ContextHandler)handler;
+                String context_path = ch.getContextPath();
+
+                if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
+                        || "/".equals(context_path))
+                {
+                    if (matched_path == null || context_path.length() > matched_path.length())
+                    {
+                        contexts.clear();
+                        matched_path = context_path;
+                    }
+
+                    if (matched_path.equals(context_path))
+                        contexts.add(ch);
+                }
+            }
+
+            if (contexts.size() > 0)
+                return contexts.get(0)._scontext;
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getMimeType(java.lang.String)
+         */
+        @Override
+        public String getMimeType(String file)
+        {
+            if (_mimeTypes == null)
+                return null;
+            return _mimeTypes.getMimeByExtension(file);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String)
+         */
+        @Override
+        public RequestDispatcher getRequestDispatcher(String uriInContext)
+        {
+            if (uriInContext == null)
+                return null;
+
+            if (!uriInContext.startsWith("/"))
+                return null;
+
+            try
+            {
+                String query = null;
+                int q = 0;
+                if ((q = uriInContext.indexOf('?')) > 0)
+                {
+                    query = uriInContext.substring(q + 1);
+                    uriInContext = uriInContext.substring(0,q);
+                }
+
+                String pathInContext = URIUtil.canonicalPath(URIUtil.decodePath(uriInContext));
+                if (pathInContext!=null)
+                {
+                    String uri = URIUtil.addPaths(getContextPath(),uriInContext);
+                    ContextHandler context = ContextHandler.this;
+                    return new Dispatcher(context,uri,pathInContext,query);
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.ignore(e);
+            }
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getRealPath(java.lang.String)
+         */
+        @Override
+        public String getRealPath(String path)
+        {
+            if (path == null)
+                return null;
+            if (path.length() == 0)
+                path = URIUtil.SLASH;
+            else if (path.charAt(0) != '/')
+                path = URIUtil.SLASH + path;
+
+            try
+            {
+                Resource resource = ContextHandler.this.getResource(path);
+                if (resource != null)
+                {
+                    File file = resource.getFile();
+                    if (file != null)
+                        return file.getCanonicalPath();
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.ignore(e);
+            }
+
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public URL getResource(String path) throws MalformedURLException
+        {
+            Resource resource = ContextHandler.this.getResource(path);
+            if (resource != null && resource.exists())
+                return resource.getURL();
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String)
+         */
+        @Override
+        public InputStream getResourceAsStream(String path)
+        {
+            try
+            {
+                URL url = getResource(path);
+                if (url == null)
+                    return null;
+                Resource r = Resource.newResource(url);
+                return r.getInputStream();
+            }
+            catch (Exception e)
+            {
+                LOG.ignore(e);
+                return null;
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String)
+         */
+        @Override
+        public Set<String> getResourcePaths(String path)
+        {
+            return ContextHandler.this.getResourcePaths(path);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#log(java.lang.Exception, java.lang.String)
+         */
+        @Override
+        public void log(Exception exception, String msg)
+        {
+            _logger.warn(msg,exception);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#log(java.lang.String)
+         */
+        @Override
+        public void log(String msg)
+        {
+            _logger.info(msg);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#log(java.lang.String, java.lang.Throwable)
+         */
+        @Override
+        public void log(String message, Throwable throwable)
+        {
+            _logger.warn(message,throwable);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
+         */
+        @Override
+        public String getInitParameter(String name)
+        {
+            return ContextHandler.this.getInitParameter(name);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getInitParameterNames()
+         */
+        @SuppressWarnings("unchecked")
+        @Override
+        public Enumeration<String> getInitParameterNames()
+        {
+            return ContextHandler.this.getInitParameterNames();
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
+         */
+        @Override
+        public synchronized Object getAttribute(String name)
+        {
+            Object o = ContextHandler.this.getAttribute(name);
+            if (o == null)
+                o = super.getAttribute(name);
+            return o;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getAttributeNames()
+         */
+        @Override
+        public synchronized Enumeration<String> getAttributeNames()
+        {
+            HashSet<String> set = new HashSet<String>();
+            Enumeration<String> e = super.getAttributeNames();
+            while (e.hasMoreElements())
+                set.add(e.nextElement());
+            e = _attributes.getAttributeNames();
+            while (e.hasMoreElements())
+                set.add(e.nextElement());
+
+            return Collections.enumeration(set);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
+         */
+        @Override
+        public synchronized void setAttribute(String name, Object value)
+        {
+            checkManagedAttribute(name,value);
+            Object old_value = super.getAttribute(name);
+
+            if (value == null)
+                super.removeAttribute(name);
+            else
+                super.setAttribute(name,value);
+
+            if (!_contextAttributeListeners.isEmpty())
+            {
+                ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value == null?value:old_value);
+
+                for (ServletContextAttributeListener l : _contextAttributeListeners)
+                {
+                    if (old_value == null)
+                        l.attributeAdded(event);
+                    else if (value == null)
+                        l.attributeRemoved(event);
+                    else
+                        l.attributeReplaced(event);
+                }
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
+         */
+        @Override
+        public synchronized void removeAttribute(String name)
+        {
+            checkManagedAttribute(name,null);
+
+            Object old_value = super.getAttribute(name);
+            super.removeAttribute(name);
+            if (old_value != null &&!_contextAttributeListeners.isEmpty())
+            {
+                ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value);
+
+                for (ServletContextAttributeListener l : _contextAttributeListeners)
+                    l.attributeRemoved(event);
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getServletContextName()
+         */
+        @Override
+        public String getServletContextName()
+        {
+            String name = ContextHandler.this.getDisplayName();
+            if (name == null)
+                name = ContextHandler.this.getContextPath();
+            return name;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String getContextPath()
+        {
+            if ((_contextPath != null) && _contextPath.equals(URIUtil.SLASH))
+                return "";
+
+            return _contextPath;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString()
+        {
+            return "ServletContext@" + ContextHandler.this.toString();
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public boolean setInitParameter(String name, String value)
+        {
+            if (ContextHandler.this.getInitParameter(name) != null)
+                return false;
+            ContextHandler.this.getInitParams().put(name,value);
+            return true;
+        }
+
+        @Override
+        public void addListener(String className)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            try
+            {
+                Class<? extends EventListener> clazz = _classLoader==null?Loader.loadClass(ContextHandler.class,className):_classLoader.loadClass(className);
+                addListener(clazz);
+            }
+            catch (ClassNotFoundException e)
+            {
+                throw new IllegalArgumentException(e);
+            }
+        }
+
+        @Override
+        public <T extends EventListener> void addListener(T t)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            checkListener(t.getClass());
+
+            ContextHandler.this.addEventListener(t);
+            ContextHandler.this.addProgrammaticListener(t);
+        }
+
+        @Override
+        public void addListener(Class<? extends EventListener> listenerClass)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            try
+            {
+                EventListener e = createListener(listenerClass);
+                addListener(e);
+            }
+            catch (ServletException e)
+            {
+                throw new IllegalArgumentException(e);
+            }
+        }
+
+        @Override
+        public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
+        {
+            try
+            {
+                return createInstance(clazz);
+            }
+            catch (Exception e)
+            {
+                throw new ServletException(e);
+            }
+        }
+
+
+        public void checkListener (Class<? extends EventListener> listener) throws IllegalStateException
+        {
+            boolean ok = false;
+            int startIndex = (isExtendedListenerTypes()?EXTENDED_LISTENER_TYPE_INDEX:DEFAULT_LISTENER_TYPE_INDEX);
+            for (int i=startIndex;i<SERVLET_LISTENER_TYPES.length;i++)
+            {
+                if (SERVLET_LISTENER_TYPES[i].isAssignableFrom(listener))
+                {
+                    ok = true;
+                    break;
+                }
+            }
+            if (!ok)
+                throw new IllegalArgumentException("Inappropriate listener class "+listener.getName());
+        }
+
+        public void setExtendedListenerTypes (boolean extended)
+        {
+           _extendedListenerTypes = extended;
+        }
+
+       public boolean isExtendedListenerTypes()
+       {
+           return _extendedListenerTypes;
+       }
+
+
+       @Override
+       public ClassLoader getClassLoader()
+       {
+           if (!_enabled)
+               throw new UnsupportedOperationException();
+           
+           //no security manager just return the classloader
+           if (System.getSecurityManager() == null)
+               return _classLoader;
+           else
+           {
+               //check to see if the classloader of the caller is the same as the context
+               //classloader, or a parent of it
+               try
+               {
+                   Class reflect = Loader.loadClass(getClass(), "sun.reflect.Reflection");
+                   Method getCallerClass = reflect.getMethod("getCallerClass", Integer.TYPE);
+                   Class caller = (Class)getCallerClass.invoke(null, 2);
+
+                   boolean ok = false;
+                   ClassLoader callerLoader = caller.getClassLoader();
+                   while (!ok && callerLoader != null)
+                   {
+                       if (callerLoader == _classLoader) 
+                           ok = true;
+                       else
+                           callerLoader = callerLoader.getParent();    
+                   }
+
+                   if (ok)
+                       return _classLoader;
+               }
+               catch (Exception e)      
+               {
+                   LOG.warn("Unable to check classloader of caller",e);
+               }
+              
+               AccessController.checkPermission(new RuntimePermission("getClassLoader"));
+               return _classLoader;
+           }
+        }
+
+        @Override
+        public JspConfigDescriptor getJspConfigDescriptor()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        public void setJspConfigDescriptor(JspConfigDescriptor d)
+        {
+
+        }
+
+        @Override
+        public void declareRoles(String... roleNames)
+        {
+            if (!isStarting())
+                throw new IllegalStateException ();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+        }
+
+        public void setEnabled(boolean enabled)
+        {
+            _enabled = enabled;
+        }
+
+        public boolean isEnabled()
+        {
+            return _enabled;
+        }
+
+
+
+        public <T> T createInstance (Class<T> clazz) throws Exception
+        {
+            T o = clazz.newInstance();
+            return o;
+        }
+    }
+
+
+    public static class NoContext extends AttributesMap implements ServletContext
+    {
+        private int _effectiveMajorVersion = SERVLET_MAJOR_VERSION;
+        private int _effectiveMinorVersion = SERVLET_MINOR_VERSION;
+
+        /* ------------------------------------------------------------ */
+        public NoContext()
+        {
+        }
+
+        @Override
+        public ServletContext getContext(String uripath)
+        {
+            return null;
+        }
+
+        @Override
+        public int getMajorVersion()
+        {
+            return SERVLET_MAJOR_VERSION;
+        }
+
+        @Override
+        public String getMimeType(String file)
+        {
+            return null;
+        }
+
+        @Override
+        public int getMinorVersion()
+        {
+            return SERVLET_MINOR_VERSION;
+        }
+
+        @Override
+        public RequestDispatcher getNamedDispatcher(String name)
+        {
+            return null;
+        }
+
+        @Override
+        public RequestDispatcher getRequestDispatcher(String uriInContext)
+        {
+            return null;
+        }
+
+        @Override
+        public String getRealPath(String path)
+        {
+            return null;
+        }
+
+        @Override
+        public URL getResource(String path) throws MalformedURLException
+        {
+            return null;
+        }
+
+        @Override
+        public InputStream getResourceAsStream(String path)
+        {
+            return null;
+        }
+
+        @Override
+        public Set<String> getResourcePaths(String path)
+        {
+            return null;
+        }
+
+        @Override
+        public String getServerInfo()
+        {
+            return "jetty/" + Server.getVersion();
+        }
+
+        @Override
+        @Deprecated
+        public Servlet getServlet(String name) throws ServletException
+        {
+            return null;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        @Deprecated
+        public Enumeration<String> getServletNames()
+        {
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        @Deprecated
+        public Enumeration<Servlet> getServlets()
+        {
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        }
+
+        @Override
+        public void log(Exception exception, String msg)
+        {
+            LOG.warn(msg,exception);
+        }
+
+        @Override
+        public void log(String msg)
+        {
+            LOG.info(msg);
+        }
+
+        @Override
+        public void log(String message, Throwable throwable)
+        {
+            LOG.warn(message,throwable);
+        }
+
+        @Override
+        public String getInitParameter(String name)
+        {
+            return null;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public Enumeration<String> getInitParameterNames()
+        {
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        }
+
+
+        @Override
+        public String getServletContextName()
+        {
+            return "No Context";
+        }
+
+        @Override
+        public String getContextPath()
+        {
+            return null;
+        }
+
+
+        @Override
+        public boolean setInitParameter(String name, String value)
+        {
+            return false;
+        }
+
+        @Override
+        public Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Dynamic addFilter(String filterName, Filter filter)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Dynamic addFilter(String filterName, String className)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String className)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public <T extends Filter> T createFilter(Class<T> c) throws ServletException
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public <T extends Servlet> T createServlet(Class<T> c) throws ServletException
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public FilterRegistration getFilterRegistration(String filterName)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Map<String, ? extends FilterRegistration> getFilterRegistrations()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public ServletRegistration getServletRegistration(String servletName)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Map<String, ? extends ServletRegistration> getServletRegistrations()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public SessionCookieConfig getSessionCookieConfig()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
+        {
+            LOG.warn(__unimplmented);
+        }
+
+        @Override
+        public void addListener(String className)
+        {
+            LOG.warn(__unimplmented);
+        }
+
+        @Override
+        public <T extends EventListener> void addListener(T t)
+        {
+            LOG.warn(__unimplmented);
+        }
+
+        @Override
+        public void addListener(Class<? extends EventListener> listenerClass)
+        {
+            LOG.warn(__unimplmented);
+        }
+
+        @Override
+        public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
+        {
+            try
+            {
+                return clazz.newInstance();
+            }
+            catch (InstantiationException e)
+            {
+                throw new ServletException(e);
+            }
+            catch (IllegalAccessException e)
+            {
+                throw new ServletException(e);
+            }
+        }
+
+        @Override
+        public ClassLoader getClassLoader()
+        {
+            AccessController.checkPermission(new RuntimePermission("getClassLoader"));
+            return ContextHandler.class.getClassLoader();
+        }
+
+        @Override
+        public int getEffectiveMajorVersion()
+        {
+            return _effectiveMajorVersion;
+        }
+
+        @Override
+        public int getEffectiveMinorVersion()
+        {
+            return _effectiveMinorVersion;
+        }
+
+        public void setEffectiveMajorVersion (int v)
+        {
+            _effectiveMajorVersion = v;
+        }
+
+        public void setEffectiveMinorVersion (int v)
+        {
+            _effectiveMinorVersion = v;
+        }
+
+        @Override
+        public JspConfigDescriptor getJspConfigDescriptor()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public void declareRoles(String... roleNames)
+        {
+            LOG.warn(__unimplmented);
+        }
+
+        /**
+         * @see javax.servlet.ServletContext#getVirtualServerName()
+         */
+        @Override
+        public String getVirtualServerName()
+        {
+            // TODO 3.1 Auto-generated method stub
+            return null;
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Interface to check aliases
+     */
+    public interface AliasCheck
+    {
+        /* ------------------------------------------------------------ */
+        /** Check an alias
+         * @param path The path the aliased resource was created for
+         * @param resource The aliased resourced
+         * @return True if the resource is OK to be served.
+         */
+        boolean check(String path, Resource resource);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Approve all aliases.
+     */
+    public static class ApproveAliases implements AliasCheck
+    {
+        @Override
+        public boolean check(String path, Resource resource)
+        {
+            return true;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Approve Aliases with same suffix.
+     * Eg. a symbolic link from /foobar.html to /somewhere/wibble.html would be
+     * approved because both the resource and alias end with ".html".
+     */
+    @Deprecated
+    public static class ApproveSameSuffixAliases implements AliasCheck
+    {
+        {
+            LOG.warn("ApproveSameSuffixAlias is not safe for production");
+        }
+        
+        @Override
+        public boolean check(String path, Resource resource)
+        {
+            int dot = path.lastIndexOf('.');
+            if (dot<0)
+                return false;
+            String suffix=path.substring(dot);
+            return resource.toString().endsWith(suffix);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Approve Aliases with a path prefix.
+     * Eg. a symbolic link from /dirA/foobar.html to /dirB/foobar.html would be
+     * approved because both the resource and alias end with "/foobar.html".
+     */
+    @Deprecated
+    public static class ApprovePathPrefixAliases implements AliasCheck
+    {
+        {
+            LOG.warn("ApprovePathPrefixAliases is not safe for production");
+        }
+        
+        @Override
+        public boolean check(String path, Resource resource)
+        {
+            int slash = path.lastIndexOf('/');
+            if (slash<0 || slash==path.length()-1)
+                return false;
+            String suffix=path.substring(slash);
+            return resource.toString().endsWith(suffix);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Approve Aliases of a non existent directory.
+     * If a directory "/foobar/" does not exist, then the resource is
+     * aliased to "/foobar".  Accept such aliases.
+     */
+    public static class ApproveNonExistentDirectoryAliases implements AliasCheck
+    {
+        @Override
+        public boolean check(String path, Resource resource)
+        {
+            if (resource.exists())
+                return false;
+            
+            String a=resource.getAlias().toString();
+            String r=resource.getURL().toString();
+            
+            if (a.length()>r.length())
+                return a.startsWith(r) && a.length()==r.length()+1 && a.endsWith("/");
+            if (a.length()<r.length())
+                return r.startsWith(a) && r.length()==a.length()+1 && r.endsWith("/");
+            
+            return a.equals(r); 
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/ContextHandlerCollection.java b/lib/jetty/org/eclipse/jetty/server/handler/ContextHandlerCollection.java
new file mode 100644 (file)
index 0000000..4ade364
--- /dev/null
@@ -0,0 +1,267 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.HttpChannelState;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** ContextHandlerCollection.
+ *
+ * This {@link org.eclipse.jetty.server.handler.HandlerCollection} is creates a
+ * {@link org.eclipse.jetty.http.PathMap} to it's contained handlers based
+ * on the context path and virtual hosts of any contained {@link org.eclipse.jetty.server.handler.ContextHandler}s.
+ * The contexts do not need to be directly contained, only children of the contained handlers.
+ * Multiple contexts may have the same context path and they are called in order until one
+ * handles the request.
+ *
+ */
+@ManagedObject("Context Handler Collection")
+public class ContextHandlerCollection extends HandlerCollection
+{
+    private static final Logger LOG = Log.getLogger(ContextHandlerCollection.class);
+
+    private volatile Trie<ContextHandler[]> _contexts;
+    private Class<? extends ContextHandler> _contextClass = ContextHandler.class;
+
+    /* ------------------------------------------------------------ */
+    public ContextHandlerCollection()
+    {
+        super(true);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Remap the context paths.
+     */
+    @ManagedOperation("update the mapping of context path to context")
+    public void mapContexts()
+    {
+        int capacity=512;
+        
+        // Loop until we have a big enough trie to hold all the context paths
+        Trie<ContextHandler[]> trie;
+        loop: while(true)
+        {
+            trie=new ArrayTernaryTrie<>(false,capacity);
+
+            Handler[] branches = getHandlers();
+
+            // loop over each group of contexts
+            for (int b=0;branches!=null && b<branches.length;b++)
+            {
+                Handler[] handlers=null;
+
+                if (branches[b] instanceof ContextHandler)
+                {
+                    handlers = new Handler[]{ branches[b] };
+                }
+                else if (branches[b] instanceof HandlerContainer)
+                {
+                    handlers = ((HandlerContainer)branches[b]).getChildHandlersByClass(ContextHandler.class);
+                }
+                else
+                    continue;
+
+                // for each context handler in a group
+                for (int i=0;handlers!=null && i<handlers.length;i++)
+                {
+                    ContextHandler handler=(ContextHandler)handlers[i];
+                    String contextPath=handler.getContextPath().substring(1);
+                    ContextHandler[] contexts=trie.get(contextPath);
+                    
+                    if (!trie.put(contextPath,ArrayUtil.addToArray(contexts,handler,ContextHandler.class)))
+                    {
+                        capacity+=512;
+                        continue loop;
+                    }
+                }
+            }
+            
+            break;
+        }
+        
+        // Sort the contexts so those with virtual hosts are considered before those without
+        for (String ctx : trie.keySet())
+        {
+            ContextHandler[] contexts=trie.get(ctx);
+            ContextHandler[] sorted=new ContextHandler[contexts.length];
+            int i=0;
+            for (ContextHandler handler:contexts)
+                if (handler.getVirtualHosts()!=null && handler.getVirtualHosts().length>0)
+                    sorted[i++]=handler;
+            for (ContextHandler handler:contexts)
+                if (handler.getVirtualHosts()==null || handler.getVirtualHosts().length==0)
+                    sorted[i++]=handler;
+            trie.put(ctx,sorted);
+        }
+
+        //for (String ctx : trie.keySet())
+        //    System.err.printf("'%s'->%s%n",ctx,Arrays.asList(trie.get(ctx)));
+        _contexts=trie;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.server.handler.HandlerCollection#setHandlers(org.eclipse.jetty.server.server.Handler[])
+     */
+    @Override
+    public void setHandlers(Handler[] handlers)
+    {
+        super.setHandlers(handlers);
+        if (isStarted())
+            mapContexts();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        mapContexts();
+        super.doStart();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        Handler[] handlers = getHandlers();
+        if (handlers==null || handlers.length==0)
+           return;
+
+       HttpChannelState async = baseRequest.getHttpChannelState();
+       if (async.isAsync())
+       {
+           ContextHandler context=async.getContextHandler();
+           if (context!=null)
+           {
+               context.handle(target,baseRequest,request, response);
+               return;
+           }
+       }
+
+       // data structure which maps a request to a context; first-best match wins
+       // { context path => [ context ] }
+       // }
+       if (target.startsWith("/"))
+       {
+           int limit = target.length()-1;
+
+           while (limit>=0)
+           {
+               // Get best match
+               ContextHandler[] contexts = _contexts.getBest(target,1,limit);
+               if (contexts==null)
+                   break;
+
+               int l=contexts[0].getContextPath().length();
+               if (l==1 || target.length()==l || target.charAt(l)=='/')
+               {
+                   for (ContextHandler handler : contexts)
+                   {
+                       handler.handle(target,baseRequest, request, response);
+                       if (baseRequest.isHandled())
+                           return;
+                   }
+               }
+               
+               limit=l-2;
+           }
+       }
+       else
+       {
+            // This may not work in all circumstances... but then I think it should never be called
+           for (int i=0;i<handlers.length;i++)
+           {
+               handlers[i].handle(target,baseRequest, request, response);
+               if ( baseRequest.isHandled())
+                   return;
+           }
+       }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Add a context handler.
+     * @param contextPath  The context path to add
+     * @return the ContextHandler just added
+     */
+    public ContextHandler addContext(String contextPath,String resourceBase)
+    {
+        try
+        {
+            ContextHandler context = _contextClass.newInstance();
+            context.setContextPath(contextPath);
+            context.setResourceBase(resourceBase);
+            addHandler(context);
+            return context;
+        }
+        catch (Exception e)
+        {
+            LOG.debug(e);
+            throw new Error(e);
+        }
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The class to use to add new Contexts
+     */
+    public Class<?> getContextClass()
+    {
+        return _contextClass;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param contextClass The class to use to add new Contexts
+     */
+    public void setContextClass(Class<? extends ContextHandler> contextClass)
+    {
+        if (contextClass ==null || !(ContextHandler.class.isAssignableFrom(contextClass)))
+            throw new IllegalArgumentException();
+        _contextClass = contextClass;
+    }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/DebugHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/DebugHandler.java
new file mode 100644 (file)
index 0000000..0ed7261
--- /dev/null
@@ -0,0 +1,184 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Locale;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.server.AbstractConnector;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.RolloverFileOutputStream;
+
+
+/**
+ * Debug Handler.
+ * A lightweight debug handler that can be used in production code.
+ * Details of the request and response are written to an output stream
+ * and the current thread name is updated with information that will link
+ * to the details in that output.
+ */
+public class DebugHandler extends HandlerWrapper implements Connection.Listener
+{
+    private DateCache _date=new DateCache("HH:mm:ss", Locale.US);
+    private OutputStream _out;
+    private PrintStream _print;
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+            throws IOException, ServletException
+    {
+        final Response base_response = baseRequest.getResponse();
+        final Thread thread=Thread.currentThread();
+        final String old_name=thread.getName();
+
+        boolean suspend=false;
+        boolean retry=false;
+        String name=(String)request.getAttribute("org.eclipse.jetty.thread.name");
+        if (name==null)
+            name=old_name+":"+baseRequest.getScheme()+"://"+baseRequest.getLocalAddr()+":"+baseRequest.getLocalPort()+baseRequest.getUri();
+        else
+            retry=true;
+
+        String ex=null;
+        try
+        {
+            if (retry)
+                print(name,"RESUME");
+            else
+                print(name,"REQUEST "+baseRequest.getRemoteAddr()+" "+request.getMethod()+" "+baseRequest.getHeader("Cookie")+"; "+baseRequest.getHeader("User-Agent"));
+            thread.setName(name);
+
+            getHandler().handle(target,baseRequest,request,response);
+        }
+        catch(IOException ioe)
+        {
+            ex=ioe.toString();
+            throw ioe;
+        }
+        catch(ServletException se)
+        {
+            ex=se.toString()+":"+se.getCause();
+            throw se;
+        }
+        catch(RuntimeException rte)
+        {
+            ex=rte.toString();
+            throw rte;
+        }
+        catch(Error e)
+        {
+            ex=e.toString();
+            throw e;
+        }
+        finally
+        {
+            thread.setName(old_name);
+            suspend=baseRequest.getHttpChannelState().isSuspended();
+            if (suspend)
+            {
+                request.setAttribute("org.eclipse.jetty.thread.name",name);
+                print(name,"SUSPEND");
+            }
+            else
+                print(name,"RESPONSE "+base_response.getStatus()+(ex==null?"":("/"+ex))+" "+base_response.getContentType());
+        }
+    }
+    
+    private void print(String name,String message)
+    {
+        long now=System.currentTimeMillis();
+        final String d=_date.formatNow(now);
+        final int ms=(int)(now%1000);
+
+        _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" "+message);
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (_out==null)
+            _out=new RolloverFileOutputStream("./logs/yyyy_mm_dd.debug.log",true);
+        _print=new PrintStream(_out);
+        
+        for (Connector connector : getServer().getConnectors())
+            if (connector instanceof AbstractConnector)
+                ((AbstractConnector)connector).addBean(this,false);
+            
+        super.doStart();
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        _print.close();
+        for (Connector connector : getServer().getConnectors())
+            if (connector instanceof AbstractConnector)
+                ((AbstractConnector)connector).removeBean(this);
+    }
+
+    /**
+     * @return the out
+     */
+    public OutputStream getOutputStream()
+    {
+        return _out;
+    }
+
+    /**
+     * @param out the out to set
+     */
+    public void setOutputStream(OutputStream out)
+    {
+        _out = out;
+    }
+    
+    @Override
+    public void onOpened(Connection connection)
+    {
+        print(Thread.currentThread().getName(),"OPENED "+connection.toString());
+    }
+
+    @Override
+    public void onClosed(Connection connection)
+    {
+        print(Thread.currentThread().getName(),"CLOSED "+connection.toString());
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/DefaultHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/DefaultHandler.java
new file mode 100644 (file)
index 0000000..f71720a
--- /dev/null
@@ -0,0 +1,206 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Default Handler.
+ *
+ * This handle will deal with unhandled requests in the server.
+ * For requests for favicon.ico, the Jetty icon is served.
+ * For reqests to '/' a 404 with a list of known contexts is served.
+ * For all other requests a normal 404 is served.
+ *
+ *
+ * @org.apache.xbean.XBean
+ */
+public class DefaultHandler extends AbstractHandler
+{
+    private static final Logger LOG = Log.getLogger(DefaultHandler.class);
+
+    final long _faviconModified=(System.currentTimeMillis()/1000)*1000L;
+    byte[] _favicon;
+    boolean _serveIcon=true;
+    boolean _showContexts=true;
+
+    public DefaultHandler()
+    {
+        try
+        {
+            URL fav = this.getClass().getClassLoader().getResource("org/eclipse/jetty/favicon.ico");
+            if (fav!=null)
+            {
+                Resource r = Resource.newResource(fav);
+                _favicon=IO.readBytes(r.getInputStream());
+            }
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (response.isCommitted() || baseRequest.isHandled())
+            return;
+
+        baseRequest.setHandled(true);
+
+        String method=request.getMethod();
+
+        // little cheat for common request
+        if (_serveIcon && _favicon!=null && HttpMethod.GET.is(method) && request.getRequestURI().equals("/favicon.ico"))
+        {
+            if (request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.toString())==_faviconModified)
+                response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+            else
+            {
+                response.setStatus(HttpServletResponse.SC_OK);
+                response.setContentType("image/x-icon");
+                response.setContentLength(_favicon.length);
+                response.setDateHeader(HttpHeader.LAST_MODIFIED.toString(), _faviconModified);
+                response.setHeader(HttpHeader.CACHE_CONTROL.toString(),"max-age=360000,public");
+                response.getOutputStream().write(_favicon);
+            }
+            return;
+        }
+
+
+        if (!_showContexts || !HttpMethod.GET.is(method) || !request.getRequestURI().equals("/"))
+        {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+
+        response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+        response.setContentType(MimeTypes.Type.TEXT_HTML.toString());
+
+        ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(1500);
+
+        writer.write("<HTML>\n<HEAD>\n<TITLE>Error 404 - Not Found");
+        writer.write("</TITLE>\n<BODY>\n<H2>Error 404 - Not Found.</H2>\n");
+        writer.write("No context on this server matched or handled this request.<BR>");
+        writer.write("Contexts known to this server are: <ul>");
+
+        Server server = getServer();
+        Handler[] handlers = server==null?null:server.getChildHandlersByClass(ContextHandler.class);
+
+        for (int i=0;handlers!=null && i<handlers.length;i++)
+        {
+            ContextHandler context = (ContextHandler)handlers[i];
+            if (context.isRunning())
+            {
+                writer.write("<li><a href=\"");
+                if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+                    writer.write("http://"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
+                writer.write(context.getContextPath());
+                if (context.getContextPath().length()>1 && context.getContextPath().endsWith("/"))
+                    writer.write("/");
+                writer.write("\">");
+                writer.write(context.getContextPath());
+                if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+                    writer.write("&nbsp;@&nbsp;"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
+                writer.write("&nbsp;--->&nbsp;");
+                writer.write(context.toString());
+                writer.write("</a></li>\n");
+            }
+            else
+            {
+                writer.write("<li>");
+                writer.write(context.getContextPath());
+                if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+                    writer.write("&nbsp;@&nbsp;"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
+                writer.write("&nbsp;--->&nbsp;");
+                writer.write(context.toString());
+                if (context.isFailed())
+                    writer.write(" [failed]");
+                if (context.isStopped())
+                    writer.write(" [stopped]");
+                writer.write("</li>\n");
+            }
+        }
+
+        writer.write("</ul><hr>");
+        writer.write("<a href=\"http://eclipse.org/jetty\"><img border=0 src=\"/favicon.ico\"/></a>&nbsp;");
+        writer.write("<a href=\"http://eclipse.org/jetty\">Powered by Jetty:// Java Web Server</a><hr/>\n");
+
+        writer.write("\n</BODY>\n</HTML>\n");
+        writer.flush();
+        response.setContentLength(writer.size());
+        try (OutputStream out=response.getOutputStream())
+        {
+            writer.writeTo(out);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns true if the handle can server the jetty favicon.ico
+     */
+    public boolean getServeIcon()
+    {
+        return _serveIcon;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param serveIcon true if the handle can server the jetty favicon.ico
+     */
+    public void setServeIcon(boolean serveIcon)
+    {
+        _serveIcon = serveIcon;
+    }
+
+    public boolean getShowContexts()
+    {
+        return _showContexts;
+    }
+
+    public void setShowContexts(boolean show)
+    {
+        _showContexts = show;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/ErrorHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/ErrorHandler.java
new file mode 100644 (file)
index 0000000..b1af520
--- /dev/null
@@ -0,0 +1,319 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Handler for Error pages
+ * An ErrorHandler is registered with {@link ContextHandler#setErrorHandler(ErrorHandler)} or
+ * {@link org.eclipse.jetty.server.Server#addBean(Object)}.
+ * It is called by the HttpResponse.sendError method to write a error page via {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
+ * or via {@link #badMessageError(int, String, HttpFields)} for bad requests for which a dispatch cannot be done.
+ *
+ */
+public class ErrorHandler extends AbstractHandler
+{    
+    private static final Logger LOG = Log.getLogger(ErrorHandler.class);
+    public final static String ERROR_PAGE="org.eclipse.jetty.server.error_page";
+    
+    boolean _showStacks=true;
+    boolean _showMessageInTitle=true;
+    String _cacheControl="must-revalidate,no-cache,no-store";
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
+    {
+        String method = request.getMethod();
+        if (!HttpMethod.GET.is(method) && !HttpMethod.POST.is(method) && !HttpMethod.HEAD.is(method))
+        {
+            baseRequest.setHandled(true);
+            return;
+        }
+        
+        if (this instanceof ErrorPageMapper)
+        {
+            String error_page=((ErrorPageMapper)this).getErrorPage(request);
+            if (error_page!=null && request.getServletContext()!=null)
+            {
+                String old_error_page=(String)request.getAttribute(ERROR_PAGE);
+                if (old_error_page==null || !old_error_page.equals(error_page))
+                {
+                    request.setAttribute(ERROR_PAGE, error_page);
+
+                    Dispatcher dispatcher = (Dispatcher) request.getServletContext().getRequestDispatcher(error_page);
+                    try
+                    {
+                        if(dispatcher!=null)
+                        {
+                            dispatcher.error(request, response);
+                            return;
+                        }
+                        LOG.warn("No error page "+error_page);
+                    }
+                    catch (ServletException e)
+                    {
+                        LOG.warn(Log.EXCEPTION, e);
+                        return;
+                    }
+                }
+            }
+        }
+        
+        baseRequest.setHandled(true);
+        response.setContentType(MimeTypes.Type.TEXT_HTML_8859_1.asString());    
+        if (_cacheControl!=null)
+            response.setHeader(HttpHeader.CACHE_CONTROL.asString(), _cacheControl);
+        ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(4096);
+        String reason=(response instanceof Response)?((Response)response).getReason():null;
+        handleErrorPage(request, writer, response.getStatus(), reason);
+        writer.flush();
+        response.setContentLength(writer.size());
+        writer.writeTo(response.getOutputStream());
+        writer.destroy();
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message)
+        throws IOException
+    {
+        writeErrorPage(request, writer, code, message, _showStacks);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
+        throws IOException
+    {
+        if (message == null)
+            message=HttpStatus.getMessage(code);
+
+        writer.write("<html>\n<head>\n");
+        writeErrorPageHead(request,writer,code,message);
+        writer.write("</head>\n<body>");
+        writeErrorPageBody(request,writer,code,message,showStacks);
+        writer.write("\n</body>\n</html>\n");
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeErrorPageHead(HttpServletRequest request, Writer writer, int code, String message)
+        throws IOException
+        {
+        writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n");
+        writer.write("<title>Error ");
+        writer.write(Integer.toString(code));
+
+        if (_showMessageInTitle)
+        {
+            writer.write(' ');
+            write(writer,message);
+        }
+        writer.write("</title>\n");
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeErrorPageBody(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
+        throws IOException
+    {
+        String uri= request.getRequestURI();
+
+        writeErrorPageMessage(request,writer,code,message,uri);
+        if (showStacks)
+            writeErrorPageStacks(request,writer);
+        writer.write("<hr><i><small>Powered by Jetty://</small></i><hr/>\n");
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeErrorPageMessage(HttpServletRequest request, Writer writer, int code, String message,String uri)
+    throws IOException
+    {
+        writer.write("<h2>HTTP ERROR ");
+        writer.write(Integer.toString(code));
+        writer.write("</h2>\n<p>Problem accessing ");
+        write(writer,uri);
+        writer.write(". Reason:\n<pre>    ");
+        write(writer,message);
+        writer.write("</pre></p>");
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeErrorPageStacks(HttpServletRequest request, Writer writer)
+        throws IOException
+    {
+        Throwable th = (Throwable)request.getAttribute("javax.servlet.error.exception");
+        while(th!=null)
+        {
+            writer.write("<h3>Caused by:</h3><pre>");
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            th.printStackTrace(pw);
+            pw.flush();
+            write(writer,sw.getBuffer().toString());
+            writer.write("</pre>\n");
+
+            th =th.getCause();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Bad Message Error body
+     * <p>Generate a error response body to be sent for a bad message.
+     * In this case there is something wrong with the request, so either
+     * a request cannot be built, or it is not safe to build a request.
+     * This method allows for a simple error page body to be returned 
+     * and some response headers to be set.
+     * @param status The error code that will be sent
+     * @param reason The reason for the error code (may be null)
+     * @param fields The header fields that will be sent with the response.
+     * @return The content as a ByteBuffer, or null for no body.
+     */
+    public ByteBuffer badMessageError(int status, String reason, HttpFields fields)
+    {
+        if (reason==null)
+            reason=HttpStatus.getMessage(status);
+        fields.put(HttpHeader.CONTENT_TYPE,MimeTypes.Type.TEXT_HTML_8859_1.asString());
+        return BufferUtil.toBuffer("<h1>Bad Message " + status + "</h1><pre>reason: " + reason + "</pre>");
+    }    
+    
+    /* ------------------------------------------------------------ */
+    /** Get the cacheControl.
+     * @return the cacheControl header to set on error responses.
+     */
+    public String getCacheControl()
+    {
+        return _cacheControl;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the cacheControl.
+     * @param cacheControl the cacheControl header to set on error responses.
+     */
+    public void setCacheControl(String cacheControl)
+    {
+        _cacheControl = cacheControl;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if stack traces are shown in the error pages
+     */
+    public boolean isShowStacks()
+    {
+        return _showStacks;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param showStacks True if stack traces are shown in the error pages
+     */
+    public void setShowStacks(boolean showStacks)
+    {
+        _showStacks = showStacks;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param showMessageInTitle if true, the error message appears in page title
+     */
+    public void setShowMessageInTitle(boolean showMessageInTitle)
+    {
+        _showMessageInTitle = showMessageInTitle;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public boolean getShowMessageInTitle()
+    {
+        return _showMessageInTitle;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void write(Writer writer,String string)
+        throws IOException
+    {
+        if (string==null)
+            return;
+
+        for (int i=0;i<string.length();i++)
+        {
+            char c=string.charAt(i);
+
+            switch(c)
+            {
+                case '&' :
+                    writer.write("&amp;");
+                    break;
+                case '<' :
+                    writer.write("&lt;");
+                    break;
+                case '>' :
+                    writer.write("&gt;");
+                    break;
+
+                default:
+                    if (Character.isISOControl(c) && !Character.isWhitespace(c))
+                        writer.write('?');
+                    else
+                        writer.write(c);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public interface ErrorPageMapper
+    {
+        String getErrorPage(HttpServletRequest request);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static ErrorHandler getErrorHandler(Server server, ContextHandler context)
+    {
+        ErrorHandler error_handler=null;
+        if (context!=null)
+            error_handler=context.getErrorHandler();
+        if (error_handler==null && server!=null)
+            error_handler = server.getBean(ErrorHandler.class);
+        return error_handler;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/HandlerCollection.java b/lib/jetty/org/eclipse/jetty/server/handler/HandlerCollection.java
new file mode 100644 (file)
index 0000000..c118c7e
--- /dev/null
@@ -0,0 +1,188 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+/* ------------------------------------------------------------ */
+/** A collection of handlers.
+ * <p>
+ * The default implementations  calls all handlers in list order,
+ * regardless of the response status or exceptions. Derived implementation
+ * may alter the order or the conditions of calling the contained
+ * handlers.
+ * <p>
+ *
+ */
+@ManagedObject("Handler of multiple handlers")
+public class HandlerCollection extends AbstractHandlerContainer
+{
+    private final boolean _mutableWhenRunning;
+    private volatile Handler[] _handlers;
+
+    /* ------------------------------------------------------------ */
+    public HandlerCollection()
+    {
+        _mutableWhenRunning=false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HandlerCollection(boolean mutableWhenRunning)
+    {
+        _mutableWhenRunning=mutableWhenRunning;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the handlers.
+     */
+    @Override
+    @ManagedAttribute(value="Wrapped handlers", readonly=true)
+    public Handler[] getHandlers()
+    {
+        return _handlers;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param handlers The handlers to set.
+     */
+    public void setHandlers(Handler[] handlers)
+    {
+        if (!_mutableWhenRunning && isStarted())
+            throw new IllegalStateException(STARTED);
+
+        if (handlers!=null)
+            for (Handler handler:handlers)
+                if (handler.getServer()!=getServer())
+                    handler.setServer(getServer());
+        Handler[] old=_handlers;;
+        _handlers = handlers;
+        updateBeans(old, handlers);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Handler#handle(String, Request, HttpServletRequest, HttpServletResponse)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException
+    {
+        if (_handlers!=null && isStarted())
+        {
+            MultiException mex=null;
+
+            for (int i=0;i<_handlers.length;i++)
+            {
+                try
+                {
+                    _handlers[i].handle(target,baseRequest, request, response);
+                }
+                catch(IOException e)
+                {
+                    throw e;
+                }
+                catch(RuntimeException e)
+                {
+                    throw e;
+                }
+                catch(Exception e)
+                {
+                    if (mex==null)
+                        mex=new MultiException();
+                    mex.add(e);
+                }
+            }
+            if (mex!=null)
+            {
+                if (mex.size()==1)
+                    throw new ServletException(mex.getThrowable(0));
+                else
+                    throw new ServletException(mex);
+            }
+
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        super.setServer(server);
+        Handler[] handlers=getHandlers();
+        if (handlers!=null)
+            for (Handler h : handlers)
+                h.setServer(server);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Add a handler.
+     * This implementation adds the passed handler to the end of the existing collection of handlers.
+     * @see org.eclipse.jetty.server.server.HandlerContainer#addHandler(org.eclipse.jetty.server.server.Handler)
+     */
+    public void addHandler(Handler handler)
+    {
+        setHandlers(ArrayUtil.addToArray(getHandlers(), handler, Handler.class));
+    }
+
+    /* ------------------------------------------------------------ */
+    public void removeHandler(Handler handler)
+    {
+        Handler[] handlers = getHandlers();
+
+        if (handlers!=null && handlers.length>0 )
+            setHandlers(ArrayUtil.removeFromArray(handlers, handler));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void expandChildren(List<Handler> list, Class<?> byClass)
+    {
+        if (getHandlers()!=null)
+            for (Handler h:getHandlers())
+                expandHandler(h, list, byClass);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroy()
+    {
+        if (!isStopped())
+            throw new IllegalStateException("!STOPPED");
+        Handler[] children=getChildHandlers();
+        setHandlers(null);
+        for (Handler child: children)
+            child.destroy();
+        super.destroy();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/HandlerList.java b/lib/jetty/org/eclipse/jetty/server/handler/HandlerList.java
new file mode 100644 (file)
index 0000000..74320b0
--- /dev/null
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+
+/* ------------------------------------------------------------ */
+/** HandlerList.
+ * This extension of {@link HandlerCollection} will call
+ * each contained handler in turn until either an exception is thrown, the response
+ * is committed or a positive response status is set.
+ */
+public class HandlerList extends HandlerCollection
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Handler#handle(String, Request, HttpServletRequest, HttpServletResponse)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException
+    {
+        Handler[] handlers = getHandlers();
+
+        if (handlers!=null && isStarted())
+        {
+            for (int i=0;i<handlers.length;i++)
+            {
+                handlers[i].handle(target,baseRequest, request, response);
+                if ( baseRequest.isHandled())
+                    return;
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/HandlerWrapper.java b/lib/jetty/org/eclipse/jetty/server/handler/HandlerWrapper.java
new file mode 100644 (file)
index 0000000..dcbf6ff
--- /dev/null
@@ -0,0 +1,142 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* ------------------------------------------------------------ */
+/** A <code>HandlerWrapper</code> acts as a {@link Handler} but delegates the {@link Handler#handle handle} method and
+ * {@link LifeCycle life cycle} events to a delegate. This is primarily used to implement the <i>Decorator</i> pattern.
+ *
+ */
+@ManagedObject("Handler wrapping another Handler")
+public class HandlerWrapper extends AbstractHandlerContainer
+{
+    protected Handler _handler;
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public HandlerWrapper()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the handlers.
+     */
+    @ManagedAttribute(value="Wrapped Handler", readonly=true)
+    public Handler getHandler()
+    {
+        return _handler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the handlers.
+     */
+    @Override
+    public Handler[] getHandlers()
+    {
+        if (_handler==null)
+            return new Handler[0];
+        return new Handler[] {_handler};
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param handler Set the {@link Handler} which should be wrapped.
+     */
+    public void setHandler(Handler handler)
+    {
+        if (isStarted())
+            throw new IllegalStateException(STARTED);
+
+        if (handler!=null)
+            handler.setServer(getServer());
+        
+        Handler old=_handler;
+        _handler=handler;
+        updateBean(old,_handler);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (_handler!=null && isStarted())
+        {
+            _handler.handle(target,baseRequest, request, response);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        if (server==getServer())
+            return;
+        
+        if (isStarted())
+            throw new IllegalStateException(STARTED);
+
+        super.setServer(server);
+        Handler h=getHandler();
+        if (h!=null)
+            h.setServer(server);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void expandChildren(List<Handler> list, Class<?> byClass)
+    {
+        expandHandler(_handler,list,byClass);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroy()
+    {
+        if (!isStopped())
+            throw new IllegalStateException("!STOPPED");
+        Handler child=getHandler();
+        if (child!=null)
+        {
+            setHandler(null);
+            child.destroy();
+        }
+        super.destroy();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/HotSwapHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/HotSwapHandler.java
new file mode 100644 (file)
index 0000000..162a719
--- /dev/null
@@ -0,0 +1,160 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+
+/* ------------------------------------------------------------ */
+/**
+ * A <code>HandlerContainer</code> that allows a hot swap of a wrapped handler.
+ *
+ */
+public class HotSwapHandler extends AbstractHandlerContainer
+{
+    private volatile Handler _handler;
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public HotSwapHandler()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the handlers.
+     */
+    public Handler getHandler()
+    {
+        return _handler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the handlers.
+     */
+    @Override
+    public Handler[] getHandlers()
+    {
+        return new Handler[]
+        { _handler };
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param handler
+     *            Set the {@link Handler} which should be wrapped.
+     */
+    public void setHandler(Handler handler)
+    {
+        if (handler == null)
+            throw new IllegalArgumentException("Parameter handler is null.");
+        try
+        {
+            updateBean(_handler,handler);
+            _handler=handler;
+            Server server = getServer();
+            handler.setServer(server);
+
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.server.EventHandler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (_handler != null && isStarted())
+        {
+            _handler.handle(target,baseRequest,request,response);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        if (isRunning())
+            throw new IllegalStateException(RUNNING);
+
+        super.setServer(server);
+
+        Handler h = getHandler();
+        if (h != null)
+            h.setServer(server);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void expandChildren(List<Handler> list, Class<?> byClass)
+    {
+        expandHandler(_handler,list,byClass);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroy()
+    {
+        if (!isStopped())
+            throw new IllegalStateException("!STOPPED");
+        Handler child = getHandler();
+        if (child != null)
+        {
+            setHandler(null);
+            child.destroy();
+        }
+        super.destroy();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/IPAccessHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/IPAccessHandler.java
new file mode 100644 (file)
index 0000000..97a3ac8
--- /dev/null
@@ -0,0 +1,385 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.IPAddressMap;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * IP Access Handler
+ * <p>
+ * Controls access to the wrapped handler by the real remote IP. Control is provided
+ * by white/black lists that include both internet addresses and URIs. This handler
+ * uses the real internet address of the connection, not one reported in the forwarded
+ * for headers, as this cannot be as easily forged.
+ * <p>
+ * Typically, the black/white lists will be used in one of three modes:
+ * <ul>
+ * <li>Blocking a few specific IPs/URLs by specifying several black list entries.
+ * <li>Allowing only some specific IPs/URLs by specifying several white lists entries.
+ * <li>Allowing a general range of IPs/URLs by specifying several general white list
+ * entries, that are then further refined by several specific black list exceptions
+ * </ul>
+ * <p>
+ * By default an empty white list is treated as match all. If there is at least one entry in
+ * the white list, then a request must match a white list entry. Black list entries
+ * are always applied, so that even if an entry matches the white list, a black list
+ * entry will override it.
+ * <p>
+ * <p>
+ * You can change white list policy setting whiteListByPath to true. In this mode a request will be white listed
+ * IF it has a matching URL in the white list, otherwise the black list applies, e.g. in default mode when
+ * whiteListByPath = false and wl = "127.0.0.1|/foo", /bar request from 127.0.0.1 will be blacklisted,
+ * if whiteListByPath=true then not.
+ * </p>
+ * Internet addresses may be specified as absolute address or as a combination of
+ * four octet wildcard specifications (a.b.c.d) that are defined as follows.
+ * </p>
+ * <pre>
+ * nnn - an absolute value (0-255)
+ * mmm-nnn - an inclusive range of absolute values,
+ *           with following shorthand notations:
+ *           nnn- => nnn-255
+ *           -nnn => 0-nnn
+ *           -    => 0-255
+ * a,b,... - a list of wildcard specifications
+ * </pre>
+ * <p>
+ * Internet address specification is separated from the URI pattern using the "|" (pipe)
+ * character. URI patterns follow the servlet specification for simple * prefix and
+ * suffix wild cards (e.g. /, /foo, /foo/bar, /foo/bar/*, *.baz).
+ * <p>
+ * Earlier versions of the handler used internet address prefix wildcard specification
+ * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.).
+ * They also used the first "/" character of the URI pattern to separate it from the
+ * internet address. Both of these features have been deprecated in the current version.
+ * <p>
+ * Examples of the entry specifications are:
+ * <ul>
+ * <li>10.10.1.2 - all requests from IP 10.10.1.2
+ * <li>10.10.1.2|/foo/bar - all requests from IP 10.10.1.2 to URI /foo/bar
+ * <li>10.10.1.2|/foo/* - all requests from IP 10.10.1.2 to URIs starting with /foo/
+ * <li>10.10.1.2|*.html - all requests from IP 10.10.1.2 to URIs ending with .html
+ * <li>10.10.0-255.0-255 - all requests from IPs within 10.10.0.0/16 subnet
+ * <li>10.10.0-.-255|/foo/bar - all requests from IPs within 10.10.0.0/16 subnet to URI /foo/bar
+ * <li>10.10.0-3,1,3,7,15|/foo/* - all requests from IPs addresses with last octet equal
+ *                                  to 1,3,7,15 in subnet 10.10.0.0/22 to URIs starting with /foo/
+ * </ul>
+ * <p>
+ * Earlier versions of the handler used internet address prefix wildcard specification
+ * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.).
+ * They also used the first "/" character of the URI pattern to separate it from the
+ * internet address. Both of these features have been deprecated in the current version.
+ */
+public class IPAccessHandler extends HandlerWrapper
+{
+    private static final Logger LOG = Log.getLogger(IPAccessHandler.class);
+    // true means nodefault match
+    PathMap<IPAddressMap<Boolean>> _white = new PathMap<IPAddressMap<Boolean>>(true);
+    PathMap<IPAddressMap<Boolean>> _black = new PathMap<IPAddressMap<Boolean>>(true);
+    boolean _whiteListByPath = false;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Creates new handler object
+     */
+    public IPAccessHandler()
+    {
+        super();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Creates new handler object and initializes white- and black-list
+     *
+     * @param white array of whitelist entries
+     * @param black array of blacklist entries
+     */
+    public IPAccessHandler(String[] white, String []black)
+    {
+        super();
+
+        if (white != null && white.length > 0)
+            setWhite(white);
+        if (black != null && black.length > 0)
+            setBlack(black);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add a whitelist entry to an existing handler configuration
+     *
+     * @param entry new whitelist entry
+     */
+    public void addWhite(String entry)
+    {
+        add(entry, _white);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add a blacklist entry to an existing handler configuration
+     *
+     * @param entry new blacklist entry
+     */
+    public void addBlack(String entry)
+    {
+        add(entry, _black);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Re-initialize the whitelist of existing handler object
+     *
+     * @param entries array of whitelist entries
+     */
+    public void setWhite(String[] entries)
+    {
+        set(entries, _white);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Re-initialize the blacklist of existing handler object
+     *
+     * @param entries array of blacklist entries
+     */
+    public void setBlack(String[] entries)
+    {
+        set(entries, _black);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Re-initialize the mode of path matching
+     *
+     * @param whiteListByPath matching mode
+     */
+    public void setWhiteListByPath(boolean whiteListByPath)
+    {
+        this._whiteListByPath = whiteListByPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Checks the incoming request against the whitelist and blacklist
+     *
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        // Get the real remote IP (not the one set by the forwarded headers (which may be forged))
+        HttpChannel<?> channel = baseRequest.getHttpChannel();
+        if (channel!=null)
+        {
+            EndPoint endp=channel.getEndPoint();
+            if (endp!=null)
+            {
+                InetSocketAddress address = endp.getRemoteAddress();
+                if (address!=null && !isAddrUriAllowed(address.getHostString(),baseRequest.getPathInfo()))
+                {
+                    response.sendError(HttpStatus.FORBIDDEN_403);
+                    baseRequest.setHandled(true);
+                    return;
+                }
+            }
+        }
+
+        getHandler().handle(target,baseRequest, request, response);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Helper method to parse the new entry and add it to
+     * the specified address pattern map.
+     *
+     * @param entry new entry
+     * @param patternMap target address pattern map
+     */
+    protected void add(String entry, PathMap<IPAddressMap<Boolean>> patternMap)
+    {
+        if (entry != null && entry.length() > 0)
+        {
+            boolean deprecated = false;
+            int idx;
+            if (entry.indexOf('|') > 0 )
+            {
+                idx = entry.indexOf('|');
+            }
+            else
+            {
+                idx = entry.indexOf('/');
+                deprecated = (idx >= 0);
+            }
+
+            String addr = idx > 0 ? entry.substring(0,idx) : entry;
+            String path = idx > 0 ? entry.substring(idx) : "/*";
+
+            if (addr.endsWith("."))
+                deprecated = true;
+            if (path!=null && (path.startsWith("|") || path.startsWith("/*.")))
+                path=path.substring(1);
+
+            IPAddressMap<Boolean> addrMap = patternMap.get(path);
+            if (addrMap == null)
+            {
+                addrMap = new IPAddressMap<Boolean>();
+                patternMap.put(path,addrMap);
+            }
+            if (addr != null && !"".equals(addr))
+                // MUST NOT BE null
+                addrMap.put(addr, true);
+
+            if (deprecated)
+                LOG.debug(toString() +" - deprecated specification syntax: "+entry);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Helper method to process a list of new entries and replace
+     * the content of the specified address pattern map
+     *
+     * @param entries new entries
+     * @param patternMap target address pattern map
+     */
+    protected void set(String[] entries,  PathMap<IPAddressMap<Boolean>> patternMap)
+    {
+        patternMap.clear();
+
+        if (entries != null && entries.length > 0)
+        {
+            for (String addrPath:entries)
+            {
+                add(addrPath, patternMap);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Check if specified request is allowed by current IPAccess rules.
+     *
+     * @param addr internet address
+     * @param path context path
+     * @return true if request is allowed
+     *
+     */
+    protected boolean isAddrUriAllowed(String addr, String path)
+    {
+        if (_white.size()>0)
+        {
+            boolean match = false;
+            boolean matchedByPath = false;
+
+            for (Map.Entry<String,IPAddressMap<Boolean>> entry : _white.getMatches(path))
+            {
+                matchedByPath=true;
+                IPAddressMap<Boolean> addrMap = entry.getValue();
+                if ((addrMap!=null && (addrMap.size()==0 || addrMap.match(addr)!=null)))
+                {
+                    match=true;
+                    break;
+                }
+            }
+            
+            if (_whiteListByPath)
+            {
+                if (matchedByPath && !match)
+                    return false;
+            }
+            else
+            {
+                if (!match)
+                    return false;
+            }
+        }
+
+        if (_black.size() > 0)
+        {
+            for (Map.Entry<String,IPAddressMap<Boolean>> entry : _black.getMatches(path))
+            {
+                IPAddressMap<Boolean> addrMap = entry.getValue();
+                if (addrMap!=null && (addrMap.size()==0 || addrMap.match(addr)!=null))
+                    return false;
+            }
+            
+        }
+
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Dump the handler configuration
+     */
+    @Override
+    public String dump()
+    {
+        StringBuilder buf = new StringBuilder();
+
+        buf.append(toString());
+        buf.append(" WHITELIST:\n");
+        dump(buf, _white);
+        buf.append(toString());
+        buf.append(" BLACKLIST:\n");
+        dump(buf, _black);
+
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Dump a pattern map into a StringBuilder buffer
+     *
+     * @param buf buffer
+     * @param patternMap pattern map to dump
+     */
+    protected void dump(StringBuilder buf, PathMap<IPAddressMap<Boolean>> patternMap)
+    {
+        for (String path: patternMap.keySet())
+        {
+            for (String addr: patternMap.get(path).keySet())
+            {
+                buf.append("# ");
+                buf.append(addr);
+                buf.append("|");
+                buf.append(path);
+                buf.append("\n");
+            }
+        }
+    }
+ }
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java
new file mode 100644 (file)
index 0000000..04a90f1
--- /dev/null
@@ -0,0 +1,134 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+
+/**
+ * Handler to adjust the idle timeout of requests while dispatched.
+ * Can be applied in jetty.xml with
+ * <pre>
+ *   &lt;Get id='handler' name='Handler'/>
+ *   &lt;Set name='Handler'>
+ *     &lt;New id='idleTimeoutHandler' class='org.eclipse.jetty.server.handler.IdleTimeoutHandler'>
+ *       &lt;Set name='Handler'>&lt;Ref id='handler'/>&lt;/Set>
+ *       &lt;Set name='IdleTimeoutMs'>5000&lt;/Set>
+ *     &lt;/New>
+ *   &lt;/Set>
+ * </pre>
+ */
+public class IdleTimeoutHandler extends HandlerWrapper
+{
+    private long _idleTimeoutMs = 1000;
+    private boolean _applyToAsync = false;
+    
+    public boolean isApplyToAsync()
+    {
+        return _applyToAsync;
+    }
+
+    /**
+     * Should the adjusted idle time be maintained for asynchronous requests
+     * @param applyToAsync true if alternate idle timeout is applied to asynchronous requests
+     */
+    public void setApplyToAsync(boolean applyToAsync)
+    {
+        _applyToAsync = applyToAsync;
+    }
+
+    public long getIdleTimeoutMs()
+    {
+        return _idleTimeoutMs;
+    }
+
+    /**
+     * @param idleTimeoutMs The idle timeout in MS to apply while dispatched or async
+     */
+    public void setIdleTimeoutMs(long idleTimeoutMs)
+    {
+        this._idleTimeoutMs = idleTimeoutMs;
+    }
+    
+   
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        HttpConnection connection = HttpConnection.getCurrentConnection();
+        final EndPoint endp = connection==null?null:connection.getEndPoint();
+        
+        final long idle_timeout;
+        if (endp==null)
+            idle_timeout=-1;
+        else
+        {
+            idle_timeout=endp.getIdleTimeout();
+            endp.setIdleTimeout(_idleTimeoutMs);
+        }
+        
+        try
+        {
+            super.handle(target,baseRequest,request,response);
+        }
+        finally
+        {
+            if (endp!=null)
+            {
+                if (_applyToAsync && request.isAsyncStarted())
+                {
+                    request.getAsyncContext().addListener(new AsyncListener()
+                    {
+                        @Override
+                        public void onTimeout(AsyncEvent event) throws IOException
+                        {                            
+                        }
+                        
+                        @Override
+                        public void onStartAsync(AsyncEvent event) throws IOException
+                        {
+                        }
+                        
+                        @Override
+                        public void onError(AsyncEvent event) throws IOException
+                        {
+                            endp.setIdleTimeout(idle_timeout);
+                        }
+                        
+                        @Override
+                        public void onComplete(AsyncEvent event) throws IOException
+                        {
+                            endp.setIdleTimeout(idle_timeout);
+                        }
+                    });
+                }
+                else 
+                    endp.setIdleTimeout(idle_timeout);
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/MovedContextHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/MovedContextHandler.java
new file mode 100644 (file)
index 0000000..5e9fd85
--- /dev/null
@@ -0,0 +1,154 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.URIUtil;
+
+/* ------------------------------------------------------------ */
+/** Moved ContextHandler.
+ * This context can be used to replace a context that has changed
+ * location.  Requests are redirected (either to a fixed URL or to a
+ * new context base).
+ */
+public class MovedContextHandler extends ContextHandler
+{
+    final Redirector _redirector;
+    String _newContextURL;
+    boolean _discardPathInfo;
+    boolean _discardQuery;
+    boolean _permanent;
+    String _expires;
+
+    public MovedContextHandler()
+    {
+        _redirector=new Redirector();
+        setHandler(_redirector);
+        setAllowNullPathInfo(true);
+    }
+
+    public MovedContextHandler(HandlerContainer parent, String contextPath, String newContextURL)
+    {
+        super(parent,contextPath);
+        _newContextURL=newContextURL;
+        _redirector=new Redirector();
+        setHandler(_redirector);
+    }
+
+    public boolean isDiscardPathInfo()
+    {
+        return _discardPathInfo;
+    }
+
+    public void setDiscardPathInfo(boolean discardPathInfo)
+    {
+        _discardPathInfo = discardPathInfo;
+    }
+
+    public String getNewContextURL()
+    {
+        return _newContextURL;
+    }
+
+    public void setNewContextURL(String newContextURL)
+    {
+        _newContextURL = newContextURL;
+    }
+
+    public boolean isPermanent()
+    {
+        return _permanent;
+    }
+
+    public void setPermanent(boolean permanent)
+    {
+        _permanent = permanent;
+    }
+
+    public boolean isDiscardQuery()
+    {
+        return _discardQuery;
+    }
+
+    public void setDiscardQuery(boolean discardQuery)
+    {
+        _discardQuery = discardQuery;
+    }
+
+    private class Redirector extends AbstractHandler
+    {
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (_newContextURL==null)
+                return;
+
+            String path=_newContextURL;
+            if (!_discardPathInfo && request.getPathInfo()!=null)
+                path=URIUtil.addPaths(path, request.getPathInfo());
+
+            StringBuilder location = URIUtil.hasScheme(path)?new StringBuilder():baseRequest.getRootURL();
+
+            location.append(path);
+            if (!_discardQuery && request.getQueryString()!=null)
+            {
+                location.append('?');
+                String q=request.getQueryString();
+                q=q.replaceAll("\r\n?&=","!");
+                location.append(q);
+            }
+
+            response.setHeader(HttpHeader.LOCATION.asString(),location.toString());
+
+            if (_expires!=null)
+                response.setHeader(HttpHeader.EXPIRES.asString(),_expires);
+
+            response.setStatus(_permanent?HttpServletResponse.SC_MOVED_PERMANENTLY:HttpServletResponse.SC_FOUND);
+            response.setContentLength(0);
+            baseRequest.setHandled(true);
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the expires header value or null if no expires header
+     */
+    public String getExpires()
+    {
+        return _expires;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param expires the expires header value or null if no expires header
+     */
+    public void setExpires(String expires)
+    {
+        _expires = expires;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/RequestLogHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/RequestLogHandler.java
new file mode 100644 (file)
index 0000000..706b988
--- /dev/null
@@ -0,0 +1,153 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.AsyncContextState;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+/**
+ * RequestLogHandler.
+ * This handler can be used to wrap an individual context for context logging.
+ *
+ *
+ * @org.apache.xbean.XBean
+ */
+public class RequestLogHandler extends HandlerWrapper
+{
+    private static final Logger LOG = Log.getLogger(RequestLogHandler.class);
+    private RequestLog _requestLog;
+    private final AsyncListener _listener = new AsyncListener()
+    {
+        
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+            
+        }
+        
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+            event.getAsyncContext().addListener(this);
+        }
+        
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+            
+        }
+        
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {
+            AsyncContextState context = (AsyncContextState)event.getAsyncContext();
+            Request request=context.getHttpChannelState().getBaseRequest();
+            Response response=request.getResponse();
+            _requestLog.log(request,response);
+        }
+    };
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+            throws IOException, ServletException
+    {
+        try
+        {
+            super.handle(target, baseRequest, request, response);
+        }
+        finally
+        {
+            if (_requestLog != null && baseRequest.getDispatcherType().equals(DispatcherType.REQUEST))
+            {
+                if (baseRequest.getHttpChannelState().isAsync())
+                {
+                    if (baseRequest.getHttpChannelState().isInitial())
+                        baseRequest.getAsyncContext().addListener(_listener);
+                }
+                else
+                    _requestLog.log(baseRequest, (Response)response);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRequestLog(RequestLog requestLog)
+    {
+        updateBean(_requestLog,requestLog);
+        _requestLog=requestLog;
+    }
+
+    /* ------------------------------------------------------------ */
+    public RequestLog getRequestLog()
+    {
+        return _requestLog;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (_requestLog==null)
+        {
+            LOG.warn("!RequestLog");
+            _requestLog=new NullRequestLog();
+        }
+        super.doStart();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        if (_requestLog instanceof NullRequestLog)
+            _requestLog=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class NullRequestLog extends AbstractLifeCycle implements RequestLog
+    {
+        @Override
+        public void log(Request request, Response response)
+        {            
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/ResourceHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/ResourceHandler.java
new file mode 100644 (file)
index 0000000..10d1e05
--- /dev/null
@@ -0,0 +1,613 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.WriterOutputStream;
+import org.eclipse.jetty.server.HttpOutput;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.FileResource;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Resource Handler.
+ *
+ * This handle will serve static content and handle If-Modified-Since headers.
+ * No caching is done.
+ * Requests for resources that do not exist are let pass (Eg no 404's).
+ *
+ *
+ * @org.apache.xbean.XBean
+ */
+public class ResourceHandler extends HandlerWrapper
+{
+    private static final Logger LOG = Log.getLogger(ResourceHandler.class);
+
+    ContextHandler _context;
+    Resource _baseResource;
+    Resource _defaultStylesheet;
+    Resource _stylesheet;
+    String[] _welcomeFiles={"index.html"};
+    MimeTypes _mimeTypes = new MimeTypes();
+    String _cacheControl;
+    boolean _directory;
+    boolean _etags;
+    int _minMemoryMappedContentLength=-1;
+    int _minAsyncContentLength=0;
+
+    /* ------------------------------------------------------------ */
+    public ResourceHandler()
+    {
+
+    }
+
+    /* ------------------------------------------------------------ */
+    public MimeTypes getMimeTypes()
+    {
+        return _mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMimeTypes(MimeTypes mimeTypes)
+    {
+        _mimeTypes = mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the directory option.
+     * @return true if directories are listed.
+     */
+    public boolean isDirectoriesListed()
+    {
+        return _directory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the directory.
+     * @param directory true if directories are listed.
+     */
+    public void setDirectoriesListed(boolean directory)
+    {
+        _directory = directory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get minimum memory mapped file content length.
+     * @return the minimum size in bytes of a file resource that will
+     * be served using a memory mapped buffer, or -1 (default) for no memory mapped
+     * buffers.
+     */
+    public int getMinMemoryMappedContentLength()
+    {
+        return _minMemoryMappedContentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set minimum memory mapped file content length.
+     * @param minMemoryMappedFileSize the minimum size in bytes of a file resource that will
+     * be served using a memory mapped buffer, or -1 for no memory mapped
+     * buffers.
+     */
+    public void setMinMemoryMappedContentLength(int minMemoryMappedFileSize)
+    {
+        _minMemoryMappedContentLength = minMemoryMappedFileSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the minimum content length for async handling.
+     * @return The minimum size in bytes of the content before asynchronous 
+     * handling is used, or -1 for no async handling or 0 (default) for using
+     * {@link HttpServletResponse#getBufferSize()} as the minimum length.
+     */
+    public int getMinAsyncContentLength()
+    {
+        return _minAsyncContentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the minimum content length for async handling.
+     * @param minAsyncContentLength The minimum size in bytes of the content before asynchronous 
+     * handling is used, or -1 for no async handling or 0 for using
+     * {@link HttpServletResponse#getBufferSize()} as the minimum length.
+     */
+    public void setMinAsyncContentLength(int minAsyncContentLength)
+    {
+        _minAsyncContentLength = minAsyncContentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if ETag processing is done
+     */
+    public boolean isEtags()
+    {
+        return _etags;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param etags True if ETag processing is done
+     */
+    public void setEtags(boolean etags)
+    {
+        _etags = etags;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStart()
+    throws Exception
+    {
+        Context scontext = ContextHandler.getCurrentContext();
+        _context = (scontext==null?null:scontext.getContextHandler());
+
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the resourceBase.
+     */
+    public Resource getBaseResource()
+    {
+        if (_baseResource==null)
+            return null;
+        return _baseResource;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the base resource as a string.
+     */
+    public String getResourceBase()
+    {
+        if (_baseResource==null)
+            return null;
+        return _baseResource.toString();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param base The resourceBase to set.
+     */
+    public void setBaseResource(Resource base)
+    {
+        _baseResource=base;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param resourceBase The base resource as a string.
+     */
+    public void setResourceBase(String resourceBase)
+    {
+        try
+        {
+            setBaseResource(Resource.newResource(resourceBase));
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+            throw new IllegalArgumentException(resourceBase);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the stylesheet as a Resource.
+     */
+    public Resource getStylesheet()
+    {
+       if(_stylesheet != null)
+       {
+           return _stylesheet;
+       }
+       else
+       {
+           if(_defaultStylesheet == null)
+           {
+               _defaultStylesheet =  Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
+           }
+           return _defaultStylesheet;
+       }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param stylesheet The location of the stylesheet to be used as a String.
+     */
+    public void setStylesheet(String stylesheet)
+    {
+        try
+        {
+            _stylesheet = Resource.newResource(stylesheet);
+            if(!_stylesheet.exists())
+            {
+                LOG.warn("unable to find custom stylesheet: " + stylesheet);
+                _stylesheet = null;
+            }
+        }
+       catch(Exception e)
+       {
+           LOG.warn(e.toString());
+           LOG.debug(e);
+           throw new IllegalArgumentException(stylesheet);
+       }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the cacheControl header to set on all static content.
+     */
+    public String getCacheControl()
+    {
+        return _cacheControl;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param cacheControl the cacheControl header to set on all static content.
+     */
+    public void setCacheControl(String cacheControl)
+    {
+        _cacheControl=cacheControl;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public Resource getResource(String path) throws MalformedURLException
+    {
+        if (path==null || !path.startsWith("/"))
+            throw new MalformedURLException(path);
+
+        Resource base = _baseResource;
+        if (base==null)
+        {
+            if (_context==null)
+                return null;
+            base=_context.getBaseResource();
+            if (base==null)
+                return null;
+        }
+
+        try
+        {
+            path=URIUtil.canonicalPath(path);
+            return base.addPath(path);
+        }
+        catch(Exception e)
+        {
+            LOG.ignore(e);
+        }
+
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected Resource getResource(HttpServletRequest request) throws MalformedURLException
+    {
+        String servletPath;
+        String pathInfo;
+        Boolean included = request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null;
+        if (included != null && included.booleanValue())
+        {
+            servletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+            pathInfo = (String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
+
+            if (servletPath == null && pathInfo == null)
+            {
+                servletPath = request.getServletPath();
+                pathInfo = request.getPathInfo();
+            }
+        }
+        else
+        {
+            servletPath = request.getServletPath();
+            pathInfo = request.getPathInfo();
+        }
+
+        String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
+        return getResource(pathInContext);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public String[] getWelcomeFiles()
+    {
+        return _welcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setWelcomeFiles(String[] welcomeFiles)
+    {
+        _welcomeFiles=welcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
+    {
+        for (int i=0;i<_welcomeFiles.length;i++)
+        {
+            Resource welcome=directory.addPath(_welcomeFiles[i]);
+            if (welcome.exists() && !welcome.isDirectory())
+                return welcome;
+        }
+
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (baseRequest.isHandled())
+            return;
+
+        boolean skipContentBody = false;
+
+        if(!HttpMethod.GET.is(request.getMethod()))
+        {
+            if(!HttpMethod.HEAD.is(request.getMethod()))
+            {
+                //try another handler
+                super.handle(target, baseRequest, request, response);
+                return;
+            }
+            skipContentBody = true;
+        }
+
+        Resource resource = getResource(request);
+        // If resource is not found
+        if (resource==null || !resource.exists())
+        {
+            // inject the jetty-dir.css file if it matches
+            if (target.endsWith("/jetty-dir.css"))
+            {
+                resource = getStylesheet();
+                if (resource==null)
+                    return;
+                response.setContentType("text/css");
+            }
+            else
+            {
+                //no resource - try other handlers
+                super.handle(target, baseRequest, request, response);
+                return;
+            }
+        }
+
+        // We are going to serve something
+        baseRequest.setHandled(true);
+
+        // handle directories
+        if (resource.isDirectory())
+        {
+            if (!request.getPathInfo().endsWith(URIUtil.SLASH))
+            {
+                response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)));
+                return;
+            }
+
+            Resource welcome=getWelcome(resource);
+            if (welcome!=null && welcome.exists())
+                resource=welcome;
+            else
+            {
+                doDirectory(request,response,resource);
+                baseRequest.setHandled(true);
+                return;
+            }
+        }
+
+        // Handle ETAGS
+        long last_modified=resource.lastModified();
+        String etag=null;
+        if (_etags)
+        {
+            // simple handling of only a single etag
+            String ifnm = request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
+            etag=resource.getWeakETag();
+            if (ifnm!=null && resource!=null && ifnm.equals(etag))
+            {
+                response.setStatus(HttpStatus.NOT_MODIFIED_304);
+                baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
+                return;
+            }
+        }
+        
+        // Handle if modified since 
+        if (last_modified>0)
+        {
+            long if_modified=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
+            if (if_modified>0 && last_modified/1000<=if_modified/1000)
+            {
+                response.setStatus(HttpStatus.NOT_MODIFIED_304);
+                return;
+            }
+        }
+
+        // set the headers
+        String mime=_mimeTypes.getMimeByExtension(resource.toString());
+        if (mime==null)
+            mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
+        doResponseHeaders(response,resource,mime);
+        if (_etags)
+            baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
+        
+        if(skipContentBody)
+            return;
+        
+        
+        // Send the content
+        OutputStream out =null;
+        try {out = response.getOutputStream();}
+        catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
+
+        // Has the output been wrapped
+        if (!(out instanceof HttpOutput))
+            // Write content via wrapped output
+            resource.writeTo(out,0,resource.length());
+        else
+        {
+            // select async by size
+            int min_async_size=_minAsyncContentLength==0?response.getBufferSize():_minAsyncContentLength;
+            
+            if (request.isAsyncSupported() && 
+                min_async_size>0 &&
+                resource.length()>=min_async_size)
+            {
+                final AsyncContext async = request.startAsync();
+                Callback callback = new Callback()
+                {
+                    @Override
+                    public void succeeded()
+                    {
+                        async.complete();
+                    }
+
+                    @Override
+                    public void failed(Throwable x)
+                    {
+                        LOG.warn(x.toString());
+                        LOG.debug(x);
+                        async.complete();
+                    }   
+                };
+
+                // Can we use a memory mapped file?
+                if (_minMemoryMappedContentLength>0 && 
+                    resource.length()>_minMemoryMappedContentLength &&
+                    resource instanceof FileResource)
+                {
+                    ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
+                    ((HttpOutput)out).sendContent(buffer,callback);
+                }
+                else  // Do a blocking write of a channel (if available) or input stream
+                {
+                    // Close of the channel/inputstream is done by the async sendContent
+                    ReadableByteChannel channel= resource.getReadableByteChannel();
+                    if (channel!=null)
+                        ((HttpOutput)out).sendContent(channel,callback);
+                    else
+                        ((HttpOutput)out).sendContent(resource.getInputStream(),callback);
+                }
+            }
+            else
+            {
+                // Can we use a memory mapped file?
+                if (_minMemoryMappedContentLength>0 && 
+                    resource.length()>_minMemoryMappedContentLength &&
+                    resource instanceof FileResource)
+                {
+                    ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
+                    ((HttpOutput)out).sendContent(buffer);
+                }
+                else  // Do a blocking write of a channel (if available) or input stream
+                {
+                    ReadableByteChannel channel= resource.getReadableByteChannel();
+                    if (channel!=null)
+                        ((HttpOutput)out).sendContent(channel);
+                    else
+                        ((HttpOutput)out).sendContent(resource.getInputStream());
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void doDirectory(HttpServletRequest request,HttpServletResponse response, Resource resource)
+        throws IOException
+    {
+        if (_directory)
+        {
+            String listing = resource.getListHTML(request.getRequestURI(),request.getPathInfo().lastIndexOf("/") > 0);
+            response.setContentType("text/html; charset=UTF-8");
+            response.getWriter().println(listing);
+        }
+        else
+            response.sendError(HttpStatus.FORBIDDEN_403);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the response headers.
+     * This method is called to set the response headers such as content type and content length.
+     * May be extended to add additional headers.
+     * @param response
+     * @param resource
+     * @param mimeType
+     */
+    protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
+    {
+        if (mimeType!=null)
+            response.setContentType(mimeType);
+
+        long length=resource.length();
+
+        if (response instanceof Response)
+        {
+            HttpFields fields = ((Response)response).getHttpFields();
+
+            if (length>0)
+                ((Response)response).setLongContentLength(length);
+
+            if (_cacheControl!=null)
+                fields.put(HttpHeader.CACHE_CONTROL,_cacheControl);
+        }
+        else
+        {
+            if (length>Integer.MAX_VALUE)
+                response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(length));
+            else if (length>0)
+                response.setContentLength((int)length);
+
+            if (_cacheControl!=null)
+                response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/ScopedHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/ScopedHandler.java
new file mode 100644 (file)
index 0000000..50998be
--- /dev/null
@@ -0,0 +1,200 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+
+
+/* ------------------------------------------------------------ */
+/** ScopedHandler.
+ *
+ * A ScopedHandler is a HandlerWrapper where the wrapped handlers
+ * each define a scope.   
+ * 
+ * <p>When {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
+ * is called on the first ScopedHandler in a chain of HandlerWrappers,
+ * the {@link #doScope(String, Request, HttpServletRequest, HttpServletResponse)} method is
+ * called on all contained ScopedHandlers, before the
+ * {@link #doHandle(String, Request, HttpServletRequest, HttpServletResponse)} method
+ * is called on all contained handlers.</p>
+ *
+ * <p>For example if Scoped handlers A, B & C were chained together, then
+ * the calling order would be:</p>
+ * <pre>
+ * A.handle(...)
+ *   A.doScope(...)
+ *     B.doScope(...)
+ *       C.doScope(...)
+ *         A.doHandle(...)
+ *           B.doHandle(...)
+ *              C.doHandle(...)
+ * </pre>
+ *
+ * <p>If non scoped handler X was in the chained A, B, X & C, then
+ * the calling order would be:</p>
+ * <pre>
+ * A.handle(...)
+ *   A.doScope(...)
+ *     B.doScope(...)
+ *       C.doScope(...)
+ *         A.doHandle(...)
+ *           B.doHandle(...)
+ *             X.handle(...)
+ *               C.handle(...)
+ *                 C.doHandle(...)
+ * </pre>
+ *
+ * <p>A typical usage pattern is:</p>
+ * <pre>
+ *     private static class MyHandler extends ScopedHandler
+ *     {
+ *         public void doScope(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ *         {
+ *             try
+ *             {
+ *                 setUpMyScope();
+ *                 super.doScope(target,request,response);
+ *             }
+ *             finally
+ *             {
+ *                 tearDownMyScope();
+ *             }
+ *         }
+ *
+ *         public void doHandle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ *         {
+ *             try
+ *             {
+ *                 doMyHandling();
+ *                 super.doHandle(target,request,response);
+ *             }
+ *             finally
+ *             {
+ *                 cleanupMyHandling();
+ *             }
+ *         }
+ *     }
+ * </pre>
+ */
+public abstract class ScopedHandler extends HandlerWrapper
+{
+    private static final ThreadLocal<ScopedHandler> __outerScope= new ThreadLocal<ScopedHandler>();
+    protected ScopedHandler _outerScope;
+    protected ScopedHandler _nextScope;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        try
+        {
+            _outerScope=__outerScope.get();
+            if (_outerScope==null)
+                __outerScope.set(this);
+
+            super.doStart();
+
+            _nextScope= getChildHandlerByClass(ScopedHandler.class);
+
+        }
+        finally
+        {
+            if (_outerScope==null)
+                __outerScope.set(null);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    @Override
+    public final void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (isStarted())
+        {
+            if (_outerScope==null)
+                doScope(target,baseRequest,request, response);
+            else
+                doHandle(target,baseRequest,request, response);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Scope the handler
+     */
+    public abstract void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException;
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Scope the handler
+     */
+    public final void nextScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException
+    {
+        // this method has been manually inlined in several locations, but
+        // is called protected by an if(never()), so your IDE can find those
+        // locations if this code is changed.
+        if (_nextScope!=null)
+            _nextScope.doScope(target,baseRequest,request, response);
+        else if (_outerScope!=null)
+            _outerScope.doHandle(target,baseRequest,request, response);
+        else
+            doHandle(target,baseRequest,request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Do the handler work within the scope.
+     */
+    public abstract void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException;
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Do the handler work within the scope.
+     */
+    public final void nextHandle(String target, final Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        // this method has been manually inlined in several locations, but
+        // is called protected by an if(never()), so your IDE can find those
+        // locations if this code is changed.
+        if (_nextScope!=null && _nextScope==_handler)
+            _nextScope.doHandle(target,baseRequest,request, response);
+        else if (_handler!=null)
+            _handler.handle(target,baseRequest, request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected boolean never()
+    {
+        return false;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/ShutdownHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/ShutdownHandler.java
new file mode 100644 (file)
index 0000000..e946fb6
--- /dev/null
@@ -0,0 +1,266 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.net.URL;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * A handler that shuts the server down on a valid request. Used to do "soft" restarts from Java. If _exitJvm ist set to true a hard System.exit() call is being
+ * made.
+ *
+ * This handler is a contribution from Johannes Brodwall: https://bugs.eclipse.org/bugs/show_bug.cgi?id=357687
+ *
+ * Usage:
+ *
+ * <pre>
+    Server server = new Server(8080);
+    HandlerList handlers = new HandlerList();
+    handlers.setHandlers(new Handler[]
+    { someOtherHandler, new ShutdownHandler(&quot;secret password&quot;) });
+    server.setHandler(handlers);
+    server.start();
+   </pre>
+ *
+   <pre>
+   public static void attemptShutdown(int port, String shutdownCookie) {
+        try {
+            URL url = new URL("http://localhost:" + port + "/shutdown?token=" + shutdownCookie);
+            HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+            connection.setRequestMethod("POST");
+            connection.getResponseCode();
+            logger.info("Shutting down " + url + ": " + connection.getResponseMessage());
+        } catch (SocketException e) {
+            logger.debug("Not running");
+            // Okay - the server is not running
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+  </pre>
+ */
+public class ShutdownHandler extends HandlerWrapper
+{
+    private static final Logger LOG = Log.getLogger(ShutdownHandler.class);
+
+    private final String _shutdownToken;
+    private boolean _sendShutdownAtStart;
+    private boolean _exitJvm = false;
+
+
+    /**
+     * Creates a listener that lets the server be shut down remotely (but only from localhost).
+     *
+     * @param server
+     *            the Jetty instance that should be shut down
+     * @param shutdownToken
+     *            a secret password to avoid unauthorized shutdown attempts
+     */
+    @Deprecated
+    public ShutdownHandler(Server server, String shutdownToken)
+    {
+        this(shutdownToken);
+    }
+
+    public ShutdownHandler(String shutdownToken)
+    {
+        this(shutdownToken,false,false);
+    }
+    
+    /**
+     * @param shutdownToken
+     * @param sendShutdownAtStart If true, a shutdown is sent as a HTTP post
+     * during startup, which will shutdown any previously running instances of
+     * this server with an identically configured ShutdownHandler
+     */
+    public ShutdownHandler(String shutdownToken, boolean exitJVM, boolean sendShutdownAtStart)
+    {
+        this._shutdownToken = shutdownToken;
+        setExitJvm(exitJVM);
+        setSendShutdownAtStart(sendShutdownAtStart);
+    }
+    
+
+    public void sendShutdown() throws IOException
+    {
+        URL url = new URL(getServerUrl() + "/shutdown?token=" + _shutdownToken);
+        try
+        {
+            HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+            connection.setRequestMethod("POST");
+            connection.getResponseCode();
+            LOG.info("Shutting down " + url + ": " + connection.getResponseMessage());
+        }
+        catch (SocketException e)
+        {
+            LOG.debug("Not running");
+            // Okay - the server is not running
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private String getServerUrl()
+    {
+        NetworkConnector connector=null;
+        for (Connector c: getServer().getConnectors())
+        {
+            if (c instanceof NetworkConnector)
+            {
+                connector=(NetworkConnector)c;
+                break;
+            }
+        }
+
+        if (connector==null)
+            return "http://localhost";
+
+        return "http://localhost:" + connector.getPort();
+    }
+    
+    
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        if (_sendShutdownAtStart)
+            sendShutdown();
+    }
+    
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (!target.equals("/shutdown"))
+        {
+            super.handle(target,baseRequest,request,response);
+            return;
+        }
+
+        if (!request.getMethod().equals("POST"))
+        {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        if (!hasCorrectSecurityToken(request))
+        {
+            LOG.warn("Unauthorized tokenless shutdown attempt from " + request.getRemoteAddr());
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return;
+        }
+        if (!requestFromLocalhost(baseRequest))
+        {
+            LOG.warn("Unauthorized non-loopback shutdown attempt from " + request.getRemoteAddr());
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return;
+        }
+
+        LOG.info("Shutting down by request from " + request.getRemoteAddr());
+
+        final Server server=getServer();
+        new Thread()
+        {
+            @Override
+            public void run ()
+            {
+                try
+                {
+                    shutdownServer(server);
+                }
+                catch (InterruptedException e)
+                {
+                    LOG.ignore(e);
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException("Shutting down server",e);
+                }
+            }
+        }.start();
+    }
+
+    private boolean requestFromLocalhost(Request request)
+    {
+        InetSocketAddress addr = request.getRemoteInetSocketAddress();
+        if (addr == null)
+        {
+            return false;
+        }
+        return addr.getAddress().isLoopbackAddress();
+    }
+
+    private boolean hasCorrectSecurityToken(HttpServletRequest request)
+    {
+        String tok = request.getParameter("token");
+        LOG.debug("Token: {}", tok);
+        return _shutdownToken.equals(tok);
+    }
+
+    private void shutdownServer(Server server) throws Exception
+    {
+        server.stop();
+
+        if (_exitJvm)
+        {
+            System.exit(0);
+        }
+    }
+
+    public void setExitJvm(boolean exitJvm)
+    {
+        this._exitJvm = exitJvm;
+    }
+
+    public boolean isSendShutdownAtStart()
+    {
+        return _sendShutdownAtStart;
+    }
+
+    public void setSendShutdownAtStart(boolean sendShutdownAtStart)
+    {
+        _sendShutdownAtStart = sendShutdownAtStart;
+    }
+
+    public String getShutdownToken()
+    {
+        return _shutdownToken;
+    }
+
+    public boolean isExitJvm()
+    {
+        return _exitJvm;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/StatisticsHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/StatisticsHandler.java
new file mode 100644 (file)
index 0000000..98bb413
--- /dev/null
@@ -0,0 +1,571 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.AsyncContextEvent;
+import org.eclipse.jetty.server.HttpChannelState;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.Graceful;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+
+@ManagedObject("Request Statistics Gathering")
+public class StatisticsHandler extends HandlerWrapper implements Graceful
+{
+    private final AtomicLong _statsStartedAt = new AtomicLong();
+
+    private final CounterStatistic _requestStats = new CounterStatistic();
+    private final SampleStatistic _requestTimeStats = new SampleStatistic();
+    private final CounterStatistic _dispatchedStats = new CounterStatistic();
+    private final SampleStatistic _dispatchedTimeStats = new SampleStatistic();
+    private final CounterStatistic _asyncWaitStats = new CounterStatistic();
+
+    private final AtomicInteger _asyncDispatches = new AtomicInteger();
+    private final AtomicInteger _expires = new AtomicInteger();
+
+    private final AtomicInteger _responses1xx = new AtomicInteger();
+    private final AtomicInteger _responses2xx = new AtomicInteger();
+    private final AtomicInteger _responses3xx = new AtomicInteger();
+    private final AtomicInteger _responses4xx = new AtomicInteger();
+    private final AtomicInteger _responses5xx = new AtomicInteger();
+    private final AtomicLong _responsesTotalBytes = new AtomicLong();
+
+    private final AtomicReference<FutureCallback> _shutdown=new AtomicReference<>();
+    
+    private final AsyncListener _onCompletion = new AsyncListener()
+    {
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+            _expires.incrementAndGet();
+        }
+        
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+            event.getAsyncContext().addListener(this);
+        }
+        
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {
+            HttpChannelState state = ((AsyncContextEvent)event).getHttpChannelState();
+
+            Request request = state.getBaseRequest();
+            final long elapsed = System.currentTimeMillis()-request.getTimeStamp();
+
+            long d=_requestStats.decrement();
+            _requestTimeStats.set(elapsed);
+
+            updateResponse(request);
+
+            _asyncWaitStats.decrement();
+            
+            // If we have no more dispatches, should we signal shutdown?
+            if (d==0)
+            {
+                FutureCallback shutdown = _shutdown.get();
+                if (shutdown!=null)
+                    shutdown.succeeded();
+            }   
+        }
+    };
+
+    /**
+     * Resets the current request statistics.
+     */
+    @ManagedOperation(value="resets statistics", impact="ACTION")
+    public void statsReset()
+    {
+        _statsStartedAt.set(System.currentTimeMillis());
+
+        _requestStats.reset();
+        _requestTimeStats.reset();
+        _dispatchedStats.reset();
+        _dispatchedTimeStats.reset();
+        _asyncWaitStats.reset();
+
+        _asyncDispatches.set(0);
+        _expires.set(0);
+        _responses1xx.set(0);
+        _responses2xx.set(0);
+        _responses3xx.set(0);
+        _responses4xx.set(0);
+        _responses5xx.set(0);
+        _responsesTotalBytes.set(0L);
+    }
+
+    @Override
+    public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
+    {
+        _dispatchedStats.increment();
+
+        final long start;
+        HttpChannelState state = request.getHttpChannelState();
+        if (state.isInitial())
+        {
+            // new request
+            _requestStats.increment();
+            start = request.getTimeStamp();
+        }
+        else
+        {
+            // resumed request
+            start = System.currentTimeMillis();
+            _asyncDispatches.incrementAndGet();
+        }
+
+        try
+        {
+            super.handle(path, request, httpRequest, httpResponse);
+        }
+        finally
+        {
+            final long now = System.currentTimeMillis();
+            final long dispatched=now-start;
+
+            _dispatchedStats.decrement();
+            _dispatchedTimeStats.set(dispatched);
+
+            if (state.isSuspended())
+            {
+                if (state.isInitial())
+                {
+                    state.addListener(_onCompletion);
+                    _asyncWaitStats.increment();
+                }
+            }
+            else if (state.isInitial())
+            {
+                long d=_requestStats.decrement();
+                _requestTimeStats.set(dispatched);
+                updateResponse(request);
+                
+                // If we have no more dispatches, should we signal shutdown?
+                FutureCallback shutdown = _shutdown.get();
+                if (shutdown!=null)
+                {
+                    httpResponse.flushBuffer();
+                    if (d==0)
+                        shutdown.succeeded();
+                }   
+            }
+            // else onCompletion will handle it.
+        }
+    }
+
+    private void updateResponse(Request request)
+    {
+        Response response = request.getResponse();
+        switch (response.getStatus() / 100)
+        {
+            case 0:
+                if (request.isHandled())
+                    _responses2xx.incrementAndGet();
+                else
+                    _responses4xx.incrementAndGet();
+                break;
+            case 1:
+                _responses1xx.incrementAndGet();
+                break;
+            case 2:
+                _responses2xx.incrementAndGet();
+                break;
+            case 3:
+                _responses3xx.incrementAndGet();
+                break;
+            case 4:
+                _responses4xx.incrementAndGet();
+                break;
+            case 5:
+                _responses5xx.incrementAndGet();
+                break;
+            default:
+                break;
+        }
+        _responsesTotalBytes.addAndGet(response.getContentCount());
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        _shutdown.set(null);
+        super.doStart();
+        statsReset();
+    }
+    
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        FutureCallback shutdown = _shutdown.get();
+        if (shutdown!=null && !shutdown.isDone())
+            shutdown.failed(new TimeoutException());
+    }
+
+    /**
+     * @return the number of requests handled by this handler
+     * since {@link #statsReset()} was last called, excluding
+     * active requests
+     * @see #getAsyncDispatches()
+     */
+    @ManagedAttribute("number of requests")
+    public int getRequests()
+    {
+        return (int)_requestStats.getTotal();
+    }
+
+    /**
+     * @return the number of requests currently active.
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("number of requests currently active")
+    public int getRequestsActive()
+    {
+        return (int)_requestStats.getCurrent();
+    }
+
+    /**
+     * @return the maximum number of active requests
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("maximum number of active requests")
+    public int getRequestsActiveMax()
+    {
+        return (int)_requestStats.getMax();
+    }
+
+    /**
+     * @return the maximum time (in milliseconds) of request handling
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("maximum time spend handling requests (in ms)")
+    public long getRequestTimeMax()
+    {
+        return _requestTimeStats.getMax();
+    }
+
+    /**
+     * @return the total time (in milliseconds) of requests handling
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("total time spend in all request handling (in ms)")
+    public long getRequestTimeTotal()
+    {
+        return _requestTimeStats.getTotal();
+    }
+
+    /**
+     * @return the mean time (in milliseconds) of request handling
+     * since {@link #statsReset()} was last called.
+     * @see #getRequestTimeTotal()
+     * @see #getRequests()
+     */
+    @ManagedAttribute("mean time spent handling requests (in ms)")
+    public double getRequestTimeMean()
+    {
+        return _requestTimeStats.getMean();
+    }
+
+    /**
+     * @return the standard deviation of time (in milliseconds) of request handling
+     * since {@link #statsReset()} was last called.
+     * @see #getRequestTimeTotal()
+     * @see #getRequests()
+     */
+    @ManagedAttribute("standard deviation for request handling (in ms)")
+    public double getRequestTimeStdDev()
+    {
+        return _requestTimeStats.getStdDev();
+    }
+
+    /**
+     * @return the number of dispatches seen by this handler
+     * since {@link #statsReset()} was last called, excluding
+     * active dispatches
+     */
+    @ManagedAttribute("number of dispatches")
+    public int getDispatched()
+    {
+        return (int)_dispatchedStats.getTotal();
+    }
+
+    /**
+     * @return the number of dispatches currently in this handler
+     * since {@link #statsReset()} was last called, including
+     * resumed requests
+     */
+    @ManagedAttribute("number of dispatches currently active")
+    public int getDispatchedActive()
+    {
+        return (int)_dispatchedStats.getCurrent();
+    }
+
+    /**
+     * @return the max number of dispatches currently in this handler
+     * since {@link #statsReset()} was last called, including
+     * resumed requests
+     */
+    @ManagedAttribute("maximum number of active dispatches being handled")
+    public int getDispatchedActiveMax()
+    {
+        return (int)_dispatchedStats.getMax();
+    }
+
+    /**
+     * @return the maximum time (in milliseconds) of request dispatch
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("maximum time spend in dispatch handling")
+    public long getDispatchedTimeMax()
+    {
+        return _dispatchedTimeStats.getMax();
+    }
+
+    /**
+     * @return the total time (in milliseconds) of requests handling
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("total time spent in dispatch handling (in ms)")
+    public long getDispatchedTimeTotal()
+    {
+        return _dispatchedTimeStats.getTotal();
+    }
+
+    /**
+     * @return the mean time (in milliseconds) of request handling
+     * since {@link #statsReset()} was last called.
+     * @see #getRequestTimeTotal()
+     * @see #getRequests()
+     */
+    @ManagedAttribute("mean time spent in dispatch handling (in ms)")
+    public double getDispatchedTimeMean()
+    {
+        return _dispatchedTimeStats.getMean();
+    }
+
+    /**
+     * @return the standard deviation of time (in milliseconds) of request handling
+     * since {@link #statsReset()} was last called.
+     * @see #getRequestTimeTotal()
+     * @see #getRequests()
+     */
+    @ManagedAttribute("standard deviation for dispatch handling (in ms)")
+    public double getDispatchedTimeStdDev()
+    {
+        return _dispatchedTimeStats.getStdDev();
+    }
+
+    /**
+     * @return the number of requests handled by this handler
+     * since {@link #statsReset()} was last called, including
+     * resumed requests
+     * @see #getAsyncDispatches()
+     */
+    @ManagedAttribute("total number of async requests")
+    public int getAsyncRequests()
+    {
+        return (int)_asyncWaitStats.getTotal();
+    }
+
+    /**
+     * @return the number of requests currently suspended.
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("currently waiting async requests")
+    public int getAsyncRequestsWaiting()
+    {
+        return (int)_asyncWaitStats.getCurrent();
+    }
+
+    /**
+     * @return the maximum number of current suspended requests
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("maximum number of waiting async requests")
+    public int getAsyncRequestsWaitingMax()
+    {
+        return (int)_asyncWaitStats.getMax();
+    }
+
+    /**
+     * @return the number of requests that have been asynchronously dispatched
+     */
+    @ManagedAttribute("number of requested that have been asynchronously dispatched")
+    public int getAsyncDispatches()
+    {
+        return _asyncDispatches.get();
+    }
+
+    /**
+     * @return the number of requests that expired while suspended.
+     * @see #getAsyncDispatches()
+     */
+    @ManagedAttribute("number of async requests requests that have expired")
+    public int getExpires()
+    {
+        return _expires.get();
+    }
+
+    /**
+     * @return the number of responses with a 1xx status returned by this context
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("number of requests with 1xx response status")
+    public int getResponses1xx()
+    {
+        return _responses1xx.get();
+    }
+
+    /**
+     * @return the number of responses with a 2xx status returned by this context
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("number of requests with 2xx response status")
+    public int getResponses2xx()
+    {
+        return _responses2xx.get();
+    }
+
+    /**
+     * @return the number of responses with a 3xx status returned by this context
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("number of requests with 3xx response status")
+    public int getResponses3xx()
+    {
+        return _responses3xx.get();
+    }
+
+    /**
+     * @return the number of responses with a 4xx status returned by this context
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("number of requests with 4xx response status")
+    public int getResponses4xx()
+    {
+        return _responses4xx.get();
+    }
+
+    /**
+     * @return the number of responses with a 5xx status returned by this context
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("number of requests with 5xx response status")
+    public int getResponses5xx()
+    {
+        return _responses5xx.get();
+    }
+
+    /**
+     * @return the milliseconds since the statistics were started with {@link #statsReset()}.
+     */
+    @ManagedAttribute("time in milliseconds stats have been collected for")
+    public long getStatsOnMs()
+    {
+        return System.currentTimeMillis() - _statsStartedAt.get();
+    }
+
+    /**
+     * @return the total bytes of content sent in responses
+     */
+    @ManagedAttribute("total number of bytes across all responses")
+    public long getResponsesBytesTotal()
+    {
+        return _responsesTotalBytes.get();
+    }
+
+    public String toStatsHTML()
+    {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("<h1>Statistics:</h1>\n");
+        sb.append("Statistics gathering started ").append(getStatsOnMs()).append("ms ago").append("<br />\n");
+
+        sb.append("<h2>Requests:</h2>\n");
+        sb.append("Total requests: ").append(getRequests()).append("<br />\n");
+        sb.append("Active requests: ").append(getRequestsActive()).append("<br />\n");
+        sb.append("Max active requests: ").append(getRequestsActiveMax()).append("<br />\n");
+        sb.append("Total requests time: ").append(getRequestTimeTotal()).append("<br />\n");
+        sb.append("Mean request time: ").append(getRequestTimeMean()).append("<br />\n");
+        sb.append("Max request time: ").append(getRequestTimeMax()).append("<br />\n");
+        sb.append("Request time standard deviation: ").append(getRequestTimeStdDev()).append("<br />\n");
+
+
+        sb.append("<h2>Dispatches:</h2>\n");
+        sb.append("Total dispatched: ").append(getDispatched()).append("<br />\n");
+        sb.append("Active dispatched: ").append(getDispatchedActive()).append("<br />\n");
+        sb.append("Max active dispatched: ").append(getDispatchedActiveMax()).append("<br />\n");
+        sb.append("Total dispatched time: ").append(getDispatchedTimeTotal()).append("<br />\n");
+        sb.append("Mean dispatched time: ").append(getDispatchedTimeMean()).append("<br />\n");
+        sb.append("Max dispatched time: ").append(getDispatchedTimeMax()).append("<br />\n");
+        sb.append("Dispatched time standard deviation: ").append(getDispatchedTimeStdDev()).append("<br />\n");
+
+
+        sb.append("Total requests suspended: ").append(getAsyncRequests()).append("<br />\n");
+        sb.append("Total requests expired: ").append(getExpires()).append("<br />\n");
+        sb.append("Total requests resumed: ").append(getAsyncDispatches()).append("<br />\n");
+
+        sb.append("<h2>Responses:</h2>\n");
+        sb.append("1xx responses: ").append(getResponses1xx()).append("<br />\n");
+        sb.append("2xx responses: ").append(getResponses2xx()).append("<br />\n");
+        sb.append("3xx responses: ").append(getResponses3xx()).append("<br />\n");
+        sb.append("4xx responses: ").append(getResponses4xx()).append("<br />\n");
+        sb.append("5xx responses: ").append(getResponses5xx()).append("<br />\n");
+        sb.append("Bytes sent total: ").append(getResponsesBytesTotal()).append("<br />\n");
+
+        return sb.toString();
+
+    }
+
+    @Override
+    public Future<Void> shutdown()
+    {
+        FutureCallback shutdown=new FutureCallback(false);
+        _shutdown.compareAndSet(null,shutdown);
+        shutdown=_shutdown.get();
+        if (_dispatchedStats.getCurrent()==0)
+            shutdown.succeeded();
+        return shutdown;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/package-info.java b/lib/jetty/org/eclipse/jetty/server/handler/package-info.java
new file mode 100644 (file)
index 0000000..1571d7b
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Core Handler API
+ */
+package org.eclipse.jetty.server.handler;
+
diff --git a/lib/jetty/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java b/lib/jetty/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java
new file mode 100644 (file)
index 0000000..f4c5958
--- /dev/null
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.nio;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.NetworkTrafficServerConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * @deprecated use {@link org.eclipse.jetty.server.NetworkTrafficServerConnector} instead.
+ */
+@Deprecated
+public class NetworkTrafficSelectChannelConnector extends NetworkTrafficServerConnector
+{
+    public NetworkTrafficSelectChannelConnector(Server server)
+    {
+        super(server);
+    }
+
+    public NetworkTrafficSelectChannelConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
+    {
+        super(server, connectionFactory, sslContextFactory);
+    }
+
+    public NetworkTrafficSelectChannelConnector(Server server, ConnectionFactory connectionFactory)
+    {
+        super(server, connectionFactory);
+    }
+
+    public NetworkTrafficSelectChannelConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, int selectors, ConnectionFactory... factories)
+    {
+        super(server, executor, scheduler, pool, acceptors, selectors, factories);
+    }
+
+    public NetworkTrafficSelectChannelConnector(Server server, SslContextFactory sslContextFactory)
+    {
+        super(server, sslContextFactory);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/nio/package-info.java b/lib/jetty/org/eclipse/jetty/server/nio/package-info.java
new file mode 100644 (file)
index 0000000..8450e18
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Core Server Connector
+ */
+package org.eclipse.jetty.server.nio;
+
diff --git a/lib/jetty/org/eclipse/jetty/server/package-info.java b/lib/jetty/org/eclipse/jetty/server/package-info.java
new file mode 100644 (file)
index 0000000..4688304
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Core Server API
+ */
+package org.eclipse.jetty.server;
+
diff --git a/lib/jetty/org/eclipse/jetty/server/session/AbstractSession.java b/lib/jetty/org/eclipse/jetty/server/session/AbstractSession.java
new file mode 100644 (file)
index 0000000..809d9d9
--- /dev/null
@@ -0,0 +1,650 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionContext;
+import javax.servlet.http.HttpSessionEvent;
+
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ *
+ * <p>
+ * Implements {@link javax.servlet.http.HttpSession} from the <code>javax.servlet</code> package.
+ * </p>
+ *
+ */
+@SuppressWarnings("deprecation")
+public abstract class AbstractSession implements AbstractSessionManager.SessionIf
+{
+    final static Logger LOG = SessionHandler.LOG;
+    public final static String SESSION_KNOWN_ONLY_TO_AUTHENTICATED="org.eclipse.jetty.security.sessionKnownOnlytoAuthenticated";
+    private  String _clusterId; // ID without any node (ie "worker") id appended
+    private  String _nodeId;    // ID of session with node(ie "worker") id appended
+    private final AbstractSessionManager _manager;
+    private boolean _idChanged;
+    private final long _created;
+    private long _cookieSet;
+    private long _accessed;         // the time of the last access
+    private long _lastAccessed;     // the time of the last access excluding this one
+    private boolean _invalid;
+    private boolean _doInvalidate;
+    private long _maxIdleMs;
+    private boolean _newSession;
+    private int _requests;
+
+
+
+    /* ------------------------------------------------------------- */
+    protected AbstractSession(AbstractSessionManager abstractSessionManager, HttpServletRequest request)
+    {
+        _manager = abstractSessionManager;
+
+        _newSession=true;
+        _created=System.currentTimeMillis();
+        _clusterId=_manager._sessionIdManager.newSessionId(request,_created);
+        _nodeId=_manager._sessionIdManager.getNodeId(_clusterId,request);
+        _accessed=_created;
+        _lastAccessed=_created;
+        _requests=1;
+        _maxIdleMs=_manager._dftMaxIdleSecs>0?_manager._dftMaxIdleSecs*1000L:-1;
+        if (LOG.isDebugEnabled())
+            LOG.debug("new session & id "+_nodeId+" "+_clusterId);
+    }
+
+    /* ------------------------------------------------------------- */
+    protected AbstractSession(AbstractSessionManager abstractSessionManager, long created, long accessed, String clusterId)
+    {
+        _manager = abstractSessionManager;
+        _created=created;
+        _clusterId=clusterId;
+        _nodeId=_manager._sessionIdManager.getNodeId(_clusterId,null);
+        _accessed=accessed;
+        _lastAccessed=accessed;
+        _requests=1;
+        _maxIdleMs=_manager._dftMaxIdleSecs>0?_manager._dftMaxIdleSecs*1000L:-1;
+        if (LOG.isDebugEnabled())
+            LOG.debug("new session "+_nodeId+" "+_clusterId);
+    }
+
+    /* ------------------------------------------------------------- */
+    /**
+     * asserts that the session is valid
+     */
+    protected void checkValid() throws IllegalStateException
+    {
+        if (_invalid)
+            throw new IllegalStateException();
+    }
+    
+    /* ------------------------------------------------------------- */
+    /** Check to see if session has expired as at the time given.
+     * @param time
+     * @return
+     */
+    protected boolean checkExpiry(long time)
+    {
+        if (_maxIdleMs>0 && _lastAccessed>0 && _lastAccessed + _maxIdleMs < time)
+            return true;
+        return false;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public AbstractSession getSession()
+    {
+        return this;
+    }
+
+    /* ------------------------------------------------------------- */
+    public long getAccessed()
+    {
+        synchronized (this)
+        {
+            return _accessed;
+        }
+    }
+
+    /* ------------------------------------------------------------- */
+    public abstract Map<String,Object> getAttributeMap();
+
+
+    
+
+    /* ------------------------------------------------------------ */
+    public abstract int getAttributes();
+  
+
+
+    /* ------------------------------------------------------------ */
+    public abstract Set<String> getNames();
+  
+
+    /* ------------------------------------------------------------- */
+    public long getCookieSetTime()
+    {
+        return _cookieSet;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public long getCreationTime() throws IllegalStateException
+    {
+        checkValid();
+        return _created;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getId() throws IllegalStateException
+    {
+        return _manager._nodeIdInSessionId?_nodeId:_clusterId;
+    }
+
+    /* ------------------------------------------------------------- */
+    public String getNodeId()
+    {
+        return _nodeId;
+    }
+
+    /* ------------------------------------------------------------- */
+    public String getClusterId()
+    {
+        return _clusterId;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public long getLastAccessedTime() throws IllegalStateException
+    {
+        checkValid();
+        return _lastAccessed;
+    }
+    
+    /* ------------------------------------------------------------- */
+    public void setLastAccessedTime(long time)
+    {
+        _lastAccessed = time;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public int getMaxInactiveInterval()
+    {
+        return (int)(_maxIdleMs/1000);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpSession#getServletContext()
+     */
+    @Override
+    public ServletContext getServletContext()
+    {
+        return _manager._context;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Deprecated
+    @Override
+    public HttpSessionContext getSessionContext() throws IllegalStateException
+    {
+        checkValid();
+        return AbstractSessionManager.__nullSessionContext;
+    }
+
+    /* ------------------------------------------------------------- */
+    /**
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #getAttribute}
+     */
+    @Deprecated
+    @Override
+    public Object getValue(String name) throws IllegalStateException
+    {
+        return getAttribute(name);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public void renewId(HttpServletRequest request)
+    {
+        _manager._sessionIdManager.renewSessionId(getClusterId(), getNodeId(), request); 
+        setIdChanged(true);
+    }
+       
+    /* ------------------------------------------------------------- */
+    public SessionManager getSessionManager()
+    {
+        return _manager;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void setClusterId (String clusterId)
+    {
+        _clusterId = clusterId;
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected void setNodeId (String nodeId)
+    {
+        _nodeId = nodeId;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    protected boolean access(long time)
+    {
+        synchronized(this)
+        {
+            if (_invalid)
+                return false;
+            _newSession=false;
+            _lastAccessed=_accessed;
+            _accessed=time;
+
+            if (checkExpiry(time))
+            {
+                invalidate();
+                return false;
+            }
+            _requests++;
+            return true;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void complete()
+    {
+        synchronized(this)
+        {
+            _requests--;
+            if (_doInvalidate && _requests<=0  )
+                doInvalidate();
+        }
+    }
+
+
+    /* ------------------------------------------------------------- */
+    protected void timeout() throws IllegalStateException
+    {
+        // remove session from context and invalidate other sessions with same ID.
+        _manager.removeSession(this,true);
+
+        // Notify listeners and unbind values
+        boolean do_invalidate=false;
+        synchronized (this)
+        {
+            if (!_invalid)
+            {
+                if (_requests<=0)
+                    do_invalidate=true;
+                else
+                    _doInvalidate=true;
+            }
+        }
+        if (do_invalidate)
+            doInvalidate();
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public void invalidate() throws IllegalStateException
+    {
+        checkValid();
+        // remove session from context and invalidate other sessions with same ID.
+        _manager.removeSession(this,true);
+        doInvalidate();
+    }
+
+    /* ------------------------------------------------------------- */
+    protected void doInvalidate() throws IllegalStateException
+    {
+        try
+        {
+            LOG.debug("invalidate {}",_clusterId);
+            if (isValid())
+                clearAttributes();
+        }
+        finally
+        {
+            synchronized (this)
+            {
+                // mark as invalid
+                _invalid=true;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------- */
+    public abstract void clearAttributes();
+   
+
+    /* ------------------------------------------------------------- */
+    public boolean isIdChanged()
+    {
+        return _idChanged;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public boolean isNew() throws IllegalStateException
+    {
+        checkValid();
+        return _newSession;
+    }
+
+    /* ------------------------------------------------------------- */
+    /**
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #setAttribute}
+     */
+    @Deprecated
+    @Override
+    public void putValue(java.lang.String name, java.lang.Object value) throws IllegalStateException
+    {
+        changeAttribute(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void removeAttribute(String name)
+    {
+        setAttribute(name,null);
+    }
+
+    /* ------------------------------------------------------------- */
+    /**
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #removeAttribute}
+     */
+    @Deprecated
+    @Override
+    public void removeValue(java.lang.String name) throws IllegalStateException
+    {
+        removeAttribute(name);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings({ "unchecked" })
+    @Override
+    public Enumeration<String> getAttributeNames()
+    {
+        synchronized (this)
+        {
+            checkValid();
+            return doGetAttributeNames();
+        }
+    }
+    
+    /* ------------------------------------------------------------- */
+    /**
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #getAttributeNames}
+     */
+    @Deprecated
+    @Override
+    public String[] getValueNames() throws IllegalStateException
+    {
+        synchronized(this)
+        {
+            checkValid();
+            Enumeration<String> anames = doGetAttributeNames();
+            if (anames == null)
+                return new String[0];
+            ArrayList<String> names = new ArrayList<String>();
+            while (anames.hasMoreElements())
+                names.add(anames.nextElement());
+            return names.toArray(new String[names.size()]);
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public abstract Object doPutOrRemove(String name, Object value);
+
+    /* ------------------------------------------------------------ */
+    public abstract Object doGet(String name);
+    
+    
+    /* ------------------------------------------------------------ */
+    public abstract Enumeration<String> doGetAttributeNames();
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object getAttribute(String name)
+    {
+        synchronized (this)
+        {
+            checkValid();
+            return doGet(name);
+        }
+    }
+   
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setAttribute(String name, Object value)
+    {
+        changeAttribute(name,value);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name
+     * @param value
+     * @deprecated use changeAttribute(String,Object) instead
+     * @return
+     */
+    protected boolean updateAttribute (String name, Object value)
+    {
+        Object old=null;
+        synchronized (this)
+        {
+            checkValid();
+            old=doPutOrRemove(name,value);
+        }
+
+        if (value==null || !value.equals(old))
+        {
+            if (old!=null)
+                unbindValue(name,old);
+            if (value!=null)
+                bindValue(name,value);
+
+            _manager.doSessionAttributeListeners(this,name,old,value);
+            return true;
+        }
+        return false;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Either set (perhaps replace) or remove the value of the attribute
+     * in the session. The appropriate session attribute listeners are
+     * also called.
+     * 
+     * @param name
+     * @param value
+     * @return
+     */
+    protected Object changeAttribute (String name, Object value)
+    {
+        Object old=null;
+        synchronized (this)
+        {
+            checkValid();
+            old=doPutOrRemove(name,value);
+        }
+
+        callSessionAttributeListeners(name, value, old);
+
+        return old;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Call binding and attribute listeners based on the new and old
+     * values of the attribute.
+     * 
+     * @param name name of the attribute
+     * @param newValue  new value of the attribute
+     * @param oldValue previous value of the attribute
+     */
+    protected void callSessionAttributeListeners (String name, Object newValue, Object oldValue)
+    {
+        if (newValue==null || !newValue.equals(oldValue))
+        {
+            if (oldValue!=null)
+                unbindValue(name,oldValue);
+            if (newValue!=null)
+                bindValue(name,newValue);
+
+            _manager.doSessionAttributeListeners(this,name,oldValue,newValue);
+        }
+    }
+  
+
+    /* ------------------------------------------------------------- */
+    public void setIdChanged(boolean changed)
+    {
+        _idChanged=changed;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public void setMaxInactiveInterval(int secs)
+    {
+        _maxIdleMs=(long)secs*1000L;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public String toString()
+    {
+        return this.getClass().getName()+":"+getId()+"@"+hashCode();
+    }
+
+    /* ------------------------------------------------------------- */
+    /** If value implements HttpSessionBindingListener, call valueBound() */
+    public void bindValue(java.lang.String name, Object value)
+    {
+        if (value!=null&&value instanceof HttpSessionBindingListener)
+            ((HttpSessionBindingListener)value).valueBound(new HttpSessionBindingEvent(this,name));
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isValid()
+    {
+        return !_invalid;
+    }
+
+    /* ------------------------------------------------------------- */
+    protected void cookieSet()
+    {
+        synchronized (this)
+        {
+            _cookieSet=_accessed;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getRequests()
+    {
+        synchronized (this)
+        {
+            return _requests;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRequests(int requests)
+    {
+        synchronized (this)
+        {
+            _requests=requests;
+        }
+    }
+
+    /* ------------------------------------------------------------- */
+    /** If value implements HttpSessionBindingListener, call valueUnbound() */
+    public void unbindValue(java.lang.String name, Object value)
+    {
+        if (value!=null&&value instanceof HttpSessionBindingListener)
+            ((HttpSessionBindingListener)value).valueUnbound(new HttpSessionBindingEvent(this,name));
+    }
+
+    /* ------------------------------------------------------------- */
+    public void willPassivate()
+    {
+        synchronized(this)
+        {
+            HttpSessionEvent event = new HttpSessionEvent(this);
+            for (Iterator<Object> iter = getAttributeMap().values().iterator(); iter.hasNext();)
+            {
+                Object value = iter.next();
+                if (value instanceof HttpSessionActivationListener)
+                {
+                    HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
+                    listener.sessionWillPassivate(event);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------- */
+    public void didActivate()
+    {
+        synchronized(this)
+        {
+            HttpSessionEvent event = new HttpSessionEvent(this);
+            for (Iterator<Object> iter = getAttributeMap().values().iterator(); iter.hasNext();)
+            {
+                Object value = iter.next();
+                if (value instanceof HttpSessionActivationListener)
+                {
+                    HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
+                    listener.sessionDidActivate(event);
+                }
+            }
+        }
+    }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/AbstractSessionIdManager.java b/lib/jetty/org/eclipse/jetty/server/session/AbstractSessionIdManager.java
new file mode 100644 (file)
index 0000000..9414408
--- /dev/null
@@ -0,0 +1,279 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class AbstractSessionIdManager extends AbstractLifeCycle implements SessionIdManager
+{
+    private static final Logger LOG = Log.getLogger(AbstractSessionIdManager.class);
+
+    private final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId";
+
+    protected Random _random;
+    protected boolean _weakRandom;
+    protected String _workerName;
+    protected String _workerAttr;
+    protected long _reseed=100000L;
+
+    /* ------------------------------------------------------------ */
+    public AbstractSessionIdManager()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public AbstractSessionIdManager(Random random)
+    {
+        _random=random;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the workname. If set, the workername is dot appended to the session
+     * ID and can be used to assist session affinity in a load balancer.
+     *
+     * @return String or null
+     */
+    @Override
+    public String getWorkerName()
+    {
+        return _workerName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the workname. If set, the workername is dot appended to the session
+     * ID and can be used to assist session affinity in a load balancer.
+     * A worker name starting with $ is used as a request attribute name to
+     * lookup the worker name that can be dynamically set by a request
+     * customiser.
+     *
+     * @param workerName
+     */
+    public void setWorkerName(String workerName)
+    {
+        if (isRunning())
+            throw new IllegalStateException(getState());
+        if (workerName.contains("."))
+            throw new IllegalArgumentException("Name cannot contain '.'");
+        _workerName=workerName;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Random getRandom()
+    {
+        return _random;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void setRandom(Random random)
+    {
+        _random=random;
+        _weakRandom=false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the reseed probability
+     */
+    public long getReseed()
+    {
+        return _reseed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the reseed probability.
+     * @param reseed  If non zero then when a random long modulo the reseed value == 1, the {@link SecureRandom} will be reseeded.
+     */
+    public void setReseed(long reseed)
+    {
+        _reseed = reseed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new session id if necessary.
+     *
+     * @see org.eclipse.jetty.server.SessionIdManager#newSessionId(javax.servlet.http.HttpServletRequest, long)
+     */
+    @Override
+    public String newSessionId(HttpServletRequest request, long created)
+    {
+        synchronized (this)
+        {
+            if (request==null)
+                return newSessionId(created);
+
+            // A requested session ID can only be used if it is in use already.
+            String requested_id=request.getRequestedSessionId();
+            if (requested_id!=null)
+            {
+                String cluster_id=getClusterId(requested_id);
+                if (idInUse(cluster_id))
+                    return cluster_id;
+            }
+
+            // Else reuse any new session ID already defined for this request.
+            String new_id=(String)request.getAttribute(__NEW_SESSION_ID);
+            if (new_id!=null&&idInUse(new_id))
+                return new_id;
+
+            // pick a new unique ID!
+            String id = newSessionId(request.hashCode());
+
+            request.setAttribute(__NEW_SESSION_ID,id);
+            return id;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public String newSessionId(long seedTerm)
+    {
+        // pick a new unique ID!
+        String id=null;
+        while (id==null||id.length()==0||idInUse(id))
+        {
+            long r0=_weakRandom
+                    ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
+                    :_random.nextLong();
+            if (r0<0)
+                r0=-r0;
+                    
+            // random chance to reseed
+            if (_reseed>0 && (r0%_reseed)== 1L)
+            {
+                LOG.debug("Reseeding {}",this);
+                if (_random instanceof SecureRandom)
+                {
+                    SecureRandom secure = (SecureRandom)_random;
+                    secure.setSeed(secure.generateSeed(8));
+                }
+                else
+                {
+                    _random.setSeed(_random.nextLong()^System.currentTimeMillis()^seedTerm^Runtime.getRuntime().freeMemory());
+                }
+            }
+            
+            long r1=_weakRandom
+                ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
+                :_random.nextLong();
+            if (r1<0)
+                r1=-r1;
+            
+            id=Long.toString(r0,36)+Long.toString(r1,36);
+
+            //add in the id of the node to ensure unique id across cluster
+            //NOTE this is different to the node suffix which denotes which node the request was received on
+            if (_workerName!=null)
+                id=_workerName + id;
+    
+        }
+        return id;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public abstract void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request);
+
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+       initRandom();
+       _workerAttr=(_workerName!=null && _workerName.startsWith("$"))?_workerName.substring(1):null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set up a random number generator for the sessionids.
+     *
+     * By preference, use a SecureRandom but allow to be injected.
+     */
+    public void initRandom ()
+    {
+        if (_random==null)
+        {
+            try
+            {
+                _random=new SecureRandom();
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Could not generate SecureRandom for session-id randomness",e);
+                _random=new Random();
+                _weakRandom=true;
+            }
+        }
+        else
+            _random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory());
+    }
+
+    /** Get the session ID with any worker ID.
+     *
+     * @param clusterId
+     * @param request
+     * @return sessionId plus any worker ID.
+     */
+    @Override
+    public String getNodeId(String clusterId, HttpServletRequest request)
+    {
+        if (_workerName!=null)
+        {
+            if (_workerAttr==null)
+                return clusterId+'.'+_workerName;
+
+            String worker=(String)request.getAttribute(_workerAttr);
+            if (worker!=null)
+                return clusterId+'.'+worker;
+        }
+    
+        return clusterId;
+    }
+
+    /** Get the session ID without any worker ID.
+     *
+     * @param nodeId the node id
+     * @return sessionId without any worker ID.
+     */
+    @Override
+    public String getClusterId(String nodeId)
+    {
+        int dot=nodeId.lastIndexOf('.');
+        return (dot>0)?nodeId.substring(0,dot):nodeId;
+    }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/AbstractSessionManager.java b/lib/jetty/org/eclipse/jetty/server/session/AbstractSessionManager.java
new file mode 100644 (file)
index 0000000..1f966c5
--- /dev/null
@@ -0,0 +1,1052 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import static java.lang.Math.round;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionContext;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+
+/* ------------------------------------------------------------ */
+/**
+ * An Abstract implementation of SessionManager. The partial implementation of
+ * SessionManager interface provides the majority of the handling required to
+ * implement a SessionManager. Concrete implementations of SessionManager based
+ * on AbstractSessionManager need only implement the newSession method to return
+ * a specialised version of the Session inner class that provides an attribute
+ * Map.
+ * <p>
+ */
+@SuppressWarnings("deprecation")
+@ManagedObject("Abstract Session Manager")
+public abstract class AbstractSessionManager extends ContainerLifeCycle implements SessionManager
+{
+    final static Logger __log = SessionHandler.LOG;
+
+    public Set<SessionTrackingMode> __defaultSessionTrackingModes =
+        Collections.unmodifiableSet(
+            new HashSet<SessionTrackingMode>(
+                    Arrays.asList(new SessionTrackingMode[]{SessionTrackingMode.COOKIE,SessionTrackingMode.URL})));
+
+    
+
+    /* ------------------------------------------------------------ */
+    public final static int __distantFuture=60*60*24*7*52*20;
+
+    static final HttpSessionContext __nullSessionContext=new HttpSessionContext()
+    {
+        @Override
+        public HttpSession getSession(String sessionId)
+        {
+            return null;
+        }
+
+        @Override
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        public Enumeration getIds()
+        {
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        }
+    };
+
+    private boolean _usingCookies=true;
+
+    /* ------------------------------------------------------------ */
+    // Setting of max inactive interval for new sessions
+    // -1 means no timeout
+    protected int _dftMaxIdleSecs=-1;
+    protected SessionHandler _sessionHandler;
+    protected boolean _httpOnly=false;
+    protected SessionIdManager _sessionIdManager;
+    protected boolean _secureCookies=false;
+    protected boolean _secureRequestOnly=true;
+
+    protected final List<HttpSessionAttributeListener> _sessionAttributeListeners = new CopyOnWriteArrayList<HttpSessionAttributeListener>();
+    protected final List<HttpSessionListener> _sessionListeners= new CopyOnWriteArrayList<HttpSessionListener>();
+    protected final List<HttpSessionIdListener> _sessionIdListeners = new CopyOnWriteArrayList<HttpSessionIdListener>();
+
+    protected ClassLoader _loader;
+    protected ContextHandler.Context _context;
+    protected String _sessionCookie=__DefaultSessionCookie;
+    protected String _sessionIdPathParameterName = __DefaultSessionIdPathParameterName;
+    protected String _sessionIdPathParameterNamePrefix =";"+ _sessionIdPathParameterName +"=";
+    protected String _sessionDomain;
+    protected String _sessionPath;
+    protected int _maxCookieAge=-1;
+    protected int _refreshCookieAge;
+    protected boolean _nodeIdInSessionId;
+    protected boolean _checkingRemoteSessionIdEncoding;
+    protected String _sessionComment;
+
+    public Set<SessionTrackingMode> _sessionTrackingModes;
+
+    private boolean _usingURLs;
+
+    protected final CounterStatistic _sessionsStats = new CounterStatistic();
+    protected final SampleStatistic _sessionTimeStats = new SampleStatistic();
+
+
+    /* ------------------------------------------------------------ */
+    public AbstractSessionManager()
+    {
+        setSessionTrackingModes(__defaultSessionTrackingModes);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ContextHandler.Context getContext()
+    {
+        return _context;
+    }
+
+    /* ------------------------------------------------------------ */
+    public ContextHandler getContextHandler()
+    {
+        return _context.getContextHandler();
+    }
+
+    @ManagedAttribute("path of the session cookie, or null for default")
+    public String getSessionPath()
+    {
+        return _sessionPath;
+    }
+
+    @ManagedAttribute("if greater the zero, the time in seconds a session cookie will last for")
+    public int getMaxCookieAge()
+    {
+        return _maxCookieAge;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public HttpCookie access(HttpSession session,boolean secure)
+    {
+        long now=System.currentTimeMillis();
+
+        AbstractSession s = ((SessionIf)session).getSession();
+
+       if (s.access(now))
+       {
+            // Do we need to refresh the cookie?
+            if (isUsingCookies() &&
+                (s.isIdChanged() ||
+                (getSessionCookieConfig().getMaxAge()>0 && getRefreshCookieAge()>0 && ((now-s.getCookieSetTime())/1000>getRefreshCookieAge()))
+                )
+               )
+            {
+                HttpCookie cookie=getSessionCookie(session,_context==null?"/":(_context.getContextPath()),secure);
+                s.cookieSet();
+                s.setIdChanged(false);
+                return cookie;
+            }
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void addEventListener(EventListener listener)
+    {
+        if (listener instanceof HttpSessionAttributeListener)
+            _sessionAttributeListeners.add((HttpSessionAttributeListener)listener);
+        if (listener instanceof HttpSessionListener)
+            _sessionListeners.add((HttpSessionListener)listener);
+        if (listener instanceof HttpSessionIdListener)
+            _sessionIdListeners.add((HttpSessionIdListener)listener);
+        addBean(listener,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void clearEventListeners()
+    {
+        for (EventListener e :getBeans(EventListener.class))
+            removeBean(e);
+        _sessionAttributeListeners.clear();
+        _sessionListeners.clear();
+        _sessionIdListeners.clear();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void complete(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        s.complete();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStart() throws Exception
+    {
+        _context=ContextHandler.getCurrentContext();
+        _loader=Thread.currentThread().getContextClassLoader();
+
+        if (_sessionIdManager==null)
+        {
+            final Server server=getSessionHandler().getServer();
+            synchronized (server)
+            {
+                _sessionIdManager=server.getSessionIdManager();
+                if (_sessionIdManager==null)
+                {
+                    //create a default SessionIdManager and set it as the shared
+                    //SessionIdManager for the Server, being careful NOT to use
+                    //the webapp context's classloader, otherwise if the context
+                    //is stopped, the classloader is leaked.
+                    ClassLoader serverLoader = server.getClass().getClassLoader();
+                    try
+                    {
+                        Thread.currentThread().setContextClassLoader(serverLoader);
+                        _sessionIdManager=new HashSessionIdManager();
+                        server.setSessionIdManager(_sessionIdManager);
+                        server.manage(_sessionIdManager);
+                        _sessionIdManager.start();
+                    }
+                    finally
+                    {
+                        Thread.currentThread().setContextClassLoader(_loader);
+                    }
+                }
+
+                // server session id is never managed by this manager
+                addBean(_sessionIdManager,false);
+            }
+        }
+        
+
+        // Look for a session cookie name
+        if (_context!=null)
+        {
+            String tmp=_context.getInitParameter(SessionManager.__SessionCookieProperty);
+            if (tmp!=null)
+                _sessionCookie=tmp;
+
+            tmp=_context.getInitParameter(SessionManager.__SessionIdPathParameterNameProperty);
+            if (tmp!=null)
+                setSessionIdPathParameterName(tmp);
+
+            // set up the max session cookie age if it isn't already
+            if (_maxCookieAge==-1)
+            {
+                tmp=_context.getInitParameter(SessionManager.__MaxAgeProperty);
+                if (tmp!=null)
+                    _maxCookieAge=Integer.parseInt(tmp.trim());
+            }
+
+            // set up the session domain if it isn't already
+            if (_sessionDomain==null)
+                _sessionDomain=_context.getInitParameter(SessionManager.__SessionDomainProperty);
+
+            // set up the sessionPath if it isn't already
+            if (_sessionPath==null)
+                _sessionPath=_context.getInitParameter(SessionManager.__SessionPathProperty);
+
+            tmp=_context.getInitParameter(SessionManager.__CheckRemoteSessionEncoding);
+            if (tmp!=null)
+                _checkingRemoteSessionIdEncoding=Boolean.parseBoolean(tmp);
+        }
+
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStop() throws Exception
+    {
+        super.doStop();
+
+        shutdownSessions();
+
+        _loader=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the httpOnly.
+     */
+    @Override
+    @ManagedAttribute("true if cookies use the http only flag")
+    public boolean getHttpOnly()
+    {
+        return _httpOnly;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public HttpSession getHttpSession(String nodeId)
+    {
+        String cluster_id = getSessionIdManager().getClusterId(nodeId);
+
+        AbstractSession session = getSession(cluster_id);
+        if (session!=null && !session.getNodeId().equals(nodeId))
+            session.setIdChanged(true);
+        return session;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the SessionIdManager used for cross context session management
+     */
+    @Override
+    @ManagedAttribute("Session ID Manager")
+    public SessionIdManager getSessionIdManager()
+    {
+        return _sessionIdManager;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return seconds
+     */
+    @Override
+    @ManagedAttribute("defailt maximum time a session may be idle for (in s)")
+    public int getMaxInactiveInterval()
+    {
+        return _dftMaxIdleSecs;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return maximum number of sessions
+     */
+    @ManagedAttribute("maximum number of simultaneous sessions")
+    public int getSessionsMax()
+    {
+        return (int)_sessionsStats.getMax();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return total number of sessions
+     */
+    @ManagedAttribute("total number of sessions")
+    public int getSessionsTotal()
+    {
+        return (int)_sessionsStats.getTotal();
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("time before a session cookie is re-set (in s)")
+    public int getRefreshCookieAge()
+    {
+        return _refreshCookieAge;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return same as SessionCookieConfig.getSecure(). If true, session
+     * cookies are ALWAYS marked as secure. If false, a session cookie is
+     * ONLY marked as secure if _secureRequestOnly == true and it is a HTTPS request.
+     */
+    @ManagedAttribute("if true, secure cookie flag is set on session cookies")
+    public boolean getSecureCookies()
+    {
+        return _secureCookies;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if session cookie is to be marked as secure only on HTTPS requests
+     */
+    public boolean isSecureRequestOnly()
+    {
+        return _secureRequestOnly;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * HTTPS request. Can be overridden by setting SessionCookieConfig.setSecure(true),
+     * in which case the session cookie will be marked as secure on both HTTPS and HTTP.
+     */
+    public void setSecureRequestOnly(boolean secureRequestOnly)
+    {
+        _secureRequestOnly = secureRequestOnly;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("the set session cookie")
+    public String getSessionCookie()
+    {
+        return _sessionCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * A sessioncookie is marked as secure IFF any of the following conditions are true:
+     * <ol>
+     * <li>SessionCookieConfig.setSecure == true</li>
+     * <li>SessionCookieConfig.setSecure == false && _secureRequestOnly==true && request is HTTPS</li>
+     * </ol>
+     * According to SessionCookieConfig javadoc, case 1 can be used when:
+     * "... even though the request that initiated the session came over HTTP,
+     * is to support a topology where the web container is front-ended by an
+     * SSL offloading load balancer. In this case, the traffic between the client
+     * and the load balancer will be over HTTPS, whereas the traffic between the
+     * load balancer and the web container will be over HTTP."
+     *
+     * For case 2, you can use _secureRequestOnly to determine if you want the
+     * Servlet Spec 3.0  default behaviour when SessionCookieConfig.setSecure==false,
+     * which is:
+     * "they shall be marked as secure only if the request that initiated the
+     * corresponding session was also secure"
+     *
+     * The default for _secureRequestOnly is true, which gives the above behaviour. If
+     * you set it to false, then a session cookie is NEVER marked as secure, even if
+     * the initiating request was secure.
+     *
+     * @see org.eclipse.jetty.server.SessionManager#getSessionCookie(javax.servlet.http.HttpSession, java.lang.String, boolean)
+     */
+    @Override
+    public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure)
+    {
+        if (isUsingCookies())
+        {
+            String sessionPath = (_cookieConfig.getPath()==null) ? contextPath : _cookieConfig.getPath();
+            sessionPath = (sessionPath==null||sessionPath.length()==0) ? "/" : sessionPath;
+            String id = getNodeId(session);
+            HttpCookie cookie = null;
+            if (_sessionComment == null)
+            {
+                cookie = new HttpCookie(
+                                        _cookieConfig.getName(),
+                                        id,
+                                        _cookieConfig.getDomain(),
+                                        sessionPath,
+                                        _cookieConfig.getMaxAge(),
+                                        _cookieConfig.isHttpOnly(),
+                                        _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure));
+            }
+            else
+            {
+                cookie = new HttpCookie(
+                                        _cookieConfig.getName(),
+                                        id,
+                                        _cookieConfig.getDomain(),
+                                        sessionPath,
+                                        _cookieConfig.getMaxAge(),
+                                        _cookieConfig.isHttpOnly(),
+                                        _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure),
+                                        _sessionComment,
+                                        1);
+            }
+
+            return cookie;
+        }
+        return null;
+    }
+
+    @ManagedAttribute("domain of the session cookie, or null for the default")
+    public String getSessionDomain()
+    {
+        return _sessionDomain;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionHandler.
+     */
+    public SessionHandler getSessionHandler()
+    {
+        return _sessionHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("number of currently active sessions")
+    public int getSessions()
+    {
+        return (int)_sessionsStats.getCurrent();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    @ManagedAttribute("name of use for URL session tracking")
+    public String getSessionIdPathParameterName()
+    {
+        return _sessionIdPathParameterName;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getSessionIdPathParameterNamePrefix()
+    {
+        return _sessionIdPathParameterNamePrefix;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the usingCookies.
+     */
+    @Override
+    public boolean isUsingCookies()
+    {
+        return _usingCookies;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isValid(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        return s.isValid();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getClusterId(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        return s.getClusterId();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getNodeId(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        return s.getNodeId();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new HttpSession for a request
+     */
+    @Override
+    public HttpSession newHttpSession(HttpServletRequest request)
+    {
+        AbstractSession session=newSession(request);
+        session.setMaxInactiveInterval(_dftMaxIdleSecs);
+        addSession(session,true);
+        return session;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void removeEventListener(EventListener listener)
+    {
+        if (listener instanceof HttpSessionAttributeListener)
+            _sessionAttributeListeners.remove(listener);
+        if (listener instanceof HttpSessionListener)
+            _sessionListeners.remove(listener);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Reset statistics values
+     */
+    @ManagedOperation(value="reset statistics", impact="ACTION")
+    public void statsReset()
+    {
+        _sessionsStats.reset(getSessions());
+        _sessionTimeStats.reset();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param httpOnly
+     *            The httpOnly to set.
+     */
+    public void setHttpOnly(boolean httpOnly)
+    {
+        _httpOnly=httpOnly;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param metaManager The metaManager used for cross context session management.
+     */
+    @Override
+    public void setSessionIdManager(SessionIdManager metaManager)
+    {
+        updateBean(_sessionIdManager, metaManager);
+        _sessionIdManager=metaManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param seconds
+     */
+    @Override
+    public void setMaxInactiveInterval(int seconds)
+    {
+        _dftMaxIdleSecs=seconds;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRefreshCookieAge(int ageInSeconds)
+    {
+        _refreshCookieAge=ageInSeconds;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setSessionCookie(String cookieName)
+    {
+        _sessionCookie=cookieName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionHandler
+     *            The sessionHandler to set.
+     */
+    @Override
+    public void setSessionHandler(SessionHandler sessionHandler)
+    {
+        _sessionHandler=sessionHandler;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setSessionIdPathParameterName(String param)
+    {
+        _sessionIdPathParameterName =(param==null||"none".equals(param))?null:param;
+        _sessionIdPathParameterNamePrefix =(param==null||"none".equals(param))?null:(";"+ _sessionIdPathParameterName +"=");
+    }
+    /* ------------------------------------------------------------ */
+    /**
+     * @param usingCookies
+     *            The usingCookies to set.
+     */
+    public void setUsingCookies(boolean usingCookies)
+    {
+        _usingCookies=usingCookies;
+    }
+
+
+    protected abstract void addSession(AbstractSession session);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add the session Registers the session with this manager and registers the
+     * session ID with the sessionIDManager;
+     */
+    protected void addSession(AbstractSession session, boolean created)
+    {
+        synchronized (_sessionIdManager)
+        {
+            _sessionIdManager.addSession(session);
+            addSession(session);
+        }
+
+        if (created)
+        {
+            _sessionsStats.increment();
+            if (_sessionListeners!=null)
+            {
+                HttpSessionEvent event=new HttpSessionEvent(session);
+                for (HttpSessionListener listener : _sessionListeners)
+                    listener.sessionCreated(event);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get a known existing session
+     * @param idInCluster The session ID in the cluster, stripped of any worker name.
+     * @return A Session or null if none exists.
+     */
+    public abstract AbstractSession getSession(String idInCluster);
+
+    /**
+     * Prepare sessions for session manager shutdown
+     * 
+     * @throws Exception
+     */
+    protected abstract void shutdownSessions() throws Exception;
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new session instance
+     * @param request
+     * @return the new session
+     */
+    protected abstract AbstractSession newSession(HttpServletRequest request);
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the cluster node id (worker id) is returned as part of the session id by {@link HttpSession#getId()}. Default is false.
+     */
+    public boolean isNodeIdInSessionId()
+    {
+        return _nodeIdInSessionId;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param nodeIdInSessionId true if the cluster node id (worker id) will be returned as part of the session id by {@link HttpSession#getId()}. Default is false.
+     */
+    public void setNodeIdInSessionId(boolean nodeIdInSessionId)
+    {
+        _nodeIdInSessionId=nodeIdInSessionId;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Remove session from manager
+     * @param session The session to remove
+     * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
+     * {@link SessionIdManager#invalidateAll(String)} should be called.
+     */
+    public void removeSession(HttpSession session, boolean invalidate)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        removeSession(s,invalidate);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Remove session from manager
+     * @param session The session to remove
+     * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
+     * {@link SessionIdManager#invalidateAll(String)} should be called.
+     */
+    public boolean removeSession(AbstractSession session, boolean invalidate)
+    {
+        // Remove session from context and global maps
+        boolean removed = removeSession(session.getClusterId());
+
+        if (removed)
+        {
+            _sessionsStats.decrement();
+            _sessionTimeStats.set(round((System.currentTimeMillis() - session.getCreationTime())/1000.0));
+
+            // Remove session from all context and global id maps
+            _sessionIdManager.removeSession(session);
+            if (invalidate)
+                _sessionIdManager.invalidateAll(session.getClusterId());
+
+            if (invalidate && _sessionListeners!=null)
+            {
+                HttpSessionEvent event=new HttpSessionEvent(session);      
+                for (int i = _sessionListeners.size()-1; i>=0; i--)
+                {
+                    _sessionListeners.get(i).sessionDestroyed(event);
+                }
+            }
+        }
+        
+        return removed;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected abstract boolean removeSession(String idInCluster);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return maximum amount of time session remained valid
+     */
+    @ManagedAttribute("maximum amount of time sessions have remained active (in s)")
+    public long getSessionTimeMax()
+    {
+        return _sessionTimeStats.getMax();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+    {
+        return __defaultSessionTrackingModes;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+    {
+        return Collections.unmodifiableSet(_sessionTrackingModes);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
+    {
+        _sessionTrackingModes=new HashSet<SessionTrackingMode>(sessionTrackingModes);
+        _usingCookies=_sessionTrackingModes.contains(SessionTrackingMode.COOKIE);
+        _usingURLs=_sessionTrackingModes.contains(SessionTrackingMode.URL);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isUsingURLs()
+    {
+        return _usingURLs;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public SessionCookieConfig getSessionCookieConfig()
+    {
+        return _cookieConfig;
+    }
+
+    /* ------------------------------------------------------------ */
+    private SessionCookieConfig _cookieConfig =
+        new CookieConfig();
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return total amount of time all sessions remained valid
+     */
+    @ManagedAttribute("total time sessions have remained valid")
+    public long getSessionTimeTotal()
+    {
+        return _sessionTimeStats.getTotal();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return mean amount of time session remained valid
+     */
+    @ManagedAttribute("mean time sessions remain valid (in s)")
+    public double getSessionTimeMean()
+    {
+        return _sessionTimeStats.getMean();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return standard deviation of amount of time session remained valid
+     */
+    @ManagedAttribute("standard deviation a session remained valid (in s)")
+    public double getSessionTimeStdDev()
+    {
+        return _sessionTimeStats.getStdDev();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.SessionManager#isCheckingRemoteSessionIdEncoding()
+     */
+    @Override
+    @ManagedAttribute("check remote session id encoding")
+    public boolean isCheckingRemoteSessionIdEncoding()
+    {
+        return _checkingRemoteSessionIdEncoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.SessionManager#setCheckingRemoteSessionIdEncoding(boolean)
+     */
+    @Override
+    public void setCheckingRemoteSessionIdEncoding(boolean remote)
+    {
+        _checkingRemoteSessionIdEncoding=remote;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Tell the HttpSessionIdListeners the id changed.
+     * NOTE: this method must be called LAST in subclass overrides, after the session has been updated
+     * with the new id.
+     * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+     */
+    @Override
+    public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+    {
+        if (!_sessionIdListeners.isEmpty())
+        {
+            AbstractSession session = getSession(newClusterId);
+            HttpSessionEvent event = new HttpSessionEvent(session);
+            for (HttpSessionIdListener l:_sessionIdListeners)
+            {
+                l.sessionIdChanged(event, oldClusterId);
+            }
+        }
+
+    }
+
+    /**
+     * CookieConfig
+     * 
+     * Implementation of the javax.servlet.SessionCookieConfig.
+     */
+    public final class CookieConfig implements SessionCookieConfig
+    {
+        @Override
+        public String getComment()
+        {
+            return _sessionComment;
+        }
+
+        @Override
+        public String getDomain()
+        {
+            return _sessionDomain;
+        }
+
+        @Override
+        public int getMaxAge()
+        {
+            return _maxCookieAge;
+        }
+
+        @Override
+        public String getName()
+        {
+            return _sessionCookie;
+        }
+
+        @Override
+        public String getPath()
+        {
+            return _sessionPath;
+        }
+
+        @Override
+        public boolean isHttpOnly()
+        {
+            return _httpOnly;
+        }
+
+        @Override
+        public boolean isSecure()
+        {
+            return _secureCookies;
+        }
+
+        @Override
+        public void setComment(String comment)
+        {  
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _sessionComment = comment;
+        }
+
+        @Override
+        public void setDomain(String domain)
+        {
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _sessionDomain=domain;
+        }
+
+        @Override
+        public void setHttpOnly(boolean httpOnly)
+        {   
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _httpOnly=httpOnly;
+        }
+
+        @Override
+        public void setMaxAge(int maxAge)
+        {               
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _maxCookieAge=maxAge;
+        }
+
+        @Override
+        public void setName(String name)
+        {  
+                if (_context != null && _context.getContextHandler().isAvailable())
+                    throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _sessionCookie=name;
+        }
+
+        @Override
+        public void setPath(String path)
+        {
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started"); 
+            _sessionPath=path;
+        }
+
+        @Override
+        public void setSecure(boolean secure)
+        {
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _secureCookies=secure;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Interface that any session wrapper should implement so that
+     * SessionManager may access the Jetty session implementation.
+     *
+     */
+    public interface SessionIf extends HttpSession
+    {
+        public AbstractSession getSession();
+    }
+
+    public void doSessionAttributeListeners(AbstractSession session, String name, Object old, Object value)
+    {
+        if (!_sessionAttributeListeners.isEmpty())
+        {
+            HttpSessionBindingEvent event=new HttpSessionBindingEvent(session,name,old==null?value:old);
+
+            for (HttpSessionAttributeListener l : _sessionAttributeListeners)
+            {
+                if (old==null)
+                    l.attributeAdded(event);
+                else if (value==null)
+                    l.attributeRemoved(event);
+                else
+                    l.attributeReplaced(event);
+            }
+        }
+    }
+
+    @Override
+    @Deprecated
+    public SessionIdManager getMetaManager()
+    {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/HashSessionIdManager.java b/lib/jetty/org/eclipse/jetty/server/session/HashSessionIdManager.java
new file mode 100644 (file)
index 0000000..a17bc06
--- /dev/null
@@ -0,0 +1,231 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.server.SessionIdManager;
+
+/* ------------------------------------------------------------ */
+/**
+ * HashSessionIdManager. An in-memory implementation of the session ID manager.
+ */
+public class HashSessionIdManager extends AbstractSessionIdManager
+{
+    private final Map<String, Set<WeakReference<HttpSession>>> _sessions = new HashMap<String, Set<WeakReference<HttpSession>>>();
+
+    /* ------------------------------------------------------------ */
+    public HashSessionIdManager()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public HashSessionIdManager(Random random)
+    {
+        super(random);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Collection of String session IDs
+     */
+    public Collection<String> getSessions()
+    {
+        return Collections.unmodifiableCollection(_sessions.keySet());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Collection of Sessions for the passed session ID
+     */
+    public Collection<HttpSession> getSession(String id)
+    {
+        ArrayList<HttpSession> sessions = new ArrayList<HttpSession>();
+        Set<WeakReference<HttpSession>> refs =_sessions.get(id);
+        if (refs!=null)
+        {
+            for (WeakReference<HttpSession> ref: refs)
+            {
+                HttpSession session = ref.get();
+                if (session!=null)
+                    sessions.add(session);
+            }
+        }
+        return sessions;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        _sessions.clear();
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see SessionIdManager#idInUse(String)
+     */
+    @Override
+    public boolean idInUse(String id)
+    {
+        synchronized (this)
+        {
+            return _sessions.containsKey(id);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see SessionIdManager#addSession(HttpSession)
+     */
+    @Override
+    public void addSession(HttpSession session)
+    {
+        String id = getClusterId(session.getId());
+        WeakReference<HttpSession> ref = new WeakReference<HttpSession>(session);
+
+        synchronized (this)
+        {
+            Set<WeakReference<HttpSession>> sessions = _sessions.get(id);
+            if (sessions==null)
+            {
+                sessions=new HashSet<WeakReference<HttpSession>>();
+                _sessions.put(id,sessions);
+            }
+            sessions.add(ref);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see SessionIdManager#removeSession(HttpSession)
+     */
+    @Override
+    public void removeSession(HttpSession session)
+    {
+        String id = getClusterId(session.getId());
+
+        synchronized (this)
+        {
+            Collection<WeakReference<HttpSession>> sessions = _sessions.get(id);
+            if (sessions!=null)
+            {
+                for (Iterator<WeakReference<HttpSession>> iter = sessions.iterator(); iter.hasNext();)
+                {
+                    WeakReference<HttpSession> ref = iter.next();
+                    HttpSession s=ref.get();
+                    if (s==null)
+                    {
+                        iter.remove();
+                        continue;
+                    }
+                    if (s==session)
+                    {
+                        iter.remove();
+                        break;
+                    }
+                }
+                if (sessions.isEmpty())
+                    _sessions.remove(id);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see SessionIdManager#invalidateAll(String)
+     */
+    @Override
+    public void invalidateAll(String id)
+    {
+        Collection<WeakReference<HttpSession>> sessions;
+        synchronized (this)
+        {
+            sessions = _sessions.remove(id);
+        }
+
+        if (sessions!=null)
+        {
+            for (WeakReference<HttpSession> ref: sessions)
+            {
+                AbstractSession session=(AbstractSession)ref.get();
+                if (session!=null && session.isValid())
+                    session.invalidate();
+            }
+            sessions.clear();
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
+    {
+        //generate a new id
+        String newClusterId = newSessionId(request.hashCode());
+
+
+        synchronized (this)
+        {
+            Set<WeakReference<HttpSession>> sessions = _sessions.remove(oldClusterId); //get the list of sessions with same id from other contexts
+            if (sessions!=null)
+            {
+                for (Iterator<WeakReference<HttpSession>> iter = sessions.iterator(); iter.hasNext();)
+                {
+                    WeakReference<HttpSession> ref = iter.next();
+                    HttpSession s = ref.get();
+                    if (s == null)
+                    {
+                        continue;
+                    }
+                    else
+                    {
+                        if (s instanceof AbstractSession)
+                        {
+                            AbstractSession abstractSession = (AbstractSession)s;
+                            abstractSession.getSessionManager().renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
+                        }
+                    }
+                }
+                _sessions.put(newClusterId, sessions);
+            }
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/HashSessionManager.java b/lib/jetty/org/eclipse/jetty/server/session/HashSessionManager.java
new file mode 100644 (file)
index 0000000..1effd32
--- /dev/null
@@ -0,0 +1,679 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * HashSessionManager
+ * 
+ * An in-memory implementation of SessionManager.
+ * <p>
+ * This manager supports saving sessions to disk, either periodically or at shutdown.
+ * Sessions can also have their content idle saved to disk to reduce the memory overheads of large idle sessions.
+ * <p>
+ * This manager will create it's own Timer instance to scavenge threads, unless it discovers a shared Timer instance
+ * set as the "org.eclipse.jetty.server.session.timer" attribute of the ContextHandler.
+ *
+ */
+public class HashSessionManager extends AbstractSessionManager
+{
+    final static Logger LOG = SessionHandler.LOG;
+
+    protected final ConcurrentMap<String,HashedSession> _sessions=new ConcurrentHashMap<String,HashedSession>();
+    private Scheduler _timer;
+    private Scheduler.Task _task;
+    long _scavengePeriodMs=30000;
+    long _savePeriodMs=0; //don't do period saves by default
+    long _idleSavePeriodMs = 0; // don't idle save sessions by default.
+    private Scheduler.Task _saveTask;
+    File _storeDir;
+    private boolean _lazyLoad=false;
+    private volatile boolean _sessionsLoaded=false;
+    private boolean _deleteUnrestorableSessions=false;
+
+
+    /**
+     * Scavenger
+     *
+     */
+    protected class Scavenger implements Runnable
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                scavenge();
+            }
+            finally
+            {
+                if (_timer != null && _timer.isRunning())
+                    _timer.schedule(this, _scavengePeriodMs, TimeUnit.MILLISECONDS);
+            }
+        }
+    }
+
+    /**
+     * Saver
+     *
+     */
+    protected class Saver implements Runnable
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                saveSessions(true);
+            }
+            catch (Exception e)
+            {       
+                LOG.warn(e);
+            }
+            finally
+            {
+                if (_timer != null && _timer.isRunning())
+                    _timer.schedule(this, _savePeriodMs, TimeUnit.MILLISECONDS);
+            }
+        }        
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public HashSessionManager()
+    {
+        super();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see AbstractSessionManager#doStart()
+     */
+    @Override
+    public void doStart() throws Exception
+    {
+        //try shared scheduler from Server first
+        _timer = getSessionHandler().getServer().getBean(Scheduler.class);
+        if (_timer == null)
+        {
+            //try one passed into the context
+            ServletContext context = ContextHandler.getCurrentContext();
+            if (context!=null)
+                _timer = (Scheduler)context.getAttribute("org.eclipse.jetty.server.session.timer");   
+        }         
+      
+        if (_timer == null)
+        {
+            //make a scheduler if none useable
+            _timer=new ScheduledExecutorScheduler(toString()+"Timer",true);
+            addBean(_timer,true);
+        }
+        else
+            addBean(_timer,false);
+            
+        super.doStart();
+
+        setScavengePeriod(getScavengePeriod());
+
+        if (_storeDir!=null)
+        {
+            if (!_storeDir.exists())
+                _storeDir.mkdirs();
+
+            if (!_lazyLoad)
+                restoreSessions();
+        }
+
+        setSavePeriod(getSavePeriod());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see AbstractSessionManager#doStop()
+     */
+    @Override
+    public void doStop() throws Exception
+    {
+        // stop the scavengers
+        synchronized(this)
+        {
+            if (_saveTask!=null)
+                _saveTask.cancel();
+            _saveTask=null;
+            if (_task!=null)
+                _task.cancel();
+            _task=null;
+            _timer=null;
+        }
+
+        // This will callback invalidate sessions - where we decide if we will save
+        super.doStop();
+
+        _sessions.clear();
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the period in seconds at which a check is made for sessions to be invalidated.
+     */
+    public int getScavengePeriod()
+    {
+        return (int)(_scavengePeriodMs/1000);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int getSessions()
+    {
+        int sessions=super.getSessions();
+        if (LOG.isDebugEnabled())
+        {
+            if (_sessions.size()!=sessions)
+                LOG.warn("sessions: "+_sessions.size()+"!="+sessions);
+        }
+        return sessions;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return seconds Idle period after which a session is saved
+     */
+    public int getIdleSavePeriod()
+    {
+      if (_idleSavePeriodMs <= 0)
+        return 0;
+
+      return (int)(_idleSavePeriodMs / 1000);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Configures the period in seconds after which a session is deemed idle and saved
+     * to save on session memory.
+     *
+     * The session is persisted, the values attribute map is cleared and the session set to idled.
+     *
+     * @param seconds Idle period after which a session is saved
+     */
+    public void setIdleSavePeriod(int seconds)
+    {
+      _idleSavePeriodMs = seconds * 1000L;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setMaxInactiveInterval(int seconds)
+    {
+        super.setMaxInactiveInterval(seconds);
+        if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000L)
+            setScavengePeriod((_dftMaxIdleSecs+9)/10);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param seconds the period is seconds at which sessions are periodically saved to disk
+     */
+    public void setSavePeriod (int seconds)
+    {
+        long period = (seconds * 1000L);
+        if (period < 0)
+            period=0;
+        _savePeriodMs=period;
+
+        if (_timer!=null)
+        {
+            synchronized (this)
+            {
+                if (_saveTask!=null)
+                    _saveTask.cancel();
+                _saveTask = null;
+                if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured
+                {
+                    _saveTask = _timer.schedule(new Saver(),_savePeriodMs,TimeUnit.MILLISECONDS);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the period in seconds at which sessions are periodically saved to disk
+     */
+    public int getSavePeriod ()
+    {
+        if (_savePeriodMs<=0)
+            return 0;
+
+        return (int)(_savePeriodMs/1000);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param seconds the period in seconds at which a check is made for sessions to be invalidated.
+     */
+    public void setScavengePeriod(int seconds)
+    { 
+        if (seconds==0)
+            seconds=60;
+
+        long old_period=_scavengePeriodMs;
+        long period=seconds*1000L;
+        if (period>60000)
+            period=60000;
+        if (period<1000)
+            period=1000;
+
+        _scavengePeriodMs=period;
+    
+        if (_timer!=null && (period!=old_period || _task==null))
+        {
+            synchronized (this)
+            {
+                if (_task!=null)
+                {
+                    _task.cancel();
+                    _task = null;
+                }
+                _task = _timer.schedule(new Scavenger(),_scavengePeriodMs, TimeUnit.MILLISECONDS);
+            }
+        }
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Find sessions that have timed out and invalidate them. This runs in the
+     * SessionScavenger thread.
+     */
+    protected void scavenge()
+    {
+        //don't attempt to scavenge if we are shutting down
+        if (isStopping() || isStopped())
+            return;
+
+        Thread thread=Thread.currentThread();
+        ClassLoader old_loader=thread.getContextClassLoader();
+        try
+        {      
+            if (_loader!=null)
+                thread.setContextClassLoader(_loader);
+
+            // For each session
+            long now=System.currentTimeMillis();
+            __log.debug("Scavenging sessions at {}", now); 
+            
+            for (Iterator<HashedSession> i=_sessions.values().iterator(); i.hasNext();)
+            {
+                HashedSession session=i.next();
+                long idleTime=session.getMaxInactiveInterval()*1000L; 
+                if (idleTime>0&&session.getAccessed()+idleTime<now)
+                {
+                    // Found a stale session, add it to the list
+                    try
+                    {
+                        session.timeout();
+                    }
+                    catch (Exception e)
+                    {
+                        __log.warn("Problem scavenging sessions", e);
+                    }
+                }
+                else if (_idleSavePeriodMs > 0 && session.getAccessed()+_idleSavePeriodMs < now)
+                {
+                    try
+                    {
+                        session.idle();
+                    }
+                    catch (Exception e)
+                    {
+                        __log.warn("Problem idling session "+ session.getId(), e);
+                    }
+                }
+            }
+        }       
+        finally
+        {
+            thread.setContextClassLoader(old_loader);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void addSession(AbstractSession session)
+    {
+        if (isRunning())
+            _sessions.put(session.getClusterId(),(HashedSession)session);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public AbstractSession getSession(String idInCluster)
+    {
+        if ( _lazyLoad && !_sessionsLoaded)
+        {
+            try
+            {
+                restoreSessions();
+            }
+            catch(Exception e)
+            {
+                LOG.warn(e);
+            }
+        }
+
+        Map<String,HashedSession> sessions=_sessions;
+        if (sessions==null)
+            return null;
+
+        HashedSession session = sessions.get(idInCluster);
+
+        if (session == null && _lazyLoad)
+            session=restoreSession(idInCluster);
+        if (session == null)
+            return null;
+
+        if (_idleSavePeriodMs!=0)
+            session.deIdle();
+
+        return session;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void shutdownSessions() throws Exception
+    {   
+        // Invalidate all sessions to cause unbind events
+        ArrayList<HashedSession> sessions=new ArrayList<HashedSession>(_sessions.values());
+        int loop=100;
+        while (sessions.size()>0 && loop-->0)
+        {
+            // If we are called from doStop
+            if (isStopping() && _storeDir != null && _storeDir.exists() && _storeDir.canWrite())
+            {
+                // Then we only save and remove the session from memory- it is not invalidated.
+                for (HashedSession session : sessions)
+                {
+                    session.save(false);
+                    _sessions.remove(session.getClusterId());
+                }
+            }
+            else
+            {
+                for (HashedSession session : sessions)
+                    session.invalidate();
+            }
+
+            // check that no new sessions were created while we were iterating
+            sessions=new ArrayList<HashedSession>(_sessions.values());
+        }
+    }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+     */
+    @Override
+    public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+    {
+        try
+        {
+            Map<String,HashedSession> sessions=_sessions;
+            if (sessions == null)
+                return;
+
+            HashedSession session = sessions.remove(oldClusterId);
+            if (session == null)
+                return;
+
+            session.remove(); //delete any previously saved session
+            session.setClusterId(newClusterId); //update ids
+            session.setNodeId(newNodeId);
+            session.save(); //save updated session: TODO consider only saving file if idled
+            sessions.put(newClusterId, session);
+            
+            super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected AbstractSession newSession(HttpServletRequest request)
+    {
+        return new HashedSession(this, request);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected AbstractSession newSession(long created, long accessed, String clusterId)
+    {
+        return new HashedSession(this, created,accessed, clusterId);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected boolean removeSession(String clusterId)
+    {
+        return _sessions.remove(clusterId)!=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setStoreDirectory (File dir) throws IOException
+    { 
+        // CanonicalFile is used to capture the base store directory in a way that will
+        // work on Windows.  Case differences may through off later checks using this directory.
+        _storeDir=dir.getCanonicalFile();
+    }
+
+    /* ------------------------------------------------------------ */
+    public File getStoreDirectory ()
+    {
+        return _storeDir;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setLazyLoad(boolean lazyLoad)
+    {
+        _lazyLoad = lazyLoad;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isLazyLoad()
+    {
+        return _lazyLoad;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isDeleteUnrestorableSessions()
+    {
+        return _deleteUnrestorableSessions;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setDeleteUnrestorableSessions(boolean deleteUnrestorableSessions)
+    {
+        _deleteUnrestorableSessions = deleteUnrestorableSessions;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void restoreSessions () throws Exception
+    {
+        _sessionsLoaded = true;
+
+        if (_storeDir==null || !_storeDir.exists())
+        {
+            return;
+        }
+
+        if (!_storeDir.canRead())
+        {
+            LOG.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
+            return;
+        }
+
+        String[] files = _storeDir.list();
+        for (int i=0;files!=null&&i<files.length;i++)
+        {
+            restoreSession(files[i]);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected synchronized HashedSession restoreSession(String idInCuster)
+    {        
+        File file = new File(_storeDir,idInCuster);
+
+        FileInputStream in = null;
+        Exception error = null;
+        try
+        {
+            if (file.exists())
+            {
+                in = new FileInputStream(file);
+                HashedSession session = restoreSession(in, null);
+                addSession(session, false);
+                session.didActivate();
+                return session;
+            }
+        }
+        catch (Exception e)
+        {
+           error = e;
+        }
+        finally
+        {
+            if (in != null) IO.close(in);
+            
+            if (error != null)
+            {
+                if (isDeleteUnrestorableSessions() && file.exists() && file.getParentFile().equals(_storeDir) )
+                {
+                    file.delete();
+                    LOG.warn("Deleting file for unrestorable session "+idInCuster, error);
+                }
+                else
+                {
+                    __log.warn("Problem restoring session "+idInCuster, error);
+                }
+            }
+            else
+               file.delete(); //delete successfully restored file
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void saveSessions(boolean reactivate) throws Exception
+    {
+        if (_storeDir==null || !_storeDir.exists())
+        {
+            return;
+        }
+
+        if (!_storeDir.canWrite())
+        {
+            LOG.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
+            return;
+        }
+
+        for (HashedSession session : _sessions.values())
+            session.save(reactivate);
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception
+    {
+        DataInputStream di = new DataInputStream(is);
+
+        String clusterId = di.readUTF();
+        di.readUTF(); // nodeId
+
+        long created = di.readLong();
+        long accessed = di.readLong();
+        int requests = di.readInt();
+
+        if (session == null)
+            session = (HashedSession)newSession(created, accessed, clusterId);
+        session.setRequests(requests);
+
+        int size = di.readInt();
+
+        restoreSessionAttributes(di, size, session);
+
+        try
+        {
+            int maxIdle = di.readInt();
+            session.setMaxInactiveInterval(maxIdle);
+        }
+        catch (EOFException e)
+        {
+            LOG.debug("No maxInactiveInterval persisted for session "+clusterId);
+            LOG.ignore(e);
+        }
+
+        return session;
+    }
+
+    
+    private void restoreSessionAttributes (InputStream is, int size, HashedSession session)
+    throws Exception
+    {
+        if (size>0)
+        {
+            ClassLoadingObjectInputStream ois =  new ClassLoadingObjectInputStream(is);
+            for (int i=0; i<size;i++)
+            {
+                String key = ois.readUTF();
+                Object value = ois.readObject();
+                session.setAttribute(key,value);
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/HashedSession.java b/lib/jetty/org/eclipse/jetty/server/session/HashedSession.java
new file mode 100644 (file)
index 0000000..d36e427
--- /dev/null
@@ -0,0 +1,286 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HashedSession extends MemSession
+{
+    private static final Logger LOG = Log.getLogger(HashedSession.class);
+
+    private final HashSessionManager _hashSessionManager;
+
+    /** Whether the session has been saved because it has been deemed idle;
+     * in which case its attribute map will have been saved and cleared. */
+    private transient boolean _idled = false;
+
+    /** Whether there has already been an attempt to save this session
+     * which has failed.  If there has, there will be no more save attempts
+     * for this session.  This is to stop the logs being flooded with errors
+     * due to serialization failures that are most likely caused by user
+     * data stored in the session that is not serializable. */
+    private transient boolean _saveFailed = false;
+    
+    /**
+     * True if an attempt has been made to de-idle a session and it failed. Once
+     * true, the session will not be attempted to be de-idled again.
+     */
+    private transient boolean _deIdleFailed = false;
+
+    /* ------------------------------------------------------------- */
+    protected HashedSession(HashSessionManager hashSessionManager, HttpServletRequest request)
+    {
+        super(hashSessionManager,request);
+        _hashSessionManager = hashSessionManager;
+    }
+
+    /* ------------------------------------------------------------- */
+    protected HashedSession(HashSessionManager hashSessionManager, long created, long accessed, String clusterId)
+    {
+        super(hashSessionManager,created, accessed, clusterId);
+        _hashSessionManager = hashSessionManager;
+    }
+
+    /* ------------------------------------------------------------- */
+    protected void checkValid()
+    {
+        if (!_deIdleFailed && _hashSessionManager._idleSavePeriodMs!=0)
+            deIdle();
+        super.checkValid();
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public void setMaxInactiveInterval(int secs)
+    {
+        super.setMaxInactiveInterval(secs);
+        if (getMaxInactiveInterval()>0&&(getMaxInactiveInterval()*1000L/10)<_hashSessionManager._scavengePeriodMs)
+            _hashSessionManager.setScavengePeriod((secs+9)/10);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doInvalidate()
+    throws IllegalStateException
+    {
+        super.doInvalidate();
+        remove();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Remove from the disk
+     */
+    synchronized void remove ()
+    {
+        if (_hashSessionManager._storeDir!=null && getId()!=null)
+        {
+            String id=getId();
+            File f = new File(_hashSessionManager._storeDir, id);
+            f.delete();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    synchronized void save(boolean reactivate)
+    throws Exception
+    {
+        // Only idle the session if not already idled and no previous save/idle has failed
+        if (!isIdled() && !_saveFailed)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Saving {} {}",super.getId(),reactivate);
+
+            try
+            {
+                willPassivate();
+                save();
+                if (reactivate)
+                    didActivate();
+                else
+                    clearAttributes();
+            }
+            catch (Exception e)
+            {       
+                LOG.warn("Problem saving session " + super.getId(), e);
+                _idled=false; // assume problem was before _values.clear();
+            }
+        }
+    }
+    
+    
+    
+    synchronized void save ()
+    throws Exception
+    {   
+        File file = null;
+        FileOutputStream fos = null;
+        if (!_saveFailed && _hashSessionManager._storeDir != null)
+        {
+            try
+            {
+                file = new File(_hashSessionManager._storeDir, super.getId());
+                if (file.exists())
+                    file.delete();
+                file.createNewFile();
+                fos = new FileOutputStream(file);
+                save(fos);
+                IO.close(fos);
+            }
+            catch (Exception e)
+            {
+                saveFailed(); // We won't try again for this session
+                if (fos != null) IO.close(fos);
+                if (file != null) file.delete(); // No point keeping the file if we didn't save the whole session
+                throw e;             
+            }
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public synchronized void save(OutputStream os)  throws IOException
+    {
+        DataOutputStream out = new DataOutputStream(os);
+        out.writeUTF(getClusterId());
+        out.writeUTF(getNodeId());
+        out.writeLong(getCreationTime());
+        out.writeLong(getAccessed());
+
+        /* Don't write these out, as they don't make sense to store because they
+         * either they cannot be true or their value will be restored in the
+         * Session constructor.
+         */
+        //out.writeBoolean(_invalid);
+        //out.writeBoolean(_doInvalidate);
+        //out.writeBoolean( _newSession);
+        out.writeInt(getRequests());
+        out.writeInt(getAttributes());
+        ObjectOutputStream oos = new ObjectOutputStream(out);
+        Enumeration<String> e=getAttributeNames();
+        while(e.hasMoreElements())
+        {
+            String key=e.nextElement();
+            oos.writeUTF(key);
+            oos.writeObject(doGet(key));
+        }
+        
+        out.writeInt(getMaxInactiveInterval());
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void deIdle()
+    {
+        if (isIdled() && !_deIdleFailed)
+        {
+            // Access now to prevent race with idling period
+            access(System.currentTimeMillis());
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("De-idling " + super.getId());
+
+            FileInputStream fis = null;
+
+            try
+            {
+                File file = new File(_hashSessionManager._storeDir, super.getId());
+                if (!file.exists() || !file.canRead())
+                    throw new FileNotFoundException(file.getName());
+
+                fis = new FileInputStream(file);
+                _idled = false;
+                _hashSessionManager.restoreSession(fis, this);
+                IO.close(fis); 
+                
+                didActivate();
+
+                // If we are doing period saves, then there is no point deleting at this point 
+                if (_hashSessionManager._savePeriodMs == 0)
+                    file.delete();
+            }
+            catch (Exception e)
+            {
+                deIdleFailed();
+                LOG.warn("Problem de-idling session " + super.getId(), e);
+                if (fis != null) IO.close(fis);//Must ensure closed before invalidate
+                invalidate();
+            }
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Idle the session to reduce session memory footprint.
+     *
+     * The session is idled by persisting it, then clearing the session values attribute map and finally setting
+     * it to an idled state.
+     */
+    public synchronized void idle()
+    throws Exception
+    {
+        save(false);
+        _idled = true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized boolean isIdled()
+    {
+      return _idled;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized boolean isSaveFailed()
+    {
+        return _saveFailed;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void saveFailed()
+    {
+        _saveFailed = true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void deIdleFailed()
+    {
+        _deIdleFailed = true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public synchronized boolean isDeIdleFailed()
+    {
+        return _deIdleFailed;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/JDBCSessionIdManager.java b/lib/jetty/org/eclipse/jetty/server/session/JDBCSessionIdManager.java
new file mode 100644 (file)
index 0000000..4c7227d
--- /dev/null
@@ -0,0 +1,1496 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.sql.Blob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import javax.naming.InitialContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.sql.DataSource;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+
+/**
+ * JDBCSessionIdManager
+ *
+ * SessionIdManager implementation that uses a database to store in-use session ids,
+ * to support distributed sessions.
+ *
+ */
+public class JDBCSessionIdManager extends AbstractSessionIdManager
+{
+    final static Logger LOG = SessionHandler.LOG;
+    public final static int MAX_INTERVAL_NOT_SET = -999;
+
+    protected final HashSet<String> _sessionIds = new HashSet<String>();
+    protected Server _server;
+    protected Driver _driver;
+    protected String _driverClassName;
+    protected String _connectionUrl;
+    protected DataSource _datasource;
+    protected String _jndiName;
+
+    protected int _deleteBlockSize = 10; //number of ids to include in where 'in' clause
+
+    protected Scheduler.Task _task; //scavenge task
+    protected Scheduler _scheduler;
+    protected Scavenger _scavenger;
+    protected boolean _ownScheduler;
+    protected long _lastScavengeTime;
+    protected long _scavengeIntervalMs = 1000L * 60 * 10; //10mins
+
+
+    protected String _createSessionIdTable;
+    protected String _createSessionTable;
+
+    protected String _selectBoundedExpiredSessions;
+    private String _selectExpiredSessions;
+    
+    protected String _insertId;
+    protected String _deleteId;
+    protected String _queryId;
+
+    protected  String _insertSession;
+    protected  String _deleteSession;
+    protected  String _updateSession;
+    protected  String _updateSessionNode;
+    protected  String _updateSessionAccessTime;
+
+    protected DatabaseAdaptor _dbAdaptor = new DatabaseAdaptor();
+    protected SessionIdTableSchema _sessionIdTableSchema = new SessionIdTableSchema();
+    protected SessionTableSchema _sessionTableSchema = new SessionTableSchema();
+    
+  
+
+    /**
+     * SessionTableSchema
+     *
+     */
+    public static class SessionTableSchema
+    {        
+        protected DatabaseAdaptor _dbAdaptor;
+        protected String _tableName = "JettySessions";
+        protected String _rowIdColumn = "rowId";
+        protected String _idColumn = "sessionId";
+        protected String _contextPathColumn = "contextPath";
+        protected String _virtualHostColumn = "virtualHost"; 
+        protected String _lastNodeColumn = "lastNode";
+        protected String _accessTimeColumn = "accessTime"; 
+        protected String _lastAccessTimeColumn = "lastAccessTime";
+        protected String _createTimeColumn = "createTime";
+        protected String _cookieTimeColumn = "cookieTime";
+        protected String _lastSavedTimeColumn = "lastSavedTime";
+        protected String _expiryTimeColumn = "expiryTime";
+        protected String _maxIntervalColumn = "maxInterval";
+        protected String _mapColumn = "map";
+        
+        
+        protected void setDatabaseAdaptor(DatabaseAdaptor dbadaptor)
+        {
+            _dbAdaptor = dbadaptor;
+        }
+        
+        
+        public String getTableName()
+        {
+            return _tableName;
+        }
+        public void setTableName(String tableName)
+        {
+            checkNotNull(tableName);
+            _tableName = tableName;
+        }
+        public String getRowIdColumn()
+        {       
+            if ("rowId".equals(_rowIdColumn) && _dbAdaptor.isRowIdReserved())
+                _rowIdColumn = "srowId";
+            return _rowIdColumn;
+        }
+        public void setRowIdColumn(String rowIdColumn)
+        {
+            checkNotNull(rowIdColumn);
+            if (_dbAdaptor == null)
+                throw new IllegalStateException ("DbAdaptor is null");
+            
+            if (_dbAdaptor.isRowIdReserved() && "rowId".equals(rowIdColumn))
+                throw new IllegalArgumentException("rowId is reserved word for Oracle");
+            
+            _rowIdColumn = rowIdColumn;
+        }
+        public String getIdColumn()
+        {
+            return _idColumn;
+        }
+        public void setIdColumn(String idColumn)
+        {
+            checkNotNull(idColumn);
+            _idColumn = idColumn;
+        }
+        public String getContextPathColumn()
+        {
+            return _contextPathColumn;
+        }
+        public void setContextPathColumn(String contextPathColumn)
+        {
+            checkNotNull(contextPathColumn);
+            _contextPathColumn = contextPathColumn;
+        }
+        public String getVirtualHostColumn()
+        {
+            return _virtualHostColumn;
+        }
+        public void setVirtualHostColumn(String virtualHostColumn)
+        {
+            checkNotNull(virtualHostColumn);
+            _virtualHostColumn = virtualHostColumn;
+        }
+        public String getLastNodeColumn()
+        {
+            return _lastNodeColumn;
+        }
+        public void setLastNodeColumn(String lastNodeColumn)
+        {
+            checkNotNull(lastNodeColumn);
+            _lastNodeColumn = lastNodeColumn;
+        }
+        public String getAccessTimeColumn()
+        {
+            return _accessTimeColumn;
+        }
+        public void setAccessTimeColumn(String accessTimeColumn)
+        {
+            checkNotNull(accessTimeColumn);
+            _accessTimeColumn = accessTimeColumn;
+        }
+        public String getLastAccessTimeColumn()
+        {
+            return _lastAccessTimeColumn;
+        }
+        public void setLastAccessTimeColumn(String lastAccessTimeColumn)
+        {
+            checkNotNull(lastAccessTimeColumn);
+            _lastAccessTimeColumn = lastAccessTimeColumn;
+        }
+        public String getCreateTimeColumn()
+        {
+            return _createTimeColumn;
+        }
+        public void setCreateTimeColumn(String createTimeColumn)
+        {
+            checkNotNull(createTimeColumn);
+            _createTimeColumn = createTimeColumn;
+        }
+        public String getCookieTimeColumn()
+        {
+            return _cookieTimeColumn;
+        }
+        public void setCookieTimeColumn(String cookieTimeColumn)
+        {
+            checkNotNull(cookieTimeColumn);
+            _cookieTimeColumn = cookieTimeColumn;
+        }
+        public String getLastSavedTimeColumn()
+        {
+            return _lastSavedTimeColumn;
+        }
+        public void setLastSavedTimeColumn(String lastSavedTimeColumn)
+        {
+            checkNotNull(lastSavedTimeColumn);
+            _lastSavedTimeColumn = lastSavedTimeColumn;
+        }
+        public String getExpiryTimeColumn()
+        {
+            return _expiryTimeColumn;
+        }
+        public void setExpiryTimeColumn(String expiryTimeColumn)
+        {
+            checkNotNull(expiryTimeColumn);
+            _expiryTimeColumn = expiryTimeColumn;
+        }
+        public String getMaxIntervalColumn()
+        {
+            return _maxIntervalColumn;
+        }
+        public void setMaxIntervalColumn(String maxIntervalColumn)
+        {
+            checkNotNull(maxIntervalColumn);
+            _maxIntervalColumn = maxIntervalColumn;
+        }
+        public String getMapColumn()
+        {
+            return _mapColumn;
+        }
+        public void setMapColumn(String mapColumn)
+        {
+            checkNotNull(mapColumn);
+            _mapColumn = mapColumn;
+        }
+        
+        public String getCreateStatementAsString ()
+        {
+            if (_dbAdaptor == null)
+                throw new IllegalStateException ("No DBAdaptor");
+            
+            String blobType = _dbAdaptor.getBlobType();
+            String longType = _dbAdaptor.getLongType();
+            
+            return "create table "+_tableName+" ("+getRowIdColumn()+" varchar(120), "+_idColumn+" varchar(120), "+
+                    _contextPathColumn+" varchar(60), "+_virtualHostColumn+" varchar(60), "+_lastNodeColumn+" varchar(60), "+_accessTimeColumn+" "+longType+", "+
+                    _lastAccessTimeColumn+" "+longType+", "+_createTimeColumn+" "+longType+", "+_cookieTimeColumn+" "+longType+", "+
+                    _lastSavedTimeColumn+" "+longType+", "+_expiryTimeColumn+" "+longType+", "+_maxIntervalColumn+" "+longType+", "+
+                    _mapColumn+" "+blobType+", primary key("+getRowIdColumn()+"))";
+        }
+        
+        public String getCreateIndexOverExpiryStatementAsString (String indexName)
+        {
+            return "create index "+indexName+" on "+getTableName()+" ("+getExpiryTimeColumn()+")";
+        }
+        
+        public String getCreateIndexOverSessionStatementAsString (String indexName)
+        {
+            return "create index "+indexName+" on "+getTableName()+" ("+getIdColumn()+", "+getContextPathColumn()+")";
+        }
+        
+        public String getAlterTableForMaxIntervalAsString ()
+        {
+            if (_dbAdaptor == null)
+                throw new IllegalStateException ("No DBAdaptor");
+            String longType = _dbAdaptor.getLongType();
+            return "alter table "+getTableName()+" add "+getMaxIntervalColumn()+" "+longType+" not null default "+MAX_INTERVAL_NOT_SET;
+        }
+        
+        private void checkNotNull(String s)
+        {
+            if (s == null)
+                throw new IllegalArgumentException(s);
+        }
+        public String getInsertSessionStatementAsString()
+        {
+           return "insert into "+getTableName()+
+            " ("+getRowIdColumn()+", "+getIdColumn()+", "+getContextPathColumn()+", "+getVirtualHostColumn()+", "+getLastNodeColumn()+
+            ", "+getAccessTimeColumn()+", "+getLastAccessTimeColumn()+", "+getCreateTimeColumn()+", "+getCookieTimeColumn()+
+            ", "+getLastSavedTimeColumn()+", "+getExpiryTimeColumn()+", "+getMaxIntervalColumn()+", "+getMapColumn()+") "+
+            " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+        }
+        public String getDeleteSessionStatementAsString()
+        {
+            return "delete from "+getTableName()+
+            " where "+getRowIdColumn()+" = ?";
+        }
+        public String getUpdateSessionStatementAsString()
+        {
+            return "update "+getTableName()+
+                    " set "+getIdColumn()+" = ?, "+getLastNodeColumn()+" = ?, "+getAccessTimeColumn()+" = ?, "+
+                    getLastAccessTimeColumn()+" = ?, "+getLastSavedTimeColumn()+" = ?, "+getExpiryTimeColumn()+" = ?, "+
+                    getMaxIntervalColumn()+" = ?, "+getMapColumn()+" = ? where "+getRowIdColumn()+" = ?";
+        }
+        public String getUpdateSessionNodeStatementAsString()
+        {
+            return "update "+getTableName()+
+                    " set "+getLastNodeColumn()+" = ? where "+getRowIdColumn()+" = ?";
+        }
+        public String getUpdateSessionAccessTimeStatementAsString()
+        {
+           return "update "+getTableName()+
+            " set "+getLastNodeColumn()+" = ?, "+getAccessTimeColumn()+" = ?, "+getLastAccessTimeColumn()+" = ?, "+
+                   getLastSavedTimeColumn()+" = ?, "+getExpiryTimeColumn()+" = ?, "+getMaxIntervalColumn()+" = ? where "+getRowIdColumn()+" = ?";
+        }
+        
+        public String getBoundedExpiredSessionsStatementAsString()
+        {
+            return "select * from "+getTableName()+" where "+getLastNodeColumn()+" = ? and "+getExpiryTimeColumn()+" >= ? and "+getExpiryTimeColumn()+" <= ?";
+        }
+        
+        public String getSelectExpiredSessionsStatementAsString()
+        {
+            return "select * from "+getTableName()+" where "+getExpiryTimeColumn()+" >0 and "+getExpiryTimeColumn()+" <= ?";
+        }
+     
+        public PreparedStatement getLoadStatement (Connection connection, String rowId, String contextPath, String virtualHosts)
+        throws SQLException
+        { 
+            if (_dbAdaptor == null)
+                throw new IllegalStateException("No DB adaptor");
+
+
+            if (contextPath == null || "".equals(contextPath))
+            {
+                if (_dbAdaptor.isEmptyStringNull())
+                {
+                    PreparedStatement statement = connection.prepareStatement("select * from "+getTableName()+
+                                                                              " where "+getIdColumn()+" = ? and "+
+                                                                              getContextPathColumn()+" is null and "+
+                                                                              getVirtualHostColumn()+" = ?");
+                    statement.setString(1, rowId);
+                    statement.setString(2, virtualHosts);
+
+                    return statement;
+                }
+            }
+
+            PreparedStatement statement = connection.prepareStatement("select * from "+getTableName()+
+                                                                      " where "+getIdColumn()+" = ? and "+getContextPathColumn()+
+                                                                      " = ? and "+getVirtualHostColumn()+" = ?");
+            statement.setString(1, rowId);
+            statement.setString(2, contextPath);
+            statement.setString(3, virtualHosts);
+
+            return statement;
+        }
+    }
+    
+    
+    
+    /**
+     * SessionIdTableSchema
+     *
+     */
+    public static class SessionIdTableSchema
+    {
+        protected DatabaseAdaptor _dbAdaptor;
+        protected String _tableName = "JettySessionIds";
+        protected String _idColumn = "id";
+
+        public void setDatabaseAdaptor(DatabaseAdaptor dbAdaptor)
+        {
+            _dbAdaptor = dbAdaptor;
+        }
+        public String getIdColumn()
+        {
+            return _idColumn;
+        }
+
+        public void setIdColumn(String idColumn)
+        {
+            checkNotNull(idColumn);
+            _idColumn = idColumn;
+        }
+
+        public String getTableName()
+        {
+            return _tableName;
+        }
+
+        public void setTableName(String tableName)
+        {
+            checkNotNull(tableName);
+            _tableName = tableName;
+        }
+
+        public String getInsertStatementAsString ()
+        {
+            return "insert into "+_tableName+" ("+_idColumn+")  values (?)";
+        }
+
+        public String getDeleteStatementAsString ()
+        {
+            return "delete from "+_tableName+" where "+_idColumn+" = ?";
+        }
+
+        public String getSelectStatementAsString ()
+        {
+            return  "select * from "+_tableName+" where "+_idColumn+" = ?";
+        }
+        
+        public String getCreateStatementAsString ()
+        {
+            return "create table "+_tableName+" ("+_idColumn+" varchar(120), primary key("+_idColumn+"))";
+        }
+        
+        private void checkNotNull(String s)
+        {
+            if (s == null)
+                throw new IllegalArgumentException(s);
+        }
+    }
+
+
+    /**
+     * DatabaseAdaptor
+     *
+     * Handles differences between databases.
+     *
+     * Postgres uses the getBytes and setBinaryStream methods to access
+     * a "bytea" datatype, which can be up to 1Gb of binary data. MySQL
+     * is happy to use the "blob" type and getBlob() methods instead.
+     *
+     * TODO if the differences become more major it would be worthwhile
+     * refactoring this class.
+     */
+    public static class DatabaseAdaptor
+    {
+        String _dbName;
+        boolean _isLower;
+        boolean _isUpper;
+        
+        protected String _blobType; //if not set, is deduced from the type of the database at runtime
+        protected String _longType; //if not set, is deduced from the type of the database at runtime
+
+
+        public DatabaseAdaptor ()
+        {           
+        }
+        
+        
+        public void adaptTo(DatabaseMetaData dbMeta)  
+        throws SQLException
+        {
+            _dbName = dbMeta.getDatabaseProductName().toLowerCase(Locale.ENGLISH);
+            LOG.debug ("Using database {}",_dbName);
+            _isLower = dbMeta.storesLowerCaseIdentifiers();
+            _isUpper = dbMeta.storesUpperCaseIdentifiers(); 
+        }
+        
+       
+        public void setBlobType(String blobType)
+        {
+            _blobType = blobType;
+        }
+        
+        public String getBlobType ()
+        {
+            if (_blobType != null)
+                return _blobType;
+
+            if (_dbName.startsWith("postgres"))
+                return "bytea";
+
+            return "blob";
+        }
+        
+
+        public void setLongType(String longType)
+        {
+            _longType = longType;
+        }
+        
+
+        public String getLongType ()
+        {
+            if (_longType != null)
+                return _longType;
+
+            if (_dbName == null)
+                throw new IllegalStateException ("DbAdaptor missing metadata");
+            
+            if (_dbName.startsWith("oracle"))
+                return "number(20)";
+
+            return "bigint";
+        }
+        
+
+        /**
+         * Convert a camel case identifier into either upper or lower
+         * depending on the way the db stores identifiers.
+         *
+         * @param identifier
+         * @return the converted identifier
+         */
+        public String convertIdentifier (String identifier)
+        {
+            if (_dbName == null)
+                throw new IllegalStateException ("DbAdaptor missing metadata");
+            
+            if (_isLower)
+                return identifier.toLowerCase(Locale.ENGLISH);
+            if (_isUpper)
+                return identifier.toUpperCase(Locale.ENGLISH);
+
+            return identifier;
+        }
+
+        public String getDBName ()
+        {
+            return _dbName;
+        }
+
+
+        public InputStream getBlobInputStream (ResultSet result, String columnName)
+        throws SQLException
+        {
+            if (_dbName == null)
+                throw new IllegalStateException ("DbAdaptor missing metadata");
+            
+            if (_dbName.startsWith("postgres"))
+            {
+                byte[] bytes = result.getBytes(columnName);
+                return new ByteArrayInputStream(bytes);
+            }
+
+            Blob blob = result.getBlob(columnName);
+            return blob.getBinaryStream();
+        }
+
+
+        public boolean isEmptyStringNull ()
+        {
+            if (_dbName == null)
+                throw new IllegalStateException ("DbAdaptor missing metadata");
+            
+            return (_dbName.startsWith("oracle"));
+        }
+        
+        /**
+         * rowId is a reserved word for Oracle, so change the name of this column
+         * @return true if db in use is oracle
+         */
+        public boolean isRowIdReserved ()
+        {
+            if (_dbName == null)
+                throw new IllegalStateException ("DbAdaptor missing metadata");
+            
+            return (_dbName != null && _dbName.startsWith("oracle"));
+        }
+    }
+
+    
+    /**
+     * Scavenger
+     *
+     */
+    protected class Scavenger implements Runnable
+    {
+
+        @Override
+        public void run()
+        {
+           try
+           {
+               scavenge();
+           }
+           finally
+           {
+               if (_scheduler != null && _scheduler.isRunning())
+                   _scheduler.schedule(this, _scavengeIntervalMs, TimeUnit.MILLISECONDS);
+           }
+        }
+    }
+
+
+    public JDBCSessionIdManager(Server server)
+    {
+        super();
+        _server=server;
+    }
+
+    public JDBCSessionIdManager(Server server, Random random)
+    {
+       super(random);
+       _server=server;
+    }
+
+    /**
+     * Configure jdbc connection information via a jdbc Driver
+     *
+     * @param driverClassName
+     * @param connectionUrl
+     */
+    public void setDriverInfo (String driverClassName, String connectionUrl)
+    {
+        _driverClassName=driverClassName;
+        _connectionUrl=connectionUrl;
+    }
+
+    /**
+     * Configure jdbc connection information via a jdbc Driver
+     *
+     * @param driverClass
+     * @param connectionUrl
+     */
+    public void setDriverInfo (Driver driverClass, String connectionUrl)
+    {
+        _driver=driverClass;
+        _connectionUrl=connectionUrl;
+    }
+
+
+    public void setDatasource (DataSource ds)
+    {
+        _datasource = ds;
+    }
+
+    public DataSource getDataSource ()
+    {
+        return _datasource;
+    }
+
+    public String getDriverClassName()
+    {
+        return _driverClassName;
+    }
+
+    public String getConnectionUrl ()
+    {
+        return _connectionUrl;
+    }
+
+    public void setDatasourceName (String jndi)
+    {
+        _jndiName=jndi;
+    }
+
+    public String getDatasourceName ()
+    {
+        return _jndiName;
+    }
+
+    /**
+     * @param name
+     * @deprecated see DbAdaptor.setBlobType
+     */
+    public void setBlobType (String name)
+    {
+        _dbAdaptor.setBlobType(name);
+    }
+
+    public DatabaseAdaptor getDbAdaptor()
+    {
+        return _dbAdaptor;
+    }
+
+    public void setDbAdaptor(DatabaseAdaptor dbAdaptor)
+    {
+        if (dbAdaptor == null)
+            throw new IllegalStateException ("DbAdaptor cannot be null");
+        
+        _dbAdaptor = dbAdaptor;
+    }
+
+    /**
+     * @return
+     * @deprecated see DbAdaptor.getBlobType
+     */
+    public String getBlobType ()
+    {
+        return _dbAdaptor.getBlobType();
+    }
+
+    /**
+     * @return
+     * @deprecated see DbAdaptor.getLogType
+     */
+    public String getLongType()
+    {
+        return _dbAdaptor.getLongType();
+    }
+
+    /**
+     * @param longType
+     * @deprecated see DbAdaptor.setLongType
+     */
+    public void setLongType(String longType)
+    {
+       _dbAdaptor.setLongType(longType);
+    }
+    
+    public SessionIdTableSchema getSessionIdTableSchema()
+    {
+        return _sessionIdTableSchema;
+    }
+
+    public void setSessionIdTableSchema(SessionIdTableSchema sessionIdTableSchema)
+    {
+        if (sessionIdTableSchema == null)
+            throw new IllegalArgumentException("Null SessionIdTableSchema");
+        
+        _sessionIdTableSchema = sessionIdTableSchema;
+    }
+
+    public SessionTableSchema getSessionTableSchema()
+    {
+        return _sessionTableSchema;
+    }
+
+    public void setSessionTableSchema(SessionTableSchema sessionTableSchema)
+    {
+        _sessionTableSchema = sessionTableSchema;
+    }
+
+    public void setDeleteBlockSize (int bsize)
+    {
+        this._deleteBlockSize = bsize;
+    }
+
+    public int getDeleteBlockSize ()
+    {
+        return this._deleteBlockSize;
+    }
+    
+    public void setScavengeInterval (long sec)
+    {
+        if (sec<=0)
+            sec=60;
+
+        long old_period=_scavengeIntervalMs;
+        long period=sec*1000L;
+
+        _scavengeIntervalMs=period;
+
+        //add a bit of variability into the scavenge time so that not all
+        //nodes with the same scavenge time sync up
+        long tenPercent = _scavengeIntervalMs/10;
+        if ((System.currentTimeMillis()%2) == 0)
+            _scavengeIntervalMs += tenPercent;
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Scavenging every "+_scavengeIntervalMs+" ms");
+        
+        //if (_timer!=null && (period!=old_period || _task==null))
+        if (_scheduler != null && (period!=old_period || _task==null))
+        {
+            synchronized (this)
+            {
+                if (_task!=null)
+                    _task.cancel();
+                if (_scavenger == null)
+                    _scavenger = new Scavenger();
+                _task = _scheduler.schedule(_scavenger,_scavengeIntervalMs,TimeUnit.MILLISECONDS);
+            }
+        }
+    }
+
+    public long getScavengeInterval ()
+    {
+        return _scavengeIntervalMs/1000;
+    }
+
+
+    @Override
+    public void addSession(HttpSession session)
+    {
+        if (session == null)
+            return;
+
+        synchronized (_sessionIds)
+        {
+            String id = ((JDBCSessionManager.Session)session).getClusterId();
+            try
+            {
+                insert(id);
+                _sessionIds.add(id);
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Problem storing session id="+id, e);
+            }
+        }
+    }
+    
+  
+    public void addSession(String id)
+    {
+        if (id == null)
+            return;
+
+        synchronized (_sessionIds)
+        {           
+            try
+            {
+                insert(id);
+                _sessionIds.add(id);
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Problem storing session id="+id, e);
+            }
+        }
+    }
+
+
+
+    @Override
+    public void removeSession(HttpSession session)
+    {
+        if (session == null)
+            return;
+
+        removeSession(((JDBCSessionManager.Session)session).getClusterId());
+    }
+
+
+
+    public void removeSession (String id)
+    {
+
+        if (id == null)
+            return;
+
+        synchronized (_sessionIds)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Removing sessionid="+id);
+            try
+            {
+                _sessionIds.remove(id);
+                delete(id);
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Problem removing session id="+id, e);
+            }
+        }
+
+    }
+
+
+    @Override
+    public boolean idInUse(String id)
+    {
+        if (id == null)
+            return false;
+
+        String clusterId = getClusterId(id);
+        boolean inUse = false;
+        synchronized (_sessionIds)
+        {
+            inUse = _sessionIds.contains(clusterId);
+        }
+
+        
+        if (inUse)
+            return true; //optimisation - if this session is one we've been managing, we can check locally
+
+        //otherwise, we need to go to the database to check
+        try
+        {
+            return exists(clusterId);
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Problem checking inUse for id="+clusterId, e);
+            return false;
+        }
+    }
+
+    /**
+     * Invalidate the session matching the id on all contexts.
+     *
+     * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String)
+     */
+    @Override
+    public void invalidateAll(String id)
+    {
+        //take the id out of the list of known sessionids for this node
+        removeSession(id);
+
+        synchronized (_sessionIds)
+        {
+            //tell all contexts that may have a session object with this id to
+            //get rid of them
+            Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+            for (int i=0; contexts!=null && i<contexts.length; i++)
+            {
+                SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+                if (sessionHandler != null)
+                {
+                    SessionManager manager = sessionHandler.getSessionManager();
+
+                    if (manager != null && manager instanceof JDBCSessionManager)
+                    {
+                        ((JDBCSessionManager)manager).invalidateSession(id);
+                    }
+                }
+            }
+        }
+    }
+
+
+    @Override
+    public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
+    {
+        //generate a new id
+        String newClusterId = newSessionId(request.hashCode());
+
+        synchronized (_sessionIds)
+        {
+            removeSession(oldClusterId);//remove the old one from the list (and database)
+            addSession(newClusterId); //add in the new session id to the list (and database)
+
+            //tell all contexts to update the id 
+            Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+            for (int i=0; contexts!=null && i<contexts.length; i++)
+            {
+                SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+                if (sessionHandler != null) 
+                {
+                    SessionManager manager = sessionHandler.getSessionManager();
+
+                    if (manager != null && manager instanceof JDBCSessionManager)
+                    {
+                        ((JDBCSessionManager)manager).renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
+                    }
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Start up the id manager.
+     *
+     * Makes necessary database tables and starts a Session
+     * scavenger thread.
+     */
+    @Override
+    public void doStart()
+    throws Exception
+    {           
+        initializeDatabase();
+        prepareTables();   
+        super.doStart();
+        if (LOG.isDebugEnabled()) 
+            LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec");
+        
+         //try and use a common scheduler, fallback to own
+         _scheduler =_server.getBean(Scheduler.class);
+         if (_scheduler == null)
+         {
+             _scheduler = new ScheduledExecutorScheduler();
+             _ownScheduler = true;
+             _scheduler.start();
+         }
+  
+        setScavengeInterval(getScavengeInterval());
+    }
+
+    /**
+     * Stop the scavenger.
+     */
+    @Override
+    public void doStop ()
+    throws Exception
+    {
+        synchronized(this)
+        {
+            if (_task!=null)
+                _task.cancel();
+            _task=null;
+            if (_ownScheduler && _scheduler !=null)
+                _scheduler.stop();
+            _scheduler=null;
+        }
+        _sessionIds.clear();
+        super.doStop();
+    }
+
+    /**
+     * Get a connection from the driver or datasource.
+     *
+     * @return the connection for the datasource
+     * @throws SQLException
+     */
+    protected Connection getConnection ()
+    throws SQLException
+    {
+        if (_datasource != null)
+            return _datasource.getConnection();
+        else
+            return DriverManager.getConnection(_connectionUrl);
+    }
+    
+
+
+
+
+
+    /**
+     * Set up the tables in the database
+     * @throws SQLException
+     */
+    /**
+     * @throws SQLException
+     */
+    private void prepareTables()
+    throws SQLException
+    {
+        if (_sessionIdTableSchema == null)
+            throw new IllegalStateException ("No SessionIdTableSchema");
+        
+        if (_sessionTableSchema == null)
+            throw new IllegalStateException ("No SessionTableSchema");
+        
+        try (Connection connection = getConnection();
+             Statement statement = connection.createStatement())
+        {
+            //make the id table
+            connection.setAutoCommit(true);
+            DatabaseMetaData metaData = connection.getMetaData();
+            _dbAdaptor.adaptTo(metaData);
+            _sessionTableSchema.setDatabaseAdaptor(_dbAdaptor);
+            _sessionIdTableSchema.setDatabaseAdaptor(_dbAdaptor);
+            
+            _createSessionIdTable = _sessionIdTableSchema.getCreateStatementAsString();
+            _insertId = _sessionIdTableSchema.getInsertStatementAsString();
+            _deleteId =  _sessionIdTableSchema.getDeleteStatementAsString();
+            _queryId = _sessionIdTableSchema.getSelectStatementAsString();
+            
+            //checking for table existence is case-sensitive, but table creation is not
+            String tableName = _dbAdaptor.convertIdentifier(_sessionIdTableSchema.getTableName());
+            try (ResultSet result = metaData.getTables(null, null, tableName, null))
+            {
+                if (!result.next())
+                {
+                    //table does not exist, so create it
+                    statement.executeUpdate(_createSessionIdTable);
+                }
+            }         
+            
+            //make the session table if necessary
+            tableName = _dbAdaptor.convertIdentifier(_sessionTableSchema.getTableName());
+            try (ResultSet result = metaData.getTables(null, null, tableName, null))
+            {
+                if (!result.next())
+                {
+                    //table does not exist, so create it
+                    _createSessionTable = _sessionTableSchema.getCreateStatementAsString();
+                    statement.executeUpdate(_createSessionTable);
+                }
+                else
+                {
+                    //session table exists, check it has maxinterval column
+                    ResultSet colResult = null;
+                    try
+                    {
+                        colResult = metaData.getColumns(null, null,
+                                                        _dbAdaptor.convertIdentifier(_sessionTableSchema.getTableName()), 
+                                                        _dbAdaptor.convertIdentifier(_sessionTableSchema.getMaxIntervalColumn()));
+                    }
+                    catch (SQLException s)
+                    {
+                        LOG.warn("Problem checking if "+_sessionTableSchema.getTableName()+
+                                 " table contains "+_sessionTableSchema.getMaxIntervalColumn()+" column. Ensure table contains column definition: \""
+                                +_sessionTableSchema.getMaxIntervalColumn()+" long not null default -999\"");
+                        throw s;
+                    }
+                    try
+                    {
+                        if (!colResult.next())
+                        {
+                            try
+                            {
+                                //add the maxinterval column
+                                statement.executeUpdate(_sessionTableSchema.getAlterTableForMaxIntervalAsString());
+                            }
+                            catch (SQLException s)
+                            {
+                                LOG.warn("Problem adding "+_sessionTableSchema.getMaxIntervalColumn()+
+                                         " column. Ensure table contains column definition: \""+_sessionTableSchema.getMaxIntervalColumn()+
+                                         " long not null default -999\"");
+                                throw s;
+                            }
+                        }
+                    }
+                    finally
+                    {
+                        colResult.close();
+                    }
+                }
+            }
+            //make some indexes on the JettySessions table
+            String index1 = "idx_"+_sessionTableSchema.getTableName()+"_expiry";
+            String index2 = "idx_"+_sessionTableSchema.getTableName()+"_session";
+
+            boolean index1Exists = false;
+            boolean index2Exists = false;
+            try (ResultSet result = metaData.getIndexInfo(null, null, tableName, false, false))
+            {
+                while (result.next())
+                {
+                    String idxName = result.getString("INDEX_NAME");
+                    if (index1.equalsIgnoreCase(idxName))
+                        index1Exists = true;
+                    else if (index2.equalsIgnoreCase(idxName))
+                        index2Exists = true;
+                }
+            }
+            if (!index1Exists)
+                statement.executeUpdate(_sessionTableSchema.getCreateIndexOverExpiryStatementAsString(index1));
+            if (!index2Exists)
+                statement.executeUpdate(_sessionTableSchema.getCreateIndexOverSessionStatementAsString(index2));
+
+            //set up some strings representing the statements for session manipulation
+            _insertSession = _sessionTableSchema.getInsertSessionStatementAsString();
+            _deleteSession = _sessionTableSchema.getDeleteSessionStatementAsString();
+            _updateSession = _sessionTableSchema.getUpdateSessionStatementAsString();
+            _updateSessionNode = _sessionTableSchema.getUpdateSessionNodeStatementAsString();
+            _updateSessionAccessTime = _sessionTableSchema.getUpdateSessionAccessTimeStatementAsString();
+            _selectBoundedExpiredSessions = _sessionTableSchema.getBoundedExpiredSessionsStatementAsString();
+            _selectExpiredSessions = _sessionTableSchema.getSelectExpiredSessionsStatementAsString();
+        }
+    }
+
+    /**
+     * Insert a new used session id into the table.
+     *
+     * @param id
+     * @throws SQLException
+     */
+    private void insert (String id)
+    throws SQLException
+    {
+        try (Connection connection = getConnection();
+                PreparedStatement query = connection.prepareStatement(_queryId))
+        {
+            connection.setAutoCommit(true);
+            query.setString(1, id);
+            try (ResultSet result = query.executeQuery())
+            {
+                //only insert the id if it isn't in the db already
+                if (!result.next())
+                {
+                    try (PreparedStatement statement = connection.prepareStatement(_insertId))
+                    {
+                        statement.setString(1, id);
+                        statement.executeUpdate();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Remove a session id from the table.
+     *
+     * @param id
+     * @throws SQLException
+     */
+    private void delete (String id)
+    throws SQLException
+    {
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_deleteId))
+        {
+            connection.setAutoCommit(true);
+            statement.setString(1, id);
+            statement.executeUpdate();
+        }
+    }
+
+
+    /**
+     * Check if a session id exists.
+     *
+     * @param id
+     * @return
+     * @throws SQLException
+     */
+    private boolean exists (String id)
+    throws SQLException
+    {
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_queryId))
+        {
+            connection.setAutoCommit(true);
+            statement.setString(1, id);
+            try (ResultSet result = statement.executeQuery())
+            {
+                return result.next();
+            }
+        }
+    }
+
+    /**
+     * Look for sessions in the database that have expired.
+     *
+     * We do this in the SessionIdManager and not the SessionManager so
+     * that we only have 1 scavenger, otherwise if there are n SessionManagers
+     * there would be n scavengers, all contending for the database.
+     *
+     * We look first for sessions that expired in the previous interval, then
+     * for sessions that expired previously - these are old sessions that no
+     * node is managing any more and have become stuck in the database.
+     */
+    private void scavenge ()
+    {
+        Connection connection = null;
+        try
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug(getWorkerName()+"- Scavenge sweep started at "+System.currentTimeMillis());
+            if (_lastScavengeTime > 0)
+            {
+                connection = getConnection();
+                connection.setAutoCommit(true);
+                Set<String> expiredSessionIds = new HashSet<String>();
+                
+                
+                //Pass 1: find sessions for which we were last managing node that have just expired since last pass
+                long lowerBound = (_lastScavengeTime - _scavengeIntervalMs);
+                long upperBound = _lastScavengeTime;
+                if (LOG.isDebugEnabled())
+                    LOG.debug (getWorkerName()+"- Pass 1: Searching for sessions expired between "+lowerBound + " and "+upperBound);
+
+                try (PreparedStatement statement = connection.prepareStatement(_selectBoundedExpiredSessions))
+                {
+                    statement.setString(1, getWorkerName());
+                    statement.setLong(2, lowerBound);
+                    statement.setLong(3, upperBound);
+                    try (ResultSet result = statement.executeQuery())
+                    {
+                        while (result.next())
+                        {
+                            String sessionId = result.getString(_sessionTableSchema.getIdColumn());
+                            expiredSessionIds.add(sessionId);
+                            if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId);
+                        }
+                    }
+                }
+                scavengeSessions(expiredSessionIds, false);
+
+
+                //Pass 2: find sessions that have expired a while ago for which this node was their last manager
+                try (PreparedStatement selectExpiredSessions = connection.prepareStatement(_selectExpiredSessions))
+                {
+                    expiredSessionIds.clear();
+                    upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs);
+                    if (upperBound > 0)
+                    {
+                        if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 2: Searching for sessions expired before "+upperBound);
+                        selectExpiredSessions.setLong(1, upperBound);
+                        try (ResultSet result = selectExpiredSessions.executeQuery())
+                        {
+                            while (result.next())
+                            {
+                                String sessionId = result.getString(_sessionTableSchema.getIdColumn());
+                                String lastNode = result.getString(_sessionTableSchema.getLastNodeColumn());
+                                if ((getWorkerName() == null && lastNode == null) || (getWorkerName() != null && getWorkerName().equals(lastNode)))
+                                    expiredSessionIds.add(sessionId);
+                                if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId+" last managed by "+getWorkerName());
+                            }
+                        }
+                        scavengeSessions(expiredSessionIds, false);
+                    }
+
+
+                    //Pass 3:
+                    //find all sessions that have expired at least a couple of scanIntervals ago
+                    //if we did not succeed in loading them (eg their related context no longer exists, can't be loaded etc) then
+                    //they are simply deleted
+                    upperBound = _lastScavengeTime - (3 * _scavengeIntervalMs);
+                    expiredSessionIds.clear();
+                    if (upperBound > 0)
+                    {
+                        if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 3: searching for sessions expired before "+upperBound);
+                        selectExpiredSessions.setLong(1, upperBound);
+                        try (ResultSet result = selectExpiredSessions.executeQuery())
+                        {
+                            while (result.next())
+                            {
+                                String sessionId = result.getString(_sessionTableSchema.getIdColumn());
+                                expiredSessionIds.add(sessionId);
+                                if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId);
+                            }
+                        }
+                        scavengeSessions(expiredSessionIds, true);
+                    }
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            if (isRunning())
+                LOG.warn("Problem selecting expired sessions", e);
+            else
+                LOG.ignore(e);
+        }
+        finally
+        {
+            _lastScavengeTime=System.currentTimeMillis();
+            if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Scavenge sweep ended at "+_lastScavengeTime);
+            if (connection != null)
+            {
+                try
+                {
+                    connection.close();
+                }
+                catch (SQLException e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * @param expiredSessionIds
+     */
+    private void scavengeSessions (Set<String> expiredSessionIds, boolean forceDelete)
+    {       
+        Set<String> remainingIds = new HashSet<String>(expiredSessionIds);
+        Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+        for (int i=0; contexts!=null && i<contexts.length; i++)
+        {
+            SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+            if (sessionHandler != null)
+            {
+                SessionManager manager = sessionHandler.getSessionManager();
+                if (manager != null && manager instanceof JDBCSessionManager)
+                {
+                    Set<String> successfullyExpiredIds = ((JDBCSessionManager)manager).expire(expiredSessionIds);
+                    if (successfullyExpiredIds != null)
+                        remainingIds.removeAll(successfullyExpiredIds);
+                }
+            }
+        }
+
+        //Any remaining ids are of those sessions that no context removed
+        if (!remainingIds.isEmpty() && forceDelete)
+        {
+            LOG.info("Forcibly deleting unrecoverable expired sessions {}", remainingIds);
+            try
+            {
+                //ensure they aren't in the local list of in-use session ids
+                synchronized (_sessionIds)
+                {
+                    _sessionIds.removeAll(remainingIds);
+                }
+                
+                cleanExpiredSessionIds(remainingIds);
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Error removing expired session ids", e);
+            }
+        }
+    }
+
+
+   
+    
+    private void cleanExpiredSessionIds (Set<String> expiredIds)
+    throws Exception
+    {
+        if (expiredIds == null || expiredIds.isEmpty())
+            return;
+
+        String[] ids = expiredIds.toArray(new String[expiredIds.size()]);
+        try (Connection con = getConnection())
+        {
+            con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
+            con.setAutoCommit(false);
+
+            int start = 0;
+            int end = 0;
+            int blocksize = _deleteBlockSize;
+            int block = 0;
+       
+            try (Statement statement = con.createStatement())
+            {
+                while (end < ids.length)
+                {
+                    start = block*blocksize;
+                    if ((ids.length -  start)  >= blocksize)
+                        end = start + blocksize;
+                    else
+                        end = ids.length;
+
+                    //take them out of the sessionIds table
+                    statement.executeUpdate(fillInClause("delete from "+_sessionIdTableSchema.getTableName()+" where "+_sessionIdTableSchema.getIdColumn()+" in ", ids, start, end));
+                    //take them out of the sessions table
+                    statement.executeUpdate(fillInClause("delete from "+_sessionTableSchema.getTableName()+" where "+_sessionTableSchema.getIdColumn()+" in ", ids, start, end));
+                    block++;
+                }
+            }
+            catch (Exception e)
+            {
+                con.rollback();
+                throw e;
+            }
+            con.commit();
+        }
+    }
+
+    
+    
+    /**
+     * 
+     * @param sql
+     * @param atoms
+     * @throws Exception
+     */
+    private String fillInClause (String sql, String[] literals, int start, int end)
+    throws Exception
+    {
+        StringBuffer buff = new StringBuffer();
+        buff.append(sql);
+        buff.append("(");
+        for (int i=start; i<end; i++)
+        {
+            buff.append("'"+(literals[i])+"'");
+            if (i+1<end)
+                buff.append(",");
+        }
+        buff.append(")");
+        return buff.toString();
+    }
+    
+    
+    
+    private void initializeDatabase ()
+    throws Exception
+    {
+        if (_datasource != null)
+            return; //already set up
+        
+        if (_jndiName!=null)
+        {
+            InitialContext ic = new InitialContext();
+            _datasource = (DataSource)ic.lookup(_jndiName);
+        }
+        else if ( _driver != null && _connectionUrl != null )
+        {
+            DriverManager.registerDriver(_driver);
+        }
+        else if (_driverClassName != null && _connectionUrl != null)
+        {
+            Class.forName(_driverClassName);
+        }
+        else
+            throw new IllegalStateException("No database configured for sessions");
+    }
+    
+   
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/JDBCSessionManager.java b/lib/jetty/org/eclipse/jetty/server/session/JDBCSessionManager.java
new file mode 100644 (file)
index 0000000..dbe9533
--- /dev/null
@@ -0,0 +1,1201 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server.session;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.ObjectOutputStream;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.session.JDBCSessionIdManager.SessionTableSchema;
+import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * JDBCSessionManager
+ *
+ * SessionManager that persists sessions to a database to enable clustering.
+ *
+ * Session data is persisted to the JettySessions table:
+ *
+ * rowId (unique in cluster: webapp name/path + virtualhost + sessionId)
+ * contextPath (of the context owning the session)
+ * sessionId (unique in a context)
+ * lastNode (name of node last handled session)
+ * accessTime (time in milliseconds session was accessed)
+ * lastAccessTime (previous time in milliseconds session was accessed)
+ * createTime (time in milliseconds session created)
+ * cookieTime (time in milliseconds session cookie created)
+ * lastSavedTime (last time in milliseconds session access times were saved)
+ * expiryTime (time in milliseconds that the session is due to expire)
+ * map (attribute map)
+ *
+ * As an optimization, to prevent thrashing the database, we do not persist
+ * the accessTime and lastAccessTime every time the session is accessed. Rather,
+ * we write it out every so often. The frequency is controlled by the saveIntervalSec
+ * field.
+ */
+public class JDBCSessionManager extends AbstractSessionManager
+{
+    private static final Logger LOG = Log.getLogger(JDBCSessionManager.class);
+
+    private ConcurrentHashMap<String, Session> _sessions;
+    protected JDBCSessionIdManager _jdbcSessionIdMgr = null;
+    protected long _saveIntervalSec = 60; //only persist changes to session access times every 60 secs
+    protected SessionTableSchema _sessionTableSchema;
+
+   
+
+
+    /**
+     * Session
+     *
+     * Session instance.
+     */
+    public class Session extends MemSession
+    {
+        private static final long serialVersionUID = 5208464051134226143L;
+        
+        /**
+         * If dirty, session needs to be (re)persisted
+         */
+        protected boolean _dirty=false;
+        
+        
+        /**
+         * Time in msec since the epoch that a session cookie was set for this session
+         */
+        protected long _cookieSet;
+        
+        
+        /**
+         * Time in msec since the epoch that the session will expire
+         */
+        protected long _expiryTime;
+        
+        
+        /**
+         * Time in msec since the epoch that the session was last persisted
+         */
+        protected long _lastSaved;
+        
+        
+        /**
+         * Unique identifier of the last node to host the session
+         */
+        protected String _lastNode;
+        
+        
+        /**
+         * Virtual host for context (used to help distinguish 2 sessions with same id on different contexts)
+         */
+        protected String _virtualHost;
+        
+        
+        /**
+         * Unique row in db for session
+         */
+        protected String _rowId;
+        
+        
+        /**
+         * Mangled context name (used to help distinguish 2 sessions with same id on different contexts)
+         */
+        protected String _canonicalContext;
+        
+   
+        /**
+         * Session from a request.
+         *
+         * @param request
+         */
+        protected Session (HttpServletRequest request)
+        {
+            super(JDBCSessionManager.this,request);
+            int maxInterval=getMaxInactiveInterval();
+            _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+            _virtualHost = JDBCSessionManager.getVirtualHost(_context);
+            _canonicalContext = canonicalize(_context.getContextPath());
+            _lastNode = getSessionIdManager().getWorkerName();
+        }
+        
+        
+        /**
+         * Session restored from database
+         * @param sessionId
+         * @param rowId
+         * @param created
+         * @param accessed
+         */
+        protected Session (String sessionId, String rowId, long created, long accessed, long maxInterval)
+        {
+            super(JDBCSessionManager.this, created, accessed, sessionId);
+            _rowId = rowId;
+            super.setMaxInactiveInterval((int)maxInterval); //restore the session's previous inactivity interval setting
+            _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+        }
+        
+        
+        protected synchronized String getRowId()
+        {
+            return _rowId;
+        }
+        
+        protected synchronized void setRowId(String rowId)
+        {
+            _rowId = rowId;
+        }
+        
+        public synchronized void setVirtualHost (String vhost)
+        {
+            _virtualHost=vhost;
+        }
+
+        public synchronized String getVirtualHost ()
+        {
+            return _virtualHost;
+        }
+        
+        public synchronized long getLastSaved ()
+        {
+            return _lastSaved;
+        }
+
+        public synchronized void setLastSaved (long time)
+        {
+            _lastSaved=time;
+        }
+
+        public synchronized void setExpiryTime (long time)
+        {
+            _expiryTime=time;
+        }
+
+        public synchronized long getExpiryTime ()
+        {
+            return _expiryTime;
+        }
+        
+
+        public synchronized void setCanonicalContext(String str)
+        {
+            _canonicalContext=str;
+        }
+
+        public synchronized String getCanonicalContext ()
+        {
+            return _canonicalContext;
+        }
+        
+        public void setCookieSet (long ms)
+        {
+            _cookieSet = ms;
+        }
+
+        public synchronized long getCookieSet ()
+        {
+            return _cookieSet;
+        }
+
+        public synchronized void setLastNode (String node)
+        {
+            _lastNode=node;
+        }
+
+        public synchronized String getLastNode ()
+        {
+            return _lastNode;
+        }
+
+        @Override
+        public void setAttribute (String name, Object value)
+        {
+            Object old = changeAttribute(name, value);
+            if (value == null && old == null)
+                return; //if same as remove attribute but attribute was already removed, no change
+            
+            _dirty = true;
+        }
+
+        @Override
+        public void removeAttribute (String name)
+        {
+            Object old = changeAttribute(name, null);
+            if (old != null) //only dirty if there was a previous value
+                _dirty=true;
+        }
+
+        @Override
+        protected void cookieSet()
+        {
+            _cookieSet = getAccessed();
+        }
+
+        /**
+         * Entry to session.
+         * Called by SessionHandler on inbound request and the session already exists in this node's memory.
+         *
+         * @see org.eclipse.jetty.server.session.AbstractSession#access(long)
+         */
+        @Override
+        protected boolean access(long time)
+        {
+            synchronized (this)
+            {
+                if (super.access(time))
+                {
+                    int maxInterval=getMaxInactiveInterval();
+                    _expiryTime = (maxInterval <= 0 ? 0 : (time + maxInterval*1000L));
+                    return true;
+                }
+                return false;
+            }
+        }
+        
+        
+        
+
+
+        /** 
+         * Change the max idle time for this session. This recalculates the expiry time.
+         * @see org.eclipse.jetty.server.session.AbstractSession#setMaxInactiveInterval(int)
+         */
+        @Override
+        public void setMaxInactiveInterval(int secs)
+        {
+            synchronized (this)
+            {
+                super.setMaxInactiveInterval(secs);
+                int maxInterval=getMaxInactiveInterval();
+                _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+                //force the session to be written out right now
+                try
+                {
+                    updateSessionAccessTime(this);
+                }
+                catch (Exception e)
+                {
+                    LOG.warn("Problem saving changed max idle time for session "+ this, e);
+                }
+            }
+        }
+
+
+        /**
+         * Exit from session
+         * @see org.eclipse.jetty.server.session.AbstractSession#complete()
+         */
+        @Override
+        protected void complete()
+        {
+            synchronized (this)
+            {
+                super.complete();
+                try
+                {
+                    if (isValid())
+                    {
+                        if (_dirty)
+                        {
+                            //The session attributes have changed, write to the db, ensuring
+                            //http passivation/activation listeners called
+                            save(true);
+                        }
+                        else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L))
+                        {
+                            updateSessionAccessTime(this);
+                        }
+                    }
+                }
+                catch (Exception e)
+                {
+                    LOG.warn("Problem persisting changed session data id="+getId(), e);
+                }
+                finally
+                {
+                    _dirty=false;
+                }
+            }
+        }
+
+        protected void save() throws Exception
+        {
+            synchronized (this)
+            {
+                try
+                {
+                    updateSession(this);
+                }
+                finally
+                {
+                    _dirty = false;
+                }
+            }
+        }
+
+        protected void save (boolean reactivate) throws Exception
+        {
+            synchronized (this)
+            {
+                if (_dirty)
+                {
+                    //The session attributes have changed, write to the db, ensuring
+                    //http passivation/activation listeners called
+                    willPassivate();                      
+                    updateSession(this);
+                    if (reactivate)
+                        didActivate();  
+                }
+            }
+        }
+
+        
+        @Override
+        protected void timeout() throws IllegalStateException
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Timing out session id="+getClusterId());
+            super.timeout();
+        }
+        
+        
+        @Override
+        public String toString ()
+        {
+            return "Session rowId="+_rowId+",id="+getId()+",lastNode="+_lastNode+
+                            ",created="+getCreationTime()+",accessed="+getAccessed()+
+                            ",lastAccessed="+getLastAccessedTime()+",cookieSet="+_cookieSet+
+                            ",maxInterval="+getMaxInactiveInterval()+",lastSaved="+_lastSaved+",expiry="+_expiryTime;
+        }
+    }
+
+
+
+
+    /**
+     * Set the time in seconds which is the interval between
+     * saving the session access time to the database.
+     *
+     * This is an optimization that prevents the database from
+     * being overloaded when a session is accessed very frequently.
+     *
+     * On session exit, if the session attributes have NOT changed,
+     * the time at which we last saved the accessed
+     * time is compared to the current accessed time. If the interval
+     * is at least saveIntervalSecs, then the access time will be
+     * persisted to the database.
+     *
+     * If any session attribute does change, then the attributes and
+     * the accessed time are persisted.
+     *
+     * @param sec
+     */
+    public void setSaveInterval (long sec)
+    {
+        _saveIntervalSec=sec;
+    }
+
+    public long getSaveInterval ()
+    {
+        return _saveIntervalSec;
+    }
+
+
+
+    /**
+     * A method that can be implemented in subclasses to support
+     * distributed caching of sessions. This method will be
+     * called whenever the session is written to the database
+     * because the session data has changed.
+     *
+     * This could be used eg with a JMS backplane to notify nodes
+     * that the session has changed and to delete the session from
+     * the node's cache, and re-read it from the database.
+     * @param session
+     */
+    public void cacheInvalidate (Session session)
+    {
+
+    }
+
+
+    /**
+     * A session has been requested by its id on this node.
+     *
+     * Load the session by id AND context path from the database.
+     * Multiple contexts may share the same session id (due to dispatching)
+     * but they CANNOT share the same contents.
+     *
+     * Check if last node id is my node id, if so, then the session we have
+     * in memory cannot be stale. If another node used the session last, then
+     * we need to refresh from the db.
+     *
+     * NOTE: this method will go to the database, so if you only want to check
+     * for the existence of a Session in memory, use _sessions.get(id) instead.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSession(java.lang.String)
+     */
+    @Override
+    public Session getSession(String idInCluster)
+    {
+        Session session = null;
+        
+        synchronized (this)
+        {
+            Session memSession = (Session)_sessions.get(idInCluster);
+            
+            //check if we need to reload the session -
+            //as an optimization, don't reload on every access
+            //to reduce the load on the database. This introduces a window of
+            //possibility that the node may decide that the session is local to it,
+            //when the session has actually been live on another node, and then
+            //re-migrated to this node. This should be an extremely rare occurrence,
+            //as load-balancers are generally well-behaved and consistently send
+            //sessions to the same node, changing only iff that node fails.
+            //Session data = null;
+            long now = System.currentTimeMillis();
+            if (LOG.isDebugEnabled())
+            {
+                if (memSession==null)
+                    LOG.debug("getSession("+idInCluster+"): not in session map,"+
+                            " now="+now+
+                            " lastSaved="+(memSession==null?0:memSession._lastSaved)+
+                            " interval="+(_saveIntervalSec * 1000L));
+                else
+                    LOG.debug("getSession("+idInCluster+"): in session map, "+
+                            " hashcode="+memSession.hashCode()+
+                            " now="+now+
+                            " lastSaved="+(memSession==null?0:memSession._lastSaved)+
+                            " interval="+(_saveIntervalSec * 1000L)+
+                            " lastNode="+memSession._lastNode+
+                            " thisNode="+getSessionIdManager().getWorkerName()+
+                            " difference="+(now - memSession._lastSaved));
+            }
+
+            try
+            {
+                if (memSession==null)
+                {
+                    LOG.debug("getSession("+idInCluster+"): no session in session map. Reloading session data from db.");
+                    session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+                }
+                else if ((now - memSession._lastSaved) >= (_saveIntervalSec * 1000L))
+                {
+                    LOG.debug("getSession("+idInCluster+"): stale session. Reloading session data from db.");
+                    session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+                }
+                else
+                {
+                    LOG.debug("getSession("+idInCluster+"): session in session map");
+                    session = memSession;
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Unable to load session "+idInCluster, e);
+                return null;
+            }
+
+
+            //If we have a session
+            if (session != null)
+            {
+                //If the session was last used on a different node, or session doesn't exist on this node
+                if (!session.getLastNode().equals(getSessionIdManager().getWorkerName()) || memSession==null)
+                {
+                    //if session doesn't expire, or has not already expired, update it and put it in this nodes' memory
+                    if (session._expiryTime <= 0 || session._expiryTime > now)
+                    {
+                        if (LOG.isDebugEnabled()) 
+                            LOG.debug("getSession("+idInCluster+"): lastNode="+session.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName());
+
+                        session.setLastNode(getSessionIdManager().getWorkerName());                            
+                        _sessions.put(idInCluster, session);
+
+                        //update in db
+                        try
+                        {
+                            updateSessionNode(session);
+                            session.didActivate();
+                        }
+                        catch (Exception e)
+                        {
+                            LOG.warn("Unable to update freshly loaded session "+idInCluster, e);
+                            return null;
+                        }
+                    }
+                    else
+                    {
+                        LOG.debug("getSession ({}): Session has expired", idInCluster);  
+                        //ensure that the session id for the expired session is deleted so that a new session with the 
+                        //same id cannot be created (because the idInUse() test would succeed)
+                        _jdbcSessionIdMgr.removeSession(idInCluster);
+                        session=null;
+                    }
+
+                }
+                else
+                {
+                    //the session loaded from the db and the one in memory are the same, so keep using the one in memory
+                    session = memSession;
+                    LOG.debug("getSession({}): Session not stale {}", idInCluster,session);
+                }
+            }
+            else
+            {
+                //No session in db with matching id and context path.
+                LOG.debug("getSession({}): No session in database matching id={}",idInCluster,idInCluster);
+            }
+
+            return session;
+        }
+    }
+    
+
+    /**
+     * Get the number of sessions.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSessions()
+     */
+    @Override
+    public int getSessions()
+    {
+        return _sessions.size();
+    }
+
+
+    /**
+     * Start the session manager.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStart()
+     */
+    @Override
+    public void doStart() throws Exception
+    {
+        if (_sessionIdManager==null)
+            throw new IllegalStateException("No session id manager defined");
+
+        _jdbcSessionIdMgr = (JDBCSessionIdManager)_sessionIdManager;
+        _sessionTableSchema = _jdbcSessionIdMgr.getSessionTableSchema();
+
+        _sessions = new ConcurrentHashMap<String, Session>();
+
+        super.doStart();
+    }
+
+
+    /**
+     * Stop the session manager.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStop()
+     */
+    @Override
+    public void doStop() throws Exception
+    {
+        super.doStop();
+        _sessions.clear();
+        _sessions = null;
+    }
+
+    @Override
+    protected void shutdownSessions()
+    {
+        //Save the current state of all of our sessions,
+        //do NOT delete them (so other nodes can manage them)
+        long gracefulStopMs = getContextHandler().getServer().getStopTimeout();
+        long stopTime = 0;
+        if (gracefulStopMs > 0)
+            stopTime = System.nanoTime() + (TimeUnit.NANOSECONDS.convert(gracefulStopMs, TimeUnit.MILLISECONDS));        
+
+        ArrayList<Session> sessions = (_sessions == null? new ArrayList<Session>() :new ArrayList<Session>(_sessions.values()) );
+
+        // loop while there are sessions, and while there is stop time remaining, or if no stop time, just 1 loop
+        while (sessions.size() > 0 && ((stopTime > 0 && (System.nanoTime() < stopTime)) || (stopTime == 0)))
+        {
+            for (Session session : sessions)
+            {
+                try
+                {
+                    session.save(false);
+                }
+                catch (Exception e)
+                {
+                    LOG.warn(e);
+                }
+                _sessions.remove(session.getClusterId());
+            }
+
+            //check if we should terminate our loop if we're not using the stop timer
+            if (stopTime == 0)
+                break;
+            
+            // Get any sessions that were added by other requests during processing and go around the loop again
+            sessions=new ArrayList<Session>(_sessions.values());
+        }
+    }
+
+    
+    /**
+     * 
+     * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+     */
+    public void renewSessionId (String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+    {
+        Session session = null;
+        try
+        {
+            session = (Session)_sessions.remove(oldClusterId);
+            if (session != null)
+            {
+                synchronized (session)
+                {
+                    session.setClusterId(newClusterId); //update ids
+                    session.setNodeId(newNodeId);
+                    _sessions.put(newClusterId, session); //put it into list in memory
+                    updateSession(session); //update database
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+        }
+
+        super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
+    }
+
+    
+
+    /**
+     * Invalidate a session.
+     *
+     * @param idInCluster
+     */
+    protected void invalidateSession (String idInCluster)
+    {
+        Session session = (Session)_sessions.get(idInCluster);
+
+        if (session != null)
+        {
+            session.invalidate();
+        }
+    }
+
+    /**
+     * Delete an existing session, both from the in-memory map and
+     * the database.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String)
+     */
+    @Override
+    protected boolean removeSession(String idInCluster)
+    {
+        Session session = (Session)_sessions.remove(idInCluster);
+        try
+        {
+            if (session != null)
+                deleteSession(session);
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Problem deleting session id="+idInCluster, e);
+        }
+        return session!=null;
+    }
+
+
+    /**
+     * Add a newly created session to our in-memory list for this node and persist it.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession)
+     */
+    @Override
+    protected void addSession(AbstractSession session)
+    {
+        if (session==null)
+            return;
+
+        _sessions.put(session.getClusterId(), (Session)session);
+
+        try
+        {
+            synchronized (session)
+            {
+                session.willPassivate();
+                storeSession(((JDBCSessionManager.Session)session));
+                session.didActivate();
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Unable to store new session id="+session.getId() , e);
+        }
+    }
+
+
+    /**
+     * Make a new Session.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#newSession(javax.servlet.http.HttpServletRequest)
+     */
+    @Override
+    protected AbstractSession newSession(HttpServletRequest request)
+    {
+        return new Session(request);
+    }
+    
+    
+    /**
+     * @param sessionId
+     * @param rowId
+     * @param created
+     * @param accessed
+     * @param maxInterval
+     * @return
+     */
+    protected AbstractSession newSession (String sessionId, String rowId, long created, long accessed, long maxInterval)
+    {
+        return new Session(sessionId, rowId, created, accessed, maxInterval);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Remove session from manager
+     * @param session The session to remove
+     * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
+     * {@link SessionIdManager#invalidateAll(String)} should be called.
+     */
+    @Override
+    public boolean removeSession(AbstractSession session, boolean invalidate)
+    {
+        // Remove session from context and global maps
+        boolean removed = super.removeSession(session, invalidate);
+
+        if (removed)
+        {
+            if (!invalidate)
+            {
+                session.willPassivate();
+            }
+        }
+        
+        return removed;
+    }
+
+
+    /**
+     * Expire any Sessions we have in memory matching the list of
+     * expired Session ids.
+     *
+     * @param sessionIds
+     */
+    protected Set<String> expire (Set<String> sessionIds)
+    {
+        //don't attempt to scavenge if we are shutting down
+        if (isStopping() || isStopped())
+            return null;
+
+        
+        Thread thread=Thread.currentThread();
+        ClassLoader old_loader=thread.getContextClassLoader();
+        
+        Set<String> successfullyExpiredIds = new HashSet<String>();
+        try
+        {
+            Iterator<?> itor = sessionIds.iterator();
+            while (itor.hasNext())
+            {
+                String sessionId = (String)itor.next();
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Expiring session id "+sessionId);
+
+                Session session = (Session)_sessions.get(sessionId);
+
+                //if session is not in our memory, then fetch from db so we can call the usual listeners on it
+                if (session == null)
+                {
+                    if (LOG.isDebugEnabled())LOG.debug("Force loading session id "+sessionId);
+                    session = loadSession(sessionId, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+                    if (session != null)
+                    {
+                        //loaded an expired session last managed on this node for this context, add it to the list so we can 
+                        //treat it like a normal expired session
+                        _sessions.put(session.getClusterId(), session);
+                    }
+                    else
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Unrecognized session id="+sessionId);
+                        continue;
+                    }
+                }
+
+                if (session != null)
+                {
+                    session.timeout();
+                    successfullyExpiredIds.add(session.getClusterId());
+                }
+            }
+            return successfullyExpiredIds;
+        }
+        catch (Throwable t)
+        {
+            LOG.warn("Problem expiring sessions", t);
+            return successfullyExpiredIds;
+        }
+        finally
+        {
+            thread.setContextClassLoader(old_loader);
+        }
+    }
+    
+  
+    /**
+     * Load a session from the database
+     * @param id
+     * @return the session data that was loaded
+     * @throws Exception
+     */
+    protected Session loadSession (final String id, final String canonicalContextPath, final String vhost)
+    throws Exception
+    {
+        final AtomicReference<Session> _reference = new AtomicReference<Session>();
+        final AtomicReference<Exception> _exception = new AtomicReference<Exception>();
+        Runnable load = new Runnable()
+        {
+            /** 
+             * @see java.lang.Runnable#run()
+             */
+            @SuppressWarnings("unchecked")
+            public void run()
+            {
+                try (Connection connection = getConnection();
+                        PreparedStatement statement = _sessionTableSchema.getLoadStatement(connection, id, canonicalContextPath, vhost);
+                        ResultSet result = statement.executeQuery())
+                {
+                    Session session = null;
+                    if (result.next())
+                    {                    
+                        long maxInterval = result.getLong(_sessionTableSchema.getMaxIntervalColumn());
+                        if (maxInterval == JDBCSessionIdManager.MAX_INTERVAL_NOT_SET)
+                        {
+                            maxInterval = getMaxInactiveInterval(); //if value not saved for maxInactiveInterval, use current value from sessionmanager
+                        }
+                        session = (Session)newSession(id, result.getString(_sessionTableSchema.getRowIdColumn()), 
+                                                  result.getLong(_sessionTableSchema.getCreateTimeColumn()), 
+                                                  result.getLong(_sessionTableSchema.getAccessTimeColumn()), 
+                                                  maxInterval);
+                        session.setCookieSet(result.getLong(_sessionTableSchema.getCookieTimeColumn()));
+                        session.setLastAccessedTime(result.getLong(_sessionTableSchema.getLastAccessTimeColumn()));
+                        session.setLastNode(result.getString(_sessionTableSchema.getLastNodeColumn()));
+                        session.setLastSaved(result.getLong(_sessionTableSchema.getLastSavedTimeColumn()));
+                        session.setExpiryTime(result.getLong(_sessionTableSchema.getExpiryTimeColumn()));
+                        session.setCanonicalContext(result.getString(_sessionTableSchema.getContextPathColumn()));
+                        session.setVirtualHost(result.getString(_sessionTableSchema.getVirtualHostColumn()));
+                                           
+                        try (InputStream is = ((JDBCSessionIdManager)getSessionIdManager())._dbAdaptor.getBlobInputStream(result, _sessionTableSchema.getMapColumn());
+                                ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is))
+                        {
+                            Object o = ois.readObject();
+                            session.addAttributes((Map<String,Object>)o);
+                        }
+
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("LOADED session "+session);
+                    }
+                    else
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Failed to load session "+id);
+                    _reference.set(session);
+                }
+                catch (Exception e)
+                {
+                    _exception.set(e);
+                }
+            }
+        };
+
+        if (_context==null)
+            load.run();
+        else
+            _context.getContextHandler().handle(load);
+
+        if (_exception.get()!=null)
+        {
+            //if the session could not be restored, take its id out of the pool of currently-in-use
+            //session ids
+            _jdbcSessionIdMgr.removeSession(id);
+            throw _exception.get();
+        }
+
+        return _reference.get();
+    }
+
+    /**
+     * Insert a session into the database.
+     *
+     * @param session
+     * @throws Exception
+     */
+    protected void storeSession (Session session)
+    throws Exception
+    {
+        if (session==null)
+            return;
+
+        //put into the database
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._insertSession))
+        {
+            String rowId = calculateRowId(session);
+
+            long now = System.currentTimeMillis();
+            connection.setAutoCommit(true);
+            statement.setString(1, rowId); //rowId
+            statement.setString(2, session.getClusterId()); //session id
+            statement.setString(3, session.getCanonicalContext()); //context path
+            statement.setString(4, session.getVirtualHost()); //first vhost
+            statement.setString(5, getSessionIdManager().getWorkerName());//my node id
+            statement.setLong(6, session.getAccessed());//accessTime
+            statement.setLong(7, session.getLastAccessedTime()); //lastAccessTime
+            statement.setLong(8, session.getCreationTime()); //time created
+            statement.setLong(9, session.getCookieSet());//time cookie was set
+            statement.setLong(10, now); //last saved time
+            statement.setLong(11, session.getExpiryTime());
+            statement.setLong(12, session.getMaxInactiveInterval());
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ObjectOutputStream(baos);
+            oos.writeObject(session.getAttributeMap());
+            oos.flush();
+            byte[] bytes = baos.toByteArray();
+
+            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+            statement.setBinaryStream(13, bais, bytes.length);//attribute map as blob
+           
+
+            statement.executeUpdate();
+            session.setRowId(rowId); //set it on the in-memory data as well as in db
+            session.setLastSaved(now);
+        }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Stored session "+session);
+    }
+
+
+    /**
+     * Update data on an existing persisted session.
+     *
+     * @param data the session
+     * @throws Exception
+     */
+    protected void updateSession (Session data)
+    throws Exception
+    {
+        if (data==null)
+            return;
+
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSession))
+        {
+            long now = System.currentTimeMillis();
+            connection.setAutoCommit(true);
+            statement.setString(1, data.getClusterId());
+            statement.setString(2, getSessionIdManager().getWorkerName());//my node id
+            statement.setLong(3, data.getAccessed());//accessTime
+            statement.setLong(4, data.getLastAccessedTime()); //lastAccessTime
+            statement.setLong(5, now); //last saved time
+            statement.setLong(6, data.getExpiryTime());
+            statement.setLong(7, data.getMaxInactiveInterval());
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ObjectOutputStream(baos);
+            oos.writeObject(data.getAttributeMap());
+            oos.flush();
+            byte[] bytes = baos.toByteArray();
+            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+
+            statement.setBinaryStream(8, bais, bytes.length);//attribute map as blob
+            statement.setString(9, data.getRowId()); //rowId
+            statement.executeUpdate();
+
+            data.setLastSaved(now);
+        }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Updated session "+data);
+    }
+
+
+    /**
+     * Update the node on which the session was last seen to be my node.
+     *
+     * @param data the session
+     * @throws Exception
+     */
+    protected void updateSessionNode (Session data)
+    throws Exception
+    {
+        String nodeId = getSessionIdManager().getWorkerName();
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionNode))
+        {
+            connection.setAutoCommit(true);
+            statement.setString(1, nodeId);
+            statement.setString(2, data.getRowId());
+            statement.executeUpdate();
+        }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Updated last node for session id="+data.getId()+", lastNode = "+nodeId);
+    }
+
+    /**
+     * Persist the time the session was last accessed.
+     *
+     * @param data the session
+     * @throws Exception
+     */
+    private void updateSessionAccessTime (Session data)
+    throws Exception
+    {
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionAccessTime))
+        {
+            long now = System.currentTimeMillis();
+            connection.setAutoCommit(true);
+            statement.setString(1, getSessionIdManager().getWorkerName());
+            statement.setLong(2, data.getAccessed());
+            statement.setLong(3, data.getLastAccessedTime());
+            statement.setLong(4, now);
+            statement.setLong(5, data.getExpiryTime());
+            statement.setLong(6, data.getMaxInactiveInterval());
+            statement.setString(7, data.getRowId());
+          
+            statement.executeUpdate();
+            data.setLastSaved(now);
+        }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Updated access time session id="+data.getId()+" with lastsaved="+data.getLastSaved());
+    }
+
+
+
+
+    /**
+     * Delete a session from the database. Should only be called
+     * when the session has been invalidated.
+     *
+     * @param data
+     * @throws Exception
+     */
+    protected void deleteSession (Session data)
+    throws Exception
+    {
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._deleteSession))
+        {
+            connection.setAutoCommit(true);
+            statement.setString(1, data.getRowId());
+            statement.executeUpdate();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Deleted Session "+data);
+        }
+    }
+
+
+
+    /**
+     * Get a connection from the driver.
+     * @return
+     * @throws SQLException
+     */
+    private Connection getConnection ()
+    throws SQLException
+    {
+        return ((JDBCSessionIdManager)getSessionIdManager()).getConnection();
+    }
+
+    /**
+     * Calculate a unique id for this session across the cluster.
+     *
+     * Unique id is composed of: contextpath_virtualhost0_sessionid
+     * @param data
+     * @return
+     */
+    private String calculateRowId (Session data)
+    {
+        String rowId = canonicalize(_context.getContextPath());
+        rowId = rowId + "_" + getVirtualHost(_context);
+        rowId = rowId+"_"+data.getId();
+        return rowId;
+    }
+
+    /**
+     * Get the first virtual host for the context.
+     *
+     * Used to help identify the exact session/contextPath.
+     *
+     * @return 0.0.0.0 if no virtual host is defined
+     */
+    private static String getVirtualHost (ContextHandler.Context context)
+    {
+        String vhost = "0.0.0.0";
+
+        if (context==null)
+            return vhost;
+
+        String [] vhosts = context.getContextHandler().getVirtualHosts();
+        if (vhosts==null || vhosts.length==0 || vhosts[0]==null)
+            return vhost;
+
+        return vhosts[0];
+    }
+
+    /**
+     * Make an acceptable file name from a context path.
+     *
+     * @param path
+     * @return
+     */
+    private static String canonicalize (String path)
+    {
+        if (path==null)
+            return "";
+
+        return path.replace('/', '_').replace('.','_').replace('\\','_');
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/MemSession.java b/lib/jetty/org/eclipse/jetty/server/session/MemSession.java
new file mode 100644 (file)
index 0000000..72dbea8
--- /dev/null
@@ -0,0 +1,146 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * MemSession
+ *
+ * A session whose data is kept in memory
+ */
+public class MemSession extends AbstractSession
+{
+
+    private final Map<String,Object> _attributes=new HashMap<String, Object>();
+
+    protected MemSession(AbstractSessionManager abstractSessionManager, HttpServletRequest request)
+    {
+        super(abstractSessionManager, request);
+    }
+
+    public MemSession(AbstractSessionManager abstractSessionManager, long created, long accessed, String clusterId)
+    {
+        super(abstractSessionManager, created, accessed, clusterId);
+    }
+    
+    
+    /* ------------------------------------------------------------- */
+    @Override
+    public Map<String,Object> getAttributeMap()
+    {
+        return _attributes;
+    }
+   
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int getAttributes()
+    {
+        synchronized (this)
+        {
+            checkValid();
+            return _attributes.size();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings({ "unchecked" })
+    @Override
+    public Enumeration<String> doGetAttributeNames()
+    {
+        List<String> names=_attributes==null?Collections.EMPTY_LIST:new ArrayList<String>(_attributes.keySet());
+        return Collections.enumeration(names);
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public Set<String> getNames()
+    {
+        synchronized (this)
+        {
+            return new HashSet<String>(_attributes.keySet());
+        }
+    }
+   
+    
+    /* ------------------------------------------------------------- */
+    @Override
+    public void clearAttributes()
+    {
+        while (_attributes!=null && _attributes.size()>0)
+        {
+            ArrayList<String> keys;
+            synchronized(this)
+            {
+                keys=new ArrayList<String>(_attributes.keySet());
+            }
+
+            Iterator<String> iter=keys.iterator();
+            while (iter.hasNext())
+            {
+                String key=(String)iter.next();
+
+                Object value;
+                synchronized(this)
+                {
+                    value=doPutOrRemove(key,null);
+                }
+                unbindValue(key,value);
+
+                ((AbstractSessionManager)getSessionManager()).doSessionAttributeListeners(this,key,value,null);
+            }
+        }
+        if (_attributes!=null)
+            _attributes.clear();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addAttributes(Map<String,Object> map)
+    {
+        _attributes.putAll(map);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object doPutOrRemove(String name, Object value)
+    {
+        return value==null?_attributes.remove(name):_attributes.put(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object doGet(String name)
+    {
+        return _attributes.get(name);
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/SessionHandler.java b/lib/jetty/org/eclipse/jetty/server/session/SessionHandler.java
new file mode 100644 (file)
index 0000000..e6aedc5
--- /dev/null
@@ -0,0 +1,346 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.EventListener;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ScopedHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * SessionHandler.
+ */
+public class SessionHandler extends ScopedHandler
+{
+    final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
+
+    public final static EnumSet<SessionTrackingMode> DEFAULT_TRACKING = EnumSet.of(SessionTrackingMode.COOKIE,SessionTrackingMode.URL);
+    
+    public static final Class[] SESSION_LISTENER_TYPES = new Class[] {HttpSessionAttributeListener.class,
+                                                                      HttpSessionIdListener.class,
+                                                                      HttpSessionListener.class};
+
+
+
+    /* -------------------------------------------------------------- */
+    private SessionManager _sessionManager;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Constructor. Construct a SessionHandler witha a HashSessionManager with a standard java.util.Random generator is created.
+     */
+    public SessionHandler()
+    {
+        this(new HashSessionManager());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param manager
+     *            The session manager
+     */
+    public SessionHandler(SessionManager manager)
+    {
+        setSessionManager(manager);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionManager.
+     */
+    public SessionManager getSessionManager()
+    {
+        return _sessionManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionManager
+     *            The sessionManager to set.
+     */
+    public void setSessionManager(SessionManager sessionManager)
+    {
+        if (isStarted())
+            throw new IllegalStateException();
+        if (sessionManager != null)
+            sessionManager.setSessionHandler(this);
+        updateBean(_sessionManager,sessionManager);
+        _sessionManager=sessionManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (_sessionManager==null)
+            setSessionManager(new HashSessionManager());
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        // Destroy sessions before destroying servlets/filters see JETTY-1266
+        super.doStop();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        SessionManager old_session_manager = null;
+        HttpSession old_session = null;
+        HttpSession access = null;
+        try
+        {
+            old_session_manager = baseRequest.getSessionManager();
+            old_session = baseRequest.getSession(false);
+
+            if (old_session_manager != _sessionManager)
+            {
+                // new session context
+                baseRequest.setSessionManager(_sessionManager);
+                baseRequest.setSession(null);
+                checkRequestedSessionId(baseRequest,request);
+            }
+
+            // access any existing session
+            HttpSession session = null;
+            if (_sessionManager != null)
+            {
+                session = baseRequest.getSession(false);
+                if (session != null)
+                {
+                    if (session != old_session)
+                    {
+                        access = session;
+                        HttpCookie cookie = _sessionManager.access(session,request.isSecure());
+                        if (cookie != null) // Handle changed ID or max-age refresh
+                            baseRequest.getResponse().addCookie(cookie);
+                    }
+                }
+                else
+                {
+                    session = baseRequest.recoverNewSession(_sessionManager);
+                    if (session != null)
+                        baseRequest.setSession(session);
+                }
+            }
+
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("sessionManager=" + _sessionManager);
+                LOG.debug("session=" + session);
+            }
+
+            // start manual inline of nextScope(target,baseRequest,request,response);
+            if (_nextScope != null)
+                _nextScope.doScope(target,baseRequest,request,response);
+            else if (_outerScope != null)
+                _outerScope.doHandle(target,baseRequest,request,response);
+            else
+                doHandle(target,baseRequest,request,response);
+            // end manual inline (pathentic attempt to reduce stack depth)
+
+        }
+        finally
+        {
+            if (access != null)
+                _sessionManager.complete(access);
+
+            HttpSession session = baseRequest.getSession(false);
+            if (session != null && old_session == null && session != access)
+                _sessionManager.complete(session);
+
+            if (old_session_manager != null && old_session_manager != _sessionManager)
+            {
+                baseRequest.setSessionManager(old_session_manager);
+                baseRequest.setSession(old_session);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        // start manual inline of nextHandle(target,baseRequest,request,response);
+        if (never())
+            nextHandle(target,baseRequest,request,response);
+        else if (_nextScope != null && _nextScope == _handler)
+            _nextScope.doHandle(target,baseRequest,request,response);
+        else if (_handler != null)
+            _handler.handle(target,baseRequest,request,response);
+        // end manual inline
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Look for a requested session ID in cookies and URI parameters
+     *
+     * @param baseRequest
+     * @param request
+     */
+    protected void checkRequestedSessionId(Request baseRequest, HttpServletRequest request)
+    {
+        String requested_session_id = request.getRequestedSessionId();
+
+        SessionManager sessionManager = getSessionManager();
+
+        if (requested_session_id != null && sessionManager != null)
+        {
+            HttpSession session = sessionManager.getHttpSession(requested_session_id);
+            if (session != null && sessionManager.isValid(session))
+                baseRequest.setSession(session);
+            return;
+        }
+        else if (!DispatcherType.REQUEST.equals(baseRequest.getDispatcherType()))
+            return;
+
+        boolean requested_session_id_from_cookie = false;
+        HttpSession session = null;
+
+        // Look for session id cookie
+        if (_sessionManager.isUsingCookies())
+        {
+            Cookie[] cookies = request.getCookies();
+            if (cookies != null && cookies.length > 0)
+            {
+                final String sessionCookie=sessionManager.getSessionCookieConfig().getName();
+                for (int i = 0; i < cookies.length; i++)
+                {
+                    if (sessionCookie.equalsIgnoreCase(cookies[i].getName()))
+                    {
+                        requested_session_id = cookies[i].getValue();
+                        requested_session_id_from_cookie = true;
+
+                        LOG.debug("Got Session ID {} from cookie",requested_session_id);
+
+                        if (requested_session_id != null)
+                        {
+                            session = sessionManager.getHttpSession(requested_session_id);
+
+                            if (session != null && sessionManager.isValid(session))
+                            {
+                                break;
+                            }
+                        }
+                        else
+                        {
+                            LOG.warn("null session id from cookie");
+                        }
+                    }
+                }
+            }
+        }
+
+        if (requested_session_id == null || session == null)
+        {
+            String uri = request.getRequestURI();
+
+            String prefix = sessionManager.getSessionIdPathParameterNamePrefix();
+            if (prefix != null)
+            {
+                int s = uri.indexOf(prefix);
+                if (s >= 0)
+                {
+                    s += prefix.length();
+                    int i = s;
+                    while (i < uri.length())
+                    {
+                        char c = uri.charAt(i);
+                        if (c == ';' || c == '#' || c == '?' || c == '/')
+                            break;
+                        i++;
+                    }
+
+                    requested_session_id = uri.substring(s,i);
+                    requested_session_id_from_cookie = false;
+                    session = sessionManager.getHttpSession(requested_session_id);
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Got Session ID {} from URL",requested_session_id);
+                }
+            }
+        }
+
+        baseRequest.setRequestedSessionId(requested_session_id);
+        baseRequest.setRequestedSessionIdFromCookie(requested_session_id!=null && requested_session_id_from_cookie);
+        if (session != null && sessionManager.isValid(session))
+            baseRequest.setSession(session);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param listener
+     */
+    public void addEventListener(EventListener listener)
+    {
+        if (_sessionManager != null)
+            _sessionManager.addEventListener(listener);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param listener
+     */
+    public void removeEventListener(EventListener listener)
+    {
+        if (_sessionManager != null)
+            _sessionManager.removeEventListener(listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void clearEventListeners()
+    {
+        if (_sessionManager != null)
+            _sessionManager.clearEventListeners();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/package-info.java b/lib/jetty/org/eclipse/jetty/server/session/package-info.java
new file mode 100644 (file)
index 0000000..ed180a1
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Session Management Implementations
+ */
+package org.eclipse.jetty.server.session;
+
diff --git a/lib/jetty/org/eclipse/jetty/servlet/BaseHolder.java b/lib/jetty/org/eclipse/jetty/servlet/BaseHolder.java
new file mode 100644 (file)
index 0000000..a32e1a6
--- /dev/null
@@ -0,0 +1,209 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletContext;
+import javax.servlet.UnavailableException;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * AbstractHolder
+ * 
+ * Base class for all servlet-related classes that may be lazily instantiated  (eg servlet, filter, 
+ * listener), and/or require metadata to be held regarding their origin 
+ * (web.xml, annotation, programmatic api etc).
+ * 
+ */
+public abstract class BaseHolder<T> extends AbstractLifeCycle implements Dumpable
+{
+    private static final Logger LOG = Log.getLogger(BaseHolder.class);
+    
+    
+    public enum Source { EMBEDDED, JAVAX_API, DESCRIPTOR, ANNOTATION };
+    
+    final protected Source _source;
+    protected transient Class<? extends T> _class;
+    protected String _className;
+    protected boolean _extInstance;
+    protected ServletHandler _servletHandler;
+    
+    /* ---------------------------------------------------------------- */
+    protected BaseHolder(Source source)
+    {
+        _source=source;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Source getSource()
+    {
+        return _source;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Do any setup necessary after starting
+     * @throws Exception
+     */
+    public void initialize()
+    throws Exception
+    {
+        if (!isStarted())
+            throw new IllegalStateException("Not started: "+this);
+    }
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    @Override
+    public void doStart()
+        throws Exception
+    {
+        //if no class already loaded and no classname, make permanently unavailable
+        if (_class==null && (_className==null || _className.equals("")))
+            throw new UnavailableException("No class in holder");
+        
+        //try to load class
+        if (_class==null)
+        {
+            try
+            {
+                _class=Loader.loadClass(Holder.class, _className);
+                if(LOG.isDebugEnabled())
+                    LOG.debug("Holding {} from {}",_class,_class.getClassLoader());
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+                throw new UnavailableException(e.getMessage());
+            }
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStop()
+        throws Exception
+    {
+        if (!_extInstance)
+            _class=null;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute(value="Class Name", readonly=true)
+    public String getClassName()
+    {
+        return _className;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Class<? extends T> getHeldClass()
+    {
+        return _class;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletHandler.
+     */
+    public ServletHandler getServletHandler()
+    {
+        return _servletHandler;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletHandler The {@link ServletHandler} that will handle requests dispatched to this servlet.
+     */
+    public void setServletHandler(ServletHandler servletHandler)
+    {
+        _servletHandler = servletHandler;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param className The className to set.
+     */
+    public void setClassName(String className)
+    {
+        _className = className;
+        _class=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param held The class to hold
+     */
+    public void setHeldClass(Class<? extends T> held)
+    {
+        _class=held;
+        if (held!=null)
+        {
+            _className=held.getName();
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    protected void illegalStateIfContextStarted()
+    {
+        if (_servletHandler!=null)
+        {
+            ServletContext context=_servletHandler.getServletContext();
+            if ((context instanceof ContextHandler.Context) && ((ContextHandler.Context)context).getContextHandler().isStarted())
+                throw new IllegalStateException("Started");
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if this holder was created for a specific instance.
+     */
+    public boolean isInstance()
+    {
+        return _extInstance;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        out.append(toString())
+        .append(" - ").append(AbstractLifeCycle.getState(this)).append("\n");
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/DefaultServlet.java b/lib/jetty/org/eclipse/jetty/servlet/DefaultServlet.java
new file mode 100644 (file)
index 0000000..c378739
--- /dev/null
@@ -0,0 +1,1100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator.CachedHttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.http.PathMap.MappedEntry;
+import org.eclipse.jetty.io.WriterOutputStream;
+import org.eclipse.jetty.server.HttpOutput;
+import org.eclipse.jetty.server.InclusiveByteRange;
+import org.eclipse.jetty.server.ResourceCache;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.MultiPartOutputStream;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+
+
+
+/* ------------------------------------------------------------ */
+/** The default servlet.
+ * This servlet, normally mapped to /, provides the handling for static
+ * content, OPTION and TRACE methods for the context.
+ * The following initParameters are supported, these can be set either
+ * on the servlet itself or as ServletContext initParameters with a prefix
+ * of org.eclipse.jetty.servlet.Default. :
+ * <PRE>
+ *  acceptRanges      If true, range requests and responses are
+ *                    supported
+ *
+ *  dirAllowed        If true, directory listings are returned if no
+ *                    welcome file is found. Else 403 Forbidden.
+ *
+ *  welcomeServlets   If true, attempt to dispatch to welcome files
+ *                    that are servlets, but only after no matching static
+ *                    resources could be found. If false, then a welcome
+ *                    file must exist on disk. If "exact", then exact
+ *                    servlet matches are supported without an existing file.
+ *                    Default is true.
+ *
+ *                    This must be false if you want directory listings,
+ *                    but have index.jsp in your welcome file list.
+ *
+ *  redirectWelcome   If true, welcome files are redirected rather than
+ *                    forwarded to.
+ *
+ *  gzip              If set to true, then static content will be served as
+ *                    gzip content encoded if a matching resource is
+ *                    found ending with ".gz"
+ *
+ *  resourceBase      Set to replace the context resource base
+ *
+ *  resourceCache     If set, this is a context attribute name, which the servlet
+ *                    will use to look for a shared ResourceCache instance.
+ *
+ *  relativeResourceBase
+ *                    Set with a pathname relative to the base of the
+ *                    servlet context root. Useful for only serving static content out
+ *                    of only specific subdirectories.
+ *
+ *  pathInfoOnly      If true, only the path info will be applied to the resourceBase
+ *
+ *  stylesheet       Set with the location of an optional stylesheet that will be used
+ *                    to decorate the directory listing html.
+ *
+ *  etags             If True, weak etags will be generated and handled.
+ *
+ *  maxCacheSize      The maximum total size of the cache or 0 for no cache.
+ *  maxCachedFileSize The maximum size of a file to cache
+ *  maxCachedFiles    The maximum number of files to cache
+ *
+ *  useFileMappedBuffer
+ *                    If set to true, it will use mapped file buffer to serve static content
+ *                    when using NIO connector. Setting this value to false means that
+ *                    a direct buffer will be used instead of a mapped file buffer.
+ *                    By default, this is set to true.
+ *
+ *  cacheControl      If set, all static content will have this value set as the cache-control
+ *                    header.
+ *
+ *
+ * </PRE>
+ *
+ *
+ *
+ *
+ */
+public class DefaultServlet extends HttpServlet implements ResourceFactory
+{
+    private static final Logger LOG = Log.getLogger(DefaultServlet.class);
+
+    private static final long serialVersionUID = 4930458713846881193L;
+    
+    private static final CachedHttpField ACCEPT_RANGES = new CachedHttpField(HttpHeader.ACCEPT_RANGES, "bytes");
+    
+    private ServletContext _servletContext;
+    private ContextHandler _contextHandler;
+
+    private boolean _acceptRanges=true;
+    private boolean _dirAllowed=true;
+    private boolean _welcomeServlets=false;
+    private boolean _welcomeExactServlets=false;
+    private boolean _redirectWelcome=false;
+    private boolean _gzip=false;
+    private boolean _pathInfoOnly=false;
+    private boolean _etags=false;
+
+    private Resource _resourceBase;
+    private ResourceCache _cache;
+
+    private MimeTypes _mimeTypes;
+    private String[] _welcomes;
+    private Resource _stylesheet;
+    private boolean _useFileMappedBuffer=false;
+    private HttpField _cacheControl;
+    private String _relativeResourceBase;
+    private ServletHandler _servletHandler;
+    private ServletHolder _defaultHolder;
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void init()
+    throws UnavailableException
+    {
+        _servletContext=getServletContext();
+        _contextHandler = initContextHandler(_servletContext);
+
+        _mimeTypes = _contextHandler.getMimeTypes();
+
+        _welcomes = _contextHandler.getWelcomeFiles();
+        if (_welcomes==null)
+            _welcomes=new String[] {"index.html","index.jsp"};
+
+        _acceptRanges=getInitBoolean("acceptRanges",_acceptRanges);
+        _dirAllowed=getInitBoolean("dirAllowed",_dirAllowed);
+        _redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome);
+        _gzip=getInitBoolean("gzip",_gzip);
+        _pathInfoOnly=getInitBoolean("pathInfoOnly",_pathInfoOnly);
+
+        if ("exact".equals(getInitParameter("welcomeServlets")))
+        {
+            _welcomeExactServlets=true;
+            _welcomeServlets=false;
+        }
+        else
+            _welcomeServlets=getInitBoolean("welcomeServlets", _welcomeServlets);
+
+        _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer);
+
+        _relativeResourceBase = getInitParameter("relativeResourceBase");
+
+        String rb=getInitParameter("resourceBase");
+        if (rb!=null)
+        {
+            if (_relativeResourceBase!=null)
+                throw new  UnavailableException("resourceBase & relativeResourceBase");
+            try{_resourceBase=_contextHandler.newResource(rb);}
+            catch (Exception e)
+            {
+                LOG.warn(Log.EXCEPTION,e);
+                throw new UnavailableException(e.toString());
+            }
+        }
+
+        String css=getInitParameter("stylesheet");
+        try
+        {
+            if(css!=null)
+            {
+                _stylesheet = Resource.newResource(css);
+                if(!_stylesheet.exists())
+                {
+                    LOG.warn("!" + css);
+                    _stylesheet = null;
+                }
+            }
+            if(_stylesheet == null)
+            {
+                _stylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
+            }
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+        }
+
+        String cc=getInitParameter("cacheControl");
+        if (cc!=null)
+            _cacheControl=new CachedHttpField(HttpHeader.CACHE_CONTROL, cc);
+        
+        String resourceCache = getInitParameter("resourceCache");
+        int max_cache_size=getInitInt("maxCacheSize", -2);
+        int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
+        int max_cached_files=getInitInt("maxCachedFiles", -2);
+        if (resourceCache!=null)
+        {
+            if (max_cache_size!=-1 || max_cached_file_size!= -2 || max_cached_files!=-2)
+                LOG.debug("ignoring resource cache configuration, using resourceCache attribute");
+            if (_relativeResourceBase!=null || _resourceBase!=null)
+                throw new UnavailableException("resourceCache specified with resource bases");
+            _cache=(ResourceCache)_servletContext.getAttribute(resourceCache);
+
+            LOG.debug("Cache {}={}",resourceCache,_cache);
+        }
+
+        _etags = getInitBoolean("etags",_etags);
+        
+        try
+        {
+            if (_cache==null && max_cached_files>0)
+            {
+                _cache= new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_etags);
+
+                if (max_cache_size>0)
+                    _cache.setMaxCacheSize(max_cache_size);
+                if (max_cached_file_size>=-1)
+                    _cache.setMaxCachedFileSize(max_cached_file_size);
+                if (max_cached_files>=-1)
+                    _cache.setMaxCachedFiles(max_cached_files);
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+            throw new UnavailableException(e.toString());
+        }
+
+        _servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
+        for (ServletHolder h :_servletHandler.getServlets())
+            if (h.getServletInstance()==this)
+                _defaultHolder=h;
+
+        
+        if (LOG.isDebugEnabled())
+            LOG.debug("resource base = "+_resourceBase);
+    }
+
+    /**
+     * Compute the field _contextHandler.<br/>
+     * In the case where the DefaultServlet is deployed on the HttpService it is likely that
+     * this method needs to be overwritten to unwrap the ServletContext facade until we reach
+     * the original jetty's ContextHandler.
+     * @param servletContext The servletContext of this servlet.
+     * @return the jetty's ContextHandler for this servletContext.
+     */
+    protected ContextHandler initContextHandler(ServletContext servletContext)
+    {
+        ContextHandler.Context scontext=ContextHandler.getCurrentContext();
+        if (scontext==null)
+        {
+            if (servletContext instanceof ContextHandler.Context)
+                return ((ContextHandler.Context)servletContext).getContextHandler();
+            else
+                throw new IllegalArgumentException("The servletContext " + servletContext + " " +
+                    servletContext.getClass().getName() + " is not " + ContextHandler.Context.class.getName());
+        }
+        else
+            return ContextHandler.getCurrentContext().getContextHandler();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getInitParameter(String name)
+    {
+        String value=getServletContext().getInitParameter("org.eclipse.jetty.servlet.Default."+name);
+        if (value==null)
+            value=super.getInitParameter(name);
+        return value;
+    }
+
+    /* ------------------------------------------------------------ */
+    private boolean getInitBoolean(String name, boolean dft)
+    {
+        String value=getInitParameter(name);
+        if (value==null || value.length()==0)
+            return dft;
+        return (value.startsWith("t")||
+                value.startsWith("T")||
+                value.startsWith("y")||
+                value.startsWith("Y")||
+                value.startsWith("1"));
+    }
+
+    /* ------------------------------------------------------------ */
+    private int getInitInt(String name, int dft)
+    {
+        String value=getInitParameter(name);
+        if (value==null)
+            value=getInitParameter(name);
+        if (value!=null && value.length()>0)
+            return Integer.parseInt(value);
+        return dft;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** get Resource to serve.
+     * Map a path to a resource. The default implementation calls
+     * HttpContext.getResource but derived servlets may provide
+     * their own mapping.
+     * @param pathInContext The path to find a resource for.
+     * @return The resource to serve.
+     */
+    @Override
+    public Resource getResource(String pathInContext)
+    {
+        Resource r=null;
+        if (_relativeResourceBase!=null)
+            pathInContext=URIUtil.addPaths(_relativeResourceBase,pathInContext);
+
+        try
+        {
+            if (_resourceBase!=null)
+            {
+                r = _resourceBase.addPath(pathInContext);
+                if (!_contextHandler.checkAlias(pathInContext,r))
+                    r=null;
+            }
+            else if (_servletContext instanceof ContextHandler.Context)
+            {
+                r = _contextHandler.getResource(pathInContext);
+            }
+            else
+            {
+                URL u = _servletContext.getResource(pathInContext);
+                r = _contextHandler.newResource(u);
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("Resource "+pathInContext+"="+r);
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+
+        if((r==null || !r.exists()) && pathInContext.endsWith("/jetty-dir.css"))
+            r=_stylesheet;
+
+        return r;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response)
+    throws ServletException, IOException
+    {
+        String servletPath=null;
+        String pathInfo=null;
+        Enumeration<String> reqRanges = null;
+        Boolean included =request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI)!=null;
+        if (included!=null && included.booleanValue())
+        {
+            servletPath=(String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+            pathInfo=(String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
+            if (servletPath==null)
+            {
+                servletPath=request.getServletPath();
+                pathInfo=request.getPathInfo();
+            }
+        }
+        else
+        {
+            included = Boolean.FALSE;
+            servletPath = _pathInfoOnly?"/":request.getServletPath();
+            pathInfo = request.getPathInfo();
+
+            // Is this a Range request?
+            reqRanges = request.getHeaders(HttpHeader.RANGE.asString());
+            if (!hasDefinedRange(reqRanges))
+                reqRanges = null;
+        }
+
+        String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
+        boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
+
+
+        // Find the resource and content
+        Resource resource=null;
+        HttpContent content=null;
+        try
+        {
+            // is gzip enabled?
+            String pathInContextGz=null;
+            boolean gzip=false;
+            if (!included.booleanValue() && _gzip && reqRanges==null && !endsWithSlash )
+            {
+                // Look for a gzip resource
+                pathInContextGz=pathInContext+".gz";
+                if (_cache==null)
+                    resource=getResource(pathInContextGz);
+                else
+                {
+                    content=_cache.lookup(pathInContextGz);
+                    resource=(content==null)?null:content.getResource();
+                }
+
+                // Does a gzip resource exist?
+                if (resource!=null && resource.exists() && !resource.isDirectory())
+                {
+                    // Tell caches that response may vary by accept-encoding
+                    response.addHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
+                    
+                    // Does the client accept gzip?
+                    String accept=request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
+                    if (accept!=null && accept.indexOf("gzip")>=0)
+                        gzip=true;
+                }
+            }
+
+            // find resource
+            if (!gzip)
+            {
+                if (_cache==null)
+                    resource=getResource(pathInContext);
+                else
+                {
+                    content=_cache.lookup(pathInContext);
+                    resource=content==null?null:content.getResource();
+                }
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("uri="+request.getRequestURI()+" resource="+resource+(content!=null?" content":""));
+
+            // Handle resource
+            if (resource==null || !resource.exists())
+            {
+                if (included)
+                    throw new FileNotFoundException("!" + pathInContext);
+                response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            }
+            else if (!resource.isDirectory())
+            {
+                if (endsWithSlash && pathInContext.length()>1)
+                {
+                    String q=request.getQueryString();
+                    pathInContext=pathInContext.substring(0,pathInContext.length()-1);
+                    if (q!=null&&q.length()!=0)
+                        pathInContext+="?"+q;
+                    response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(_servletContext.getContextPath(),pathInContext)));
+                }
+                else
+                {
+                    // ensure we have content
+                    if (content==null)
+                        content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),response.getBufferSize(),_etags);
+
+                    if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
+                    {
+                        if (gzip)
+                        {
+                            response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),"gzip");
+                            String mt=_servletContext.getMimeType(pathInContext);
+                            if (mt!=null)
+                                response.setContentType(mt);
+                        }
+                        sendData(request,response,included.booleanValue(),resource,content,reqRanges);
+                    }
+                }
+            }
+            else
+            {
+                String welcome=null;
+
+                if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.eclipse.jetty.server.nullPathInfo")!=null))
+                {
+                    StringBuffer buf=request.getRequestURL();
+                    synchronized(buf)
+                    {
+                        int param=buf.lastIndexOf(";");
+                        if (param<0)
+                            buf.append('/');
+                        else
+                            buf.insert(param,'/');
+                        String q=request.getQueryString();
+                        if (q!=null&&q.length()!=0)
+                        {
+                            buf.append('?');
+                            buf.append(q);
+                        }
+                        response.setContentLength(0);
+                        response.sendRedirect(response.encodeRedirectURL(buf.toString()));
+                    }
+                }
+                // else look for a welcome file
+                else if (null!=(welcome=getWelcomeFile(pathInContext)))
+                {
+                    LOG.debug("welcome={}",welcome);
+                    if (_redirectWelcome)
+                    {
+                        // Redirect to the index
+                        response.setContentLength(0);
+                        String q=request.getQueryString();
+                        if (q!=null&&q.length()!=0)
+                            response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)+"?"+q));
+                        else
+                            response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)));
+                    }
+                    else
+                    {
+                        // Forward to the index
+                        RequestDispatcher dispatcher=request.getRequestDispatcher(welcome);
+                        if (dispatcher!=null)
+                        {
+                            if (included.booleanValue())
+                                dispatcher.include(request,response);
+                            else
+                            {
+                                request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
+                                dispatcher.forward(request,response);
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),_etags);
+                    if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
+                        sendDirectory(request,response,resource,pathInContext);
+                }
+            }
+        }
+        catch(IllegalArgumentException e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+            if(!response.isCommitted())
+                response.sendError(500, e.getMessage());
+        }
+        finally
+        {
+            if (content!=null)
+                content.release();
+            else if (resource!=null)
+                resource.close();
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    private boolean hasDefinedRange(Enumeration<String> reqRanges)
+    {
+        return (reqRanges!=null && reqRanges.hasMoreElements());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response)
+    throws ServletException, IOException
+    {
+        doGet(request,response);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+    {
+        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
+    throws ServletException, IOException
+    {
+        resp.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Finds a matching welcome file for the supplied {@link Resource}. This will be the first entry in the list of
+     * configured {@link #_welcomes welcome files} that existing within the directory referenced by the <code>Resource</code>.
+     * If the resource is not a directory, or no matching file is found, then it may look for a valid servlet mapping.
+     * If there is none, then <code>null</code> is returned.
+     * The list of welcome files is read from the {@link ContextHandler} for this servlet, or
+     * <code>"index.jsp" , "index.html"</code> if that is <code>null</code>.
+     * @param resource
+     * @return The path of the matching welcome file in context or null.
+     * @throws IOException
+     * @throws MalformedURLException
+     */
+    private String getWelcomeFile(String pathInContext) throws MalformedURLException, IOException
+    {
+        if (_welcomes==null)
+            return null;
+
+        String welcome_servlet=null;
+        for (int i=0;i<_welcomes.length;i++)
+        {
+            String welcome_in_context=URIUtil.addPaths(pathInContext,_welcomes[i]);
+            Resource welcome=getResource(welcome_in_context);
+            if (welcome!=null && welcome.exists())
+                return _welcomes[i];
+
+            if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
+            {
+                MappedEntry<?> entry=_servletHandler.getHolderEntry(welcome_in_context);
+                if (entry!=null && entry.getValue()!=_defaultHolder &&
+                        (_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context))))
+                    welcome_servlet=welcome_in_context;
+
+            }
+        }
+        return welcome_servlet;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Check modification date headers.
+     */
+    protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, Resource resource, HttpContent content)
+    throws IOException
+    {
+        try
+        {
+            if (!HttpMethod.HEAD.is(request.getMethod()))
+            {
+                if (_etags)
+                {
+                    String ifm=request.getHeader(HttpHeader.IF_MATCH.asString());
+                    if (ifm!=null)
+                    {
+                        boolean match=false;
+                        if (content.getETag()!=null)
+                        {
+                            QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
+                            while (!match && quoted.hasMoreTokens())
+                            {
+                                String tag = quoted.nextToken();
+                                if (content.getETag().equals(tag))
+                                    match=true;
+                            }
+                        }
+
+                        if (!match)
+                        {
+                            response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
+                            return false;
+                        }
+                    }
+                    
+                    String if_non_match_etag=request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
+                    if (if_non_match_etag!=null && content.getETag()!=null)
+                    {
+                        // Look for GzipFiltered version of etag
+                        if (content.getETag().equals(request.getAttribute("o.e.j.s.GzipFilter.ETag")))
+                        {
+                            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                            response.setHeader(HttpHeader.ETAG.asString(),if_non_match_etag);
+                            return false;
+                        }
+                        
+                        // Handle special case of exact match.
+                        if (content.getETag().equals(if_non_match_etag))
+                        {
+                            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                            response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+                            return false;
+                        }
+
+                        // Handle list of tags
+                        QuotedStringTokenizer quoted = new QuotedStringTokenizer(if_non_match_etag,", ",false,true);
+                        while (quoted.hasMoreTokens())
+                        {
+                            String tag = quoted.nextToken();
+                            if (content.getETag().equals(tag))
+                            {
+                                response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                                response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+                                return false;
+                            }
+                        }
+                        
+                        // If etag requires content to be served, then do not check if-modified-since
+                        return true;
+                    }
+                }
+                
+                // Handle if modified since
+                String ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
+                if (ifms!=null)
+                {
+                    //Get jetty's Response impl
+                    String mdlm=content.getLastModified();
+                    if (mdlm!=null && ifms.equals(mdlm))
+                    {
+                        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                        if (_etags)
+                            response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+                        response.flushBuffer();
+                        return false;
+                    }
+
+                    long ifmsl=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
+                    if (ifmsl!=-1 && resource.lastModified()/1000 <= ifmsl/1000)
+                    { 
+                        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                        if (_etags)
+                            response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+                        response.flushBuffer();
+                        return false;
+                    }
+                }
+
+                // Parse the if[un]modified dates and compare to resource
+                long date=request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString());
+                if (date!=-1 && resource.lastModified()/1000 > date/1000)
+                {
+                    response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+                    return false;
+                }
+
+            }
+        }
+        catch(IllegalArgumentException iae)
+        {
+            if(!response.isCommitted())
+                response.sendError(400, iae.getMessage());
+            throw iae;
+        }
+        return true;
+    }
+
+
+    /* ------------------------------------------------------------------- */
+    protected void sendDirectory(HttpServletRequest request,
+            HttpServletResponse response,
+            Resource resource,
+            String pathInContext)
+    throws IOException
+    {
+        if (!_dirAllowed)
+        {
+            response.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }
+
+        byte[] data=null;
+        String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH);
+
+        //If the DefaultServlet has a resource base set, use it
+        if (_resourceBase != null)
+        {
+            // handle ResourceCollection
+            if (_resourceBase instanceof ResourceCollection)
+                resource=_resourceBase.addPath(pathInContext);
+        }
+        //Otherwise, try using the resource base of its enclosing context handler
+        else if (_contextHandler.getBaseResource() instanceof ResourceCollection)
+            resource=_contextHandler.getBaseResource().addPath(pathInContext);
+
+        String dir = resource.getListHTML(base,pathInContext.length()>1);
+        if (dir==null)
+        {
+            response.sendError(HttpServletResponse.SC_FORBIDDEN,
+            "No directory");
+            return;
+        }
+
+        data=dir.getBytes("UTF-8");
+        response.setContentType("text/html; charset=UTF-8");
+        response.setContentLength(data.length);
+        response.getOutputStream().write(data);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void sendData(HttpServletRequest request,
+            HttpServletResponse response,
+            boolean include,
+            Resource resource,
+            HttpContent content,
+            Enumeration<String> reqRanges)
+    throws IOException
+    {
+        final long content_length = (content==null)?resource.length():content.getContentLength();
+
+        // Get the output stream (or writer)
+        OutputStream out =null;
+        boolean written;
+        try
+        {
+            out = response.getOutputStream();
+
+            // has a filter already written to the response?
+            written = out instanceof HttpOutput
+                ? ((HttpOutput)out).isWritten()
+                : true;
+        }
+        catch(IllegalStateException e)
+        {
+            out = new WriterOutputStream(response.getWriter());
+            written=true; // there may be data in writer buffer, so assume written
+        }
+
+        if ( reqRanges == null || !reqRanges.hasMoreElements() || content_length<0)
+        {
+            //  if there were no ranges, send entire entity
+            if (include)
+            {
+                resource.writeTo(out,0,content_length);
+            }
+            // else if we can't do a bypass write because of wrapping
+            else if (content==null || written || !(out instanceof HttpOutput))
+            {
+                // write normally
+                writeHeaders(response,content,written?-1:content_length);
+                ByteBuffer buffer = (content==null)?null:content.getIndirectBuffer();
+                if (buffer!=null)
+                    BufferUtil.writeTo(buffer,out);
+                else
+                    resource.writeTo(out,0,content_length);
+            }
+            // else do a bypass write
+            else
+            {
+                // write the headers
+                if (response instanceof Response)
+                {
+                    Response r = (Response)response;
+                    writeOptionHeaders(r.getHttpFields());
+                    r.setHeaders(content);
+                }
+                else
+                    writeHeaders(response,content,content_length);
+
+                // write the content asynchronously if supported
+                if (request.isAsyncSupported())
+                {
+                    final AsyncContext context = request.startAsync();
+
+                    ((HttpOutput)out).sendContent(content,new Callback()
+                    {
+                        @Override
+                        public void succeeded()
+                        {   
+                            context.complete();
+                        }
+
+                        @Override
+                        public void failed(Throwable x)
+                        {
+                            if (x instanceof IOException)
+                                LOG.debug(x);
+                            else
+                                LOG.warn(x);
+                            context.complete();
+                        }
+                    });
+                }
+                // otherwise write content blocking
+                else
+                {
+                    ((HttpOutput)out).sendContent(content);
+                }
+            }
+        }
+        else
+        {
+            // Parse the satisfiable ranges
+            List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
+
+            //  if there are no satisfiable ranges, send 416 response
+            if (ranges==null || ranges.size()==0)
+            {
+                writeHeaders(response, content, content_length);
+                response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+                response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
+                        InclusiveByteRange.to416HeaderRangeString(content_length));
+                resource.writeTo(out,0,content_length);
+                return;
+            }
+
+            //  if there is only a single valid range (must be satisfiable
+            //  since were here now), send that range with a 216 response
+            if ( ranges.size()== 1)
+            {
+                InclusiveByteRange singleSatisfiableRange = ranges.get(0);
+                long singleLength = singleSatisfiableRange.getSize(content_length);
+                writeHeaders(response,content,singleLength                     );
+                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+                if (!response.containsHeader(HttpHeader.DATE.asString()))
+                    response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
+                response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
+                        singleSatisfiableRange.toHeaderRangeString(content_length));
+                resource.writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
+                return;
+            }
+
+            //  multiple non-overlapping valid ranges cause a multipart
+            //  216 response which does not require an overall
+            //  content-length header
+            //
+            writeHeaders(response,content,-1);
+            String mimetype=(content==null?null:content.getContentType());
+            if (mimetype==null)
+                LOG.warn("Unknown mimetype for "+request.getRequestURI());
+            MultiPartOutputStream multi = new MultiPartOutputStream(out);
+            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+            if (!response.containsHeader(HttpHeader.DATE.asString()))
+                response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
+
+            // If the request has a "Request-Range" header then we need to
+            // send an old style multipart/x-byteranges Content-Type. This
+            // keeps Netscape and acrobat happy. This is what Apache does.
+            String ctp;
+            if (request.getHeader(HttpHeader.REQUEST_RANGE.asString())!=null)
+                ctp = "multipart/x-byteranges; boundary=";
+            else
+                ctp = "multipart/byteranges; boundary=";
+            response.setContentType(ctp+multi.getBoundary());
+
+            InputStream in=resource.getInputStream();
+            long pos=0;
+
+            // calculate the content-length
+            int length=0;
+            String[] header = new String[ranges.size()];
+            for (int i=0;i<ranges.size();i++)
+            {
+                InclusiveByteRange ibr = ranges.get(i);
+                header[i]=ibr.toHeaderRangeString(content_length);
+                length+=
+                    ((i>0)?2:0)+
+                    2+multi.getBoundary().length()+2+
+                    (mimetype==null?0:HttpHeader.CONTENT_TYPE.asString().length()+2+mimetype.length())+2+
+                    HttpHeader.CONTENT_RANGE.asString().length()+2+header[i].length()+2+
+                    2+
+                    (ibr.getLast(content_length)-ibr.getFirst(content_length))+1;
+            }
+            length+=2+2+multi.getBoundary().length()+2+2;
+            response.setContentLength(length);
+
+            for (int i=0;i<ranges.size();i++)
+            {
+                InclusiveByteRange ibr =  ranges.get(i);
+                multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
+
+                long start=ibr.getFirst(content_length);
+                long size=ibr.getSize(content_length);
+                if (in!=null)
+                {
+                    // Handle non cached resource
+                    if (start<pos)
+                    {
+                        in.close();
+                        in=resource.getInputStream();
+                        pos=0;
+                    }
+                    if (pos<start)
+                    {
+                        in.skip(start-pos);
+                        pos=start;
+                    }
+                    
+                    IO.copy(in,multi,size);
+                    pos+=size;
+                }
+                else
+                    // Handle cached resource
+                    (resource).writeTo(multi,start,size);
+            }
+            if (in!=null)
+                in.close();
+            multi.close();
+        }
+        return;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
+    {        
+        if (content.getContentType()!=null && response.getContentType()==null)
+            response.setContentType(content.getContentType().toString());
+
+        if (response instanceof Response)
+        {
+            Response r=(Response)response;
+            HttpFields fields = r.getHttpFields();
+
+            if (content.getLastModified()!=null)
+                fields.put(HttpHeader.LAST_MODIFIED,content.getLastModified());
+            else if (content.getResource()!=null)
+            {
+                long lml=content.getResource().lastModified();
+                if (lml!=-1)
+                    fields.putDateField(HttpHeader.LAST_MODIFIED,lml);
+            }
+
+            if (count != -1)
+                r.setLongContentLength(count);
+
+            writeOptionHeaders(fields);
+            
+            if (_etags)
+                fields.put(HttpHeader.ETAG,content.getETag());
+        }
+        else
+        {
+            long lml=content.getResource().lastModified();
+            if (lml>=0)
+                response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),lml);
+
+            if (count != -1)
+            {
+                if (count<Integer.MAX_VALUE)
+                    response.setContentLength((int)count);
+                else
+                    response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(count));
+            }
+
+            writeOptionHeaders(response);
+
+            if (_etags)
+                response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeOptionHeaders(HttpFields fields)
+    {
+        if (_acceptRanges)
+            fields.put(ACCEPT_RANGES);
+
+        if (_cacheControl!=null)
+            fields.put(_cacheControl);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeOptionHeaders(HttpServletResponse response)
+    {
+        if (_acceptRanges)
+            response.setHeader(HttpHeader.ACCEPT_RANGES.asString(),"bytes");
+
+        if (_cacheControl!=null)
+            response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl.getValue());
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.Servlet#destroy()
+     */
+    @Override
+    public void destroy()
+    {
+        if (_cache!=null)
+            _cache.flushCache();
+        super.destroy();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java b/lib/jetty/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java
new file mode 100644 (file)
index 0000000..b8efeb1
--- /dev/null
@@ -0,0 +1,229 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+
+/* ------------------------------------------------------------ */
+/** Error Page Error Handler
+ *
+ * An ErrorHandler that maps exceptions and status codes to URIs for dispatch using
+ * the internal ERROR style of dispatch.
+ *
+ */
+public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.ErrorPageMapper
+{
+    public final static String GLOBAL_ERROR_PAGE = "org.eclipse.jetty.server.error_page.global";
+
+    protected ServletContext _servletContext;
+    private final Map<String,String> _errorPages= new HashMap<String,String>(); // code or exception to URL
+    private final List<ErrorCodeRange> _errorPageList=new ArrayList<ErrorCodeRange>(); // list of ErrorCode by range
+
+    /* ------------------------------------------------------------ */
+    public ErrorPageErrorHandler()
+    {}
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getErrorPage(HttpServletRequest request)
+    {
+        String error_page= null;
+
+        Throwable th= (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
+
+        // Walk the cause hierarchy
+        while (error_page == null && th != null )
+        {
+            Class<?> exClass=th.getClass();
+            error_page= (String)_errorPages.get(exClass.getName());
+
+            // walk the inheritance hierarchy
+            while (error_page == null)
+            {
+                exClass= exClass.getSuperclass();
+                if (exClass==null)
+                    break;
+                error_page= (String)_errorPages.get(exClass.getName());
+            }
+
+            th=(th instanceof ServletException)?((ServletException)th).getRootCause():null;
+        }
+
+        if (error_page == null)
+        {
+            // look for an exact code match
+            Integer code=(Integer)request.getAttribute(Dispatcher.ERROR_STATUS_CODE);
+            if (code!=null)
+            {
+                error_page= (String)_errorPages.get(Integer.toString(code));
+
+                // if still not found
+                if ((error_page == null) && (_errorPageList != null))
+                {
+                    // look for an error code range match.
+                    for (int i = 0; i < _errorPageList.size(); i++)
+                    {
+                        ErrorCodeRange errCode = (ErrorCodeRange) _errorPageList.get(i);
+                        if (errCode.isInRange(code))
+                        {
+                            error_page = errCode.getUri();
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        //try servlet 3.x global error page
+        if (error_page == null)
+            error_page = _errorPages.get(GLOBAL_ERROR_PAGE);
+        
+        return error_page;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the errorPages.
+     */
+    public Map<String,String> getErrorPages()
+    {
+        return _errorPages;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param errorPages The errorPages to set. A map of Exception class name  or error code as a string to URI string
+     */
+    public void setErrorPages(Map<String,String> errorPages)
+    {
+        _errorPages.clear();
+        if (errorPages!=null)
+            _errorPages.putAll(errorPages);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add Error Page mapping for an exception class
+     * This method is called as a result of an exception-type element in a web.xml file
+     * or may be called directly
+     * @param exception The exception
+     * @param uri The URI of the error page.
+     */
+    public void addErrorPage(Class<? extends Throwable> exception,String uri)
+    {
+        _errorPages.put(exception.getName(),uri);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add Error Page mapping for an exception class
+     * This method is called as a result of an exception-type element in a web.xml file
+     * or may be called directly
+     * @param exceptionClassName The exception
+     * @param uri The URI of the error page.
+     */
+    public void addErrorPage(String exceptionClassName,String uri)
+    {
+        _errorPages.put(exceptionClassName,uri);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add Error Page mapping for a status code.
+     * This method is called as a result of an error-code element in a web.xml file
+     * or may be called directly
+     * @param code The HTTP status code to match
+     * @param uri The URI of the error page.
+     */
+    public void addErrorPage(int code,String uri)
+    {
+        _errorPages.put(Integer.toString(code),uri);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add Error Page mapping for a status code range.
+     * This method is not available from web.xml and must be called
+     * directly.
+     * @param from The lowest matching status code
+     * @param to The highest matching status code
+     * @param uri The URI of the error page.
+     */
+    public void addErrorPage(int from, int to, String uri)
+    {
+        _errorPageList.add(new ErrorCodeRange(from, to, uri));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        _servletContext=ContextHandler.getCurrentContext();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class ErrorCodeRange
+    {
+        private int _from;
+        private int _to;
+        private String _uri;
+
+        ErrorCodeRange(int from, int to, String uri)
+            throws IllegalArgumentException
+        {
+            if (from > to)
+                throw new IllegalArgumentException("from>to");
+
+            _from = from;
+            _to = to;
+            _uri = uri;
+        }
+
+        boolean isInRange(int value)
+        {
+            if ((value >= _from) && (value <= _to))
+            {
+                return true;
+            }
+
+            return false;
+        }
+
+        String getUri()
+        {
+            return _uri;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "from: " + _from + ",to: " + _to + ",uri: " + _uri;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/FilterHolder.java b/lib/jetty/org/eclipse/jetty/servlet/FilterHolder.java
new file mode 100644 (file)
index 0000000..d9635fa
--- /dev/null
@@ -0,0 +1,289 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+import javax.servlet.FilterRegistration;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* --------------------------------------------------------------------- */
+/**
+ *
+ */
+public class FilterHolder extends Holder<Filter>
+{
+    private static final Logger LOG = Log.getLogger(FilterHolder.class);
+
+    /* ------------------------------------------------------------ */
+    private transient Filter _filter;
+    private transient Config _config;
+    private transient FilterRegistration.Dynamic _registration;
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor
+     */
+    public FilterHolder()
+    {
+        this(Source.EMBEDDED);
+    }
+
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor
+     */
+    public FilterHolder(Holder.Source source)
+    {
+        super(source);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor
+     */
+    public FilterHolder(Class<? extends Filter> filter)
+    {
+        this(Source.EMBEDDED);
+        setHeldClass(filter);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor for existing filter.
+     */
+    public FilterHolder(Filter filter)
+    {
+        this(Source.EMBEDDED);
+        setFilter(filter);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStart()
+        throws Exception
+    {
+        super.doStart();
+
+        if (!javax.servlet.Filter.class
+            .isAssignableFrom(_class))
+        {
+            String msg = _class+" is not a javax.servlet.Filter";
+            super.stop();
+            throw new IllegalStateException(msg);
+        }
+    }
+    
+    
+    
+
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void initialize() throws Exception
+    {
+        super.initialize();
+        
+        if (_filter==null)
+        {
+            try
+            {
+                ServletContext context=_servletHandler.getServletContext();
+                _filter=(context instanceof ServletContextHandler.Context)
+                    ?((ServletContextHandler.Context)context).createFilter(getHeldClass())
+                    :getHeldClass().newInstance();
+            }
+            catch (ServletException se)
+            {
+                Throwable cause = se.getRootCause();
+                if (cause instanceof InstantiationException)
+                    throw (InstantiationException)cause;
+                if (cause instanceof IllegalAccessException)
+                    throw (IllegalAccessException)cause;
+                throw se;
+            }
+        }
+
+        _config=new Config();
+        LOG.debug("Filter.init {}",_filter);
+        _filter.init(_config);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStop()
+        throws Exception
+    {
+        if (_filter!=null)
+        {
+            try
+            {
+                destroyInstance(_filter);
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+        }
+        if (!_extInstance)
+            _filter=null;
+
+        _config=null;
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroyInstance (Object o)
+        throws Exception
+    {
+        if (o==null)
+            return;
+        Filter f = (Filter)o;
+        f.destroy();
+        getServletHandler().destroyFilter(f);
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void setFilter(Filter filter)
+    {
+        _filter=filter;
+        _extInstance=true;
+        setHeldClass(filter.getClass());
+        if (getName()==null)
+            setName(filter.getClass().getName());
+    }
+
+    /* ------------------------------------------------------------ */
+    public Filter getFilter()
+    {
+        return _filter;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return getName();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        super.dump(out, indent);
+        if(_filter instanceof Dumpable) {
+            ((Dumpable) _filter).dump(out, indent);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public FilterRegistration.Dynamic getRegistration()
+    {
+        if (_registration == null)
+            _registration = new Registration();
+        return _registration;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected class Registration extends HolderRegistration implements FilterRegistration.Dynamic
+    {
+        public void addMappingForServletNames(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... servletNames)
+        {
+            illegalStateIfContextStarted();
+            FilterMapping mapping = new FilterMapping();
+            mapping.setFilterHolder(FilterHolder.this);
+            mapping.setServletNames(servletNames);
+            mapping.setDispatcherTypes(dispatcherTypes);
+            if (isMatchAfter)
+                _servletHandler.addFilterMapping(mapping);
+            else
+                _servletHandler.prependFilterMapping(mapping);
+        }
+
+        public void addMappingForUrlPatterns(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns)
+        {
+            illegalStateIfContextStarted();
+            FilterMapping mapping = new FilterMapping();
+            mapping.setFilterHolder(FilterHolder.this);
+            mapping.setPathSpecs(urlPatterns);
+            mapping.setDispatcherTypes(dispatcherTypes);
+            if (isMatchAfter)
+                _servletHandler.addFilterMapping(mapping);
+            else
+                _servletHandler.prependFilterMapping(mapping);
+        }
+
+        public Collection<String> getServletNameMappings()
+        {
+            FilterMapping[] mappings =_servletHandler.getFilterMappings();
+            List<String> names=new ArrayList<String>();
+            for (FilterMapping mapping : mappings)
+            {
+                if (mapping.getFilterHolder()!=FilterHolder.this)
+                    continue;
+                String[] servlets=mapping.getServletNames();
+                if (servlets!=null && servlets.length>0)
+                    names.addAll(Arrays.asList(servlets));
+            }
+            return names;
+        }
+
+        public Collection<String> getUrlPatternMappings()
+        {
+            FilterMapping[] mappings =_servletHandler.getFilterMappings();
+            List<String> patterns=new ArrayList<String>();
+            for (FilterMapping mapping : mappings)
+            {
+                if (mapping.getFilterHolder()!=FilterHolder.this)
+                    continue;
+                String[] specs=mapping.getPathSpecs();
+                patterns.addAll(TypeUtil.asList(specs));
+            }
+            return patterns;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    class Config extends HolderConfig implements FilterConfig
+    {
+        /* ------------------------------------------------------------ */
+        public String getFilterName()
+        {
+            return _name;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/FilterMapping.java b/lib/jetty/org/eclipse/jetty/servlet/FilterMapping.java
new file mode 100644 (file)
index 0000000..1d5a9a4
--- /dev/null
@@ -0,0 +1,294 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.EnumSet;
+
+import javax.servlet.DispatcherType;
+
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+
+@ManagedObject("Filter Mappings")
+public class FilterMapping implements Dumpable
+{
+    /** Dispatch types */
+    public static final int DEFAULT=0;
+    public static final int REQUEST=1;
+    public static final int FORWARD=2;
+    public static final int INCLUDE=4;
+    public static final int ERROR=8;
+    public static final int ASYNC=16;
+    public static final int ALL=31;
+
+
+    /* ------------------------------------------------------------ */
+    /** Dispatch type from name
+     */
+    public static DispatcherType dispatch(String type)
+    {
+        if ("request".equalsIgnoreCase(type))
+            return DispatcherType.REQUEST;
+        if ("forward".equalsIgnoreCase(type))
+            return DispatcherType.FORWARD;
+        if ("include".equalsIgnoreCase(type))
+            return DispatcherType.INCLUDE;
+        if ("error".equalsIgnoreCase(type))
+            return DispatcherType.ERROR;
+        if ("async".equalsIgnoreCase(type))
+            return DispatcherType.ASYNC;
+        throw new IllegalArgumentException(type);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Dispatch type from name
+     */
+    public static int dispatch(DispatcherType type)
+    {
+       switch(type)
+       {
+         case REQUEST:
+                 return REQUEST;
+         case ASYNC:
+                 return ASYNC;
+         case FORWARD:
+                 return FORWARD;
+         case INCLUDE:
+                 return INCLUDE;
+         case ERROR:
+                 return ERROR;
+       }
+        throw new IllegalArgumentException(type.toString());
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+
+
+    private int _dispatches=DEFAULT;
+    private String _filterName;
+    private transient FilterHolder _holder;
+    private String[] _pathSpecs;
+    private String[] _servletNames;
+
+    /* ------------------------------------------------------------ */
+    public FilterMapping()
+    {}
+
+    /* ------------------------------------------------------------ */
+    /** Check if this filter applies to a path.
+     * @param path The path to check or null to just check type
+     * @param type The type of request: __REQUEST,__FORWARD,__INCLUDE, __ASYNC or __ERROR.
+     * @return True if this filter applies
+     */
+    boolean appliesTo(String path, int type)
+    {
+        if (appliesTo(type))
+        {
+            for (int i=0;i<_pathSpecs.length;i++)
+                if (_pathSpecs[i]!=null &&  PathMap.match(_pathSpecs[i], path,true))
+                    return true;
+        }
+
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Check if this filter applies to a particular dispatch type.
+     * @param type The type of request:
+     *      {@link Handler#REQUEST}, {@link Handler#FORWARD}, {@link Handler#INCLUDE} or {@link Handler#ERROR}.
+     * @return <code>true</code> if this filter applies
+     */
+    boolean appliesTo(int type)
+    {
+       if (_dispatches==0)
+               return type==REQUEST || type==ASYNC && _holder.isAsyncSupported();
+        return (_dispatches&type)!=0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean appliesTo(DispatcherType t)
+    {
+        return appliesTo(dispatch(t));
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean isDefaultDispatches()
+    {
+        return _dispatches==0;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the filterName.
+     */
+    @ManagedAttribute(value="filter name", readonly=true)
+    public String getFilterName()
+    {
+        return _filterName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the holder.
+     */
+    FilterHolder getFilterHolder()
+    {
+        return _holder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the pathSpec.
+     */
+    @ManagedAttribute(value="url patterns", readonly=true)
+    public String[] getPathSpecs()
+    {
+        return _pathSpecs;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setDispatcherTypes(EnumSet<DispatcherType> dispatcherTypes)
+    {
+        _dispatches=DEFAULT;
+        if (dispatcherTypes!=null)
+        {
+            if (dispatcherTypes.contains(DispatcherType.ERROR))
+                _dispatches|=ERROR;
+            if (dispatcherTypes.contains(DispatcherType.FORWARD))
+                _dispatches|=FORWARD;
+            if (dispatcherTypes.contains(DispatcherType.INCLUDE))
+                _dispatches|=INCLUDE;
+            if (dispatcherTypes.contains(DispatcherType.REQUEST))
+                _dispatches|=REQUEST;
+            if (dispatcherTypes.contains(DispatcherType.ASYNC))
+                _dispatches|=ASYNC;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param dispatches The dispatches to set.
+     * @see #DEFAULT
+     * @see #REQUEST
+     * @see #ERROR
+     * @see #FORWARD
+     * @see #INCLUDE
+     */
+    public void setDispatches(int dispatches)
+    {
+        _dispatches = dispatches;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filterName The filterName to set.
+     */
+    public void setFilterName(String filterName)
+    {
+        _filterName = filterName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param holder The holder to set.
+     */
+    void setFilterHolder(FilterHolder holder)
+    {
+        _holder = holder;
+        setFilterName(holder.getName());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpecs The Path specifications to which this filter should be mapped.
+     */
+    public void setPathSpecs(String[] pathSpecs)
+    {
+        _pathSpecs = pathSpecs;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpec The pathSpec to set.
+     */
+    public void setPathSpec(String pathSpec)
+    {
+        _pathSpecs = new String[]{pathSpec};
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletName.
+     */
+    @ManagedAttribute(value="servlet names", readonly=true)
+    public String[] getServletNames()
+    {
+        return _servletNames;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletNames Maps the {@link #setFilterName(String) named filter} to multiple servlets
+     * @see #setServletName
+     */
+    public void setServletNames(String[] servletNames)
+    {
+        _servletNames = servletNames;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletName Maps the {@link #setFilterName(String) named filter} to a single servlet
+     * @see #setServletNames
+     */
+    public void setServletName(String servletName)
+    {
+        _servletNames = new String[]{servletName};
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toString()
+    {
+        return
+        TypeUtil.asList(_pathSpecs)+"/"+
+        TypeUtil.asList(_servletNames)+"=="+
+        _dispatches+"=>"+
+        _filterName;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        out.append(String.valueOf(this)).append("\n");
+    }
+
+    /* ------------------------------------------------------------ */
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/Holder.java b/lib/jetty/org/eclipse/jetty/servlet/Holder.java
new file mode 100644 (file)
index 0000000..2f690b1
--- /dev/null
@@ -0,0 +1,318 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.Registration;
+import javax.servlet.ServletContext;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* --------------------------------------------------------------------- */
+/**
+ * Holder
+ * 
+ * Specialization of AbstractHolder for servlet-related classes that 
+ * have init-params etc
+ * 
+ */
+@ManagedObject("Holder - a container for servlets and the like")
+public class Holder<T> extends BaseHolder<T>
+{
+    private static final Logger LOG = Log.getLogger(Holder.class);
+
+    protected final Map<String,String> _initParams=new HashMap<String,String>(3);
+    protected String _displayName;
+    protected boolean _asyncSupported;
+    protected String _name;
+
+
+    /* ---------------------------------------------------------------- */
+    protected Holder(Source source)
+    {
+        super(source);
+        switch(_source)
+        {
+            case JAVAX_API:
+            case DESCRIPTOR:
+            case ANNOTATION:
+                _asyncSupported=false;
+                break;
+            default:
+                _asyncSupported=true;
+        }
+    }
+
+  
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute(value="Display Name", readonly=true)
+    public String getDisplayName()
+    {
+        return _displayName;
+    }
+
+    /* ---------------------------------------------------------------- */
+    public String getInitParameter(String param)
+    {
+        if (_initParams==null)
+            return null;
+        return (String)_initParams.get(param);
+    }
+
+    /* ------------------------------------------------------------ */
+    public Enumeration getInitParameterNames()
+    {
+        if (_initParams==null)
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        return Collections.enumeration(_initParams.keySet());
+    }
+
+    /* ---------------------------------------------------------------- */
+    @ManagedAttribute(value="Initial Parameters", readonly=true)
+    public Map<String,String> getInitParameters()
+    {
+        return _initParams;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute(value="Name", readonly=true)
+    public String getName()
+    {
+        return _name;
+    }
+
+  
+    /* ------------------------------------------------------------ */
+    public void destroyInstance(Object instance)
+    throws Exception
+    {
+    }
+    /* ------------------------------------------------------------ */
+    /**
+     * @param className The className to set.
+     */
+    public void setClassName(String className)
+    {
+        super.setClassName(className);
+        if (_name==null)
+            _name=className+"-"+Integer.toHexString(this.hashCode());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param held The class to hold
+     */
+    public void setHeldClass(Class<? extends T> held)
+    {
+        super.setHeldClass(held);
+        if (held!=null)
+        {
+            if (_name==null)
+                _name=held.getName()+"-"+Integer.toHexString(this.hashCode());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setDisplayName(String name)
+    {
+        _displayName=name;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setInitParameter(String param,String value)
+    {
+        _initParams.put(param,value);
+    }
+
+    /* ---------------------------------------------------------------- */
+    public void setInitParameters(Map<String,String> map)
+    {
+        _initParams.clear();
+        _initParams.putAll(map);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * The name is a primary key for the held object.
+     * Ensure that the name is set BEFORE adding a Holder
+     * (eg ServletHolder or FilterHolder) to a ServletHandler.
+     * @param name The name to set.
+     */
+    public void setName(String name)
+    {
+        _name = name;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public void setAsyncSupported(boolean suspendable)
+    {
+        _asyncSupported=suspendable;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isAsyncSupported()
+    {
+        return _asyncSupported;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        super.dump(out,indent);
+        ContainerLifeCycle.dump(out,indent,_initParams.entrySet());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String dump()
+    {
+        return super.dump();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x==%s",_name,hashCode(),_className);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected class HolderConfig
+    {
+
+        /* -------------------------------------------------------- */
+        public ServletContext getServletContext()
+        {
+            return _servletHandler.getServletContext();
+        }
+
+        /* -------------------------------------------------------- */
+        public String getInitParameter(String param)
+        {
+            return Holder.this.getInitParameter(param);
+        }
+
+        /* -------------------------------------------------------- */
+        public Enumeration getInitParameterNames()
+        {
+            return Holder.this.getInitParameterNames();
+        }
+    }
+
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    protected class HolderRegistration implements Registration.Dynamic
+    {
+        public void setAsyncSupported(boolean isAsyncSupported)
+        {
+            illegalStateIfContextStarted();
+            Holder.this.setAsyncSupported(isAsyncSupported);
+        }
+
+        public void setDescription(String description)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug(this+" is "+description);
+        }
+
+        public String getClassName()
+        {
+            return Holder.this.getClassName();
+        }
+
+        public String getInitParameter(String name)
+        {
+            return Holder.this.getInitParameter(name);
+        }
+
+        public Map<String, String> getInitParameters()
+        {
+            return Holder.this.getInitParameters();
+        }
+
+        public String getName()
+        {
+            return Holder.this.getName();
+        }
+
+        public boolean setInitParameter(String name, String value)
+        {
+            illegalStateIfContextStarted();
+            if (name == null) {
+                throw new IllegalArgumentException("init parameter name required");
+            }
+            if (value == null) {
+                throw new IllegalArgumentException("non-null value required for init parameter " + name);
+            }
+            if (Holder.this.getInitParameter(name)!=null)
+                return false;
+            Holder.this.setInitParameter(name,value);
+            return true;
+        }
+
+        public Set<String> setInitParameters(Map<String, String> initParameters)
+        {
+            illegalStateIfContextStarted();
+            Set<String> clash=null;
+            for (Map.Entry<String, String> entry : initParameters.entrySet())
+            {
+                if (entry.getKey() == null) {
+                    throw new IllegalArgumentException("init parameter name required");
+                }
+                if (entry.getValue() == null) {
+                    throw new IllegalArgumentException("non-null value required for init parameter " + entry.getKey());
+                }
+                if (Holder.this.getInitParameter(entry.getKey())!=null)
+                {
+                    if (clash==null)
+                        clash=new HashSet<String>();
+                    clash.add(entry.getKey());
+                }
+            }
+            if (clash!=null)
+                return clash;
+            Holder.this.getInitParameters().putAll(initParameters);
+            return Collections.emptySet();
+        }
+    }
+}
+
+
+
+
+
diff --git a/lib/jetty/org/eclipse/jetty/servlet/Invoker.java b/lib/jetty/org/eclipse/jetty/servlet/Invoker.java
new file mode 100644 (file)
index 0000000..4613144
--- /dev/null
@@ -0,0 +1,314 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**  Dynamic Servlet Invoker.
+ * This servlet invokes anonymous servlets that have not been defined
+ * in the web.xml or by other means. The first element of the pathInfo
+ * of a request passed to the envoker is treated as a servlet name for
+ * an existing servlet, or as a class name of a new servlet.
+ * This servlet is normally mapped to /servlet/*
+ * This servlet support the following initParams:
+ * <PRE>
+ *  nonContextServlets       If false, the invoker can only load
+ *                           servlets from the contexts classloader.
+ *                           This is false by default and setting this
+ *                           to true may have security implications.
+ *
+ *  verbose                  If true, log dynamic loads
+ *
+ *  *                        All other parameters are copied to the
+ *                           each dynamic servlet as init parameters
+ * </PRE>
+ * @version $Id: Invoker.java 4780 2009-03-17 15:36:08Z jesse $
+ *
+ */
+public class Invoker extends HttpServlet
+{
+    private static final Logger LOG = Log.getLogger(Invoker.class);
+
+
+    private ContextHandler _contextHandler;
+    private ServletHandler _servletHandler;
+    private Map.Entry _invokerEntry;
+    private Map _parameters;
+    private boolean _nonContextServlets;
+    private boolean _verbose;
+
+    /* ------------------------------------------------------------ */
+    public void init()
+    {
+        ServletContext config=getServletContext();
+        _contextHandler=((ContextHandler.Context)config).getContextHandler();
+
+        Handler handler=_contextHandler.getHandler();
+        while (handler!=null && !(handler instanceof ServletHandler) && (handler instanceof HandlerWrapper))
+            handler=((HandlerWrapper)handler).getHandler();
+        _servletHandler = (ServletHandler)handler;
+        Enumeration e = getInitParameterNames();
+        while(e.hasMoreElements())
+        {
+            String param=(String)e.nextElement();
+            String value=getInitParameter(param);
+            String lvalue=value.toLowerCase(Locale.ENGLISH);
+            if ("nonContextServlets".equals(param))
+            {
+                _nonContextServlets=value.length()>0 && lvalue.startsWith("t");
+            }
+            if ("verbose".equals(param))
+            {
+                _verbose=value.length()>0 && lvalue.startsWith("t");
+            }
+            else
+            {
+                if (_parameters==null)
+                    _parameters=new HashMap();
+                _parameters.put(param,value);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void service(HttpServletRequest request, HttpServletResponse response)
+       throws ServletException, IOException
+    {
+        // Get the requested path and info
+        boolean included=false;
+        String servlet_path=(String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH);
+        if (servlet_path==null)
+            servlet_path=request.getServletPath();
+        else
+            included=true;
+        String path_info = (String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
+        if (path_info==null)
+            path_info=request.getPathInfo();
+
+        // Get the servlet class
+        String servlet = path_info;
+        if (servlet==null || servlet.length()<=1 )
+        {
+            response.sendError(404);
+            return;
+        }
+
+
+        int i0=servlet.charAt(0)=='/'?1:0;
+        int i1=servlet.indexOf('/',i0);
+        servlet=i1<0?servlet.substring(i0):servlet.substring(i0,i1);
+
+        // look for a named holder
+        ServletHolder[] holders = _servletHandler.getServlets();
+        ServletHolder holder = getHolder (holders, servlet);
+
+        if (holder!=null)
+        {
+            // Found a named servlet (from a user's web.xml file) so
+            // now we add a mapping for it
+            if (LOG.isDebugEnabled())
+                LOG.debug("Adding servlet mapping for named servlet:"+servlet+":"+URIUtil.addPaths(servlet_path,servlet)+"/*");
+            ServletMapping mapping = new ServletMapping();
+            mapping.setServletName(servlet);
+            mapping.setPathSpec(URIUtil.addPaths(servlet_path,servlet)+"/*");
+            _servletHandler.setServletMappings((ServletMapping[])ArrayUtil.addToArray(_servletHandler.getServletMappings(), mapping, ServletMapping.class));
+        }
+        else
+        {
+            // look for a class mapping
+            if (servlet.endsWith(".class"))
+                servlet=servlet.substring(0,servlet.length()-6);
+            if (servlet==null || servlet.length()==0)
+            {
+                response.sendError(404);
+                return;
+            }
+
+            synchronized(_servletHandler)
+            {
+                // find the entry for the invoker (me)
+                 _invokerEntry=_servletHandler.getHolderEntry(servlet_path);
+
+                // Check for existing mapping (avoid threaded race).
+                String path=URIUtil.addPaths(servlet_path,servlet);
+                Map.Entry entry = _servletHandler.getHolderEntry(path);
+
+                if (entry!=null && !entry.equals(_invokerEntry))
+                {
+                    // Use the holder
+                    holder=(ServletHolder)entry.getValue();
+                }
+                else
+                {
+                    // Make a holder
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Making new servlet="+servlet+" with path="+path+"/*");
+                    holder=_servletHandler.addServletWithMapping(servlet, path+"/*");
+
+                    if (_parameters!=null)
+                        holder.setInitParameters(_parameters);
+
+                    try {holder.start();}
+                    catch (Exception e)
+                    {
+                        LOG.debug(e);
+                        throw new UnavailableException(e.toString());
+                    }
+
+                    // Check it is from an allowable classloader
+                    if (!_nonContextServlets)
+                    {
+                        Object s=holder.getServlet();
+
+                        if (_contextHandler.getClassLoader()!=
+                            s.getClass().getClassLoader())
+                        {
+                            try
+                            {
+                                holder.stop();
+                            }
+                            catch (Exception e)
+                            {
+                                LOG.ignore(e);
+                            }
+
+                            LOG.warn("Dynamic servlet "+s+
+                                         " not loaded from context "+
+                                         request.getContextPath());
+                            throw new UnavailableException("Not in context");
+                        }
+                    }
+
+                    if (_verbose && LOG.isDebugEnabled())
+                        LOG.debug("Dynamic load '"+servlet+"' at "+path);
+                }
+            }
+        }
+
+        if (holder!=null)
+        {
+            final Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
+            holder.handle(baseRequest,
+                    new InvokedRequest(request,included,servlet,servlet_path,path_info),
+                          response);
+        }
+        else
+        {
+            LOG.info("Can't find holder for servlet: "+servlet);
+            response.sendError(404);
+        }
+
+
+    }
+
+    /* ------------------------------------------------------------ */
+    class InvokedRequest extends HttpServletRequestWrapper
+    {
+        String _servletPath;
+        String _pathInfo;
+        boolean _included;
+
+        /* ------------------------------------------------------------ */
+        InvokedRequest(HttpServletRequest request,
+                boolean included,
+                String name,
+                String servletPath,
+                String pathInfo)
+        {
+            super(request);
+            _included=included;
+            _servletPath=URIUtil.addPaths(servletPath,name);
+            _pathInfo=pathInfo.substring(name.length()+1);
+            if (_pathInfo.length()==0)
+                _pathInfo=null;
+        }
+
+        /* ------------------------------------------------------------ */
+        public String getServletPath()
+        {
+            if (_included)
+                return super.getServletPath();
+            return _servletPath;
+        }
+
+        /* ------------------------------------------------------------ */
+        public String getPathInfo()
+        {
+            if (_included)
+                return super.getPathInfo();
+            return _pathInfo;
+        }
+
+        /* ------------------------------------------------------------ */
+        public Object getAttribute(String name)
+        {
+            if (_included)
+            {
+                if (name.equals(Dispatcher.INCLUDE_REQUEST_URI))
+                    return URIUtil.addPaths(URIUtil.addPaths(getContextPath(),_servletPath),_pathInfo);
+                if (name.equals(Dispatcher.INCLUDE_PATH_INFO))
+                    return _pathInfo;
+                if (name.equals(Dispatcher.INCLUDE_SERVLET_PATH))
+                    return _servletPath;
+            }
+            return super.getAttribute(name);
+        }
+    }
+
+
+    private ServletHolder getHolder(ServletHolder[] holders, String servlet)
+    {
+        if (holders == null)
+            return null;
+
+        ServletHolder holder = null;
+        for (int i=0; holder==null && i<holders.length; i++)
+        {
+            if (holders[i].getName().equals(servlet))
+            {
+                holder = holders[i];
+            }
+        }
+        return holder;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java b/lib/jetty/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java
new file mode 100644 (file)
index 0000000..d0fcbfe
--- /dev/null
@@ -0,0 +1,144 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Servlet handling JSP Property Group mappings
+ * <p>
+ * This servlet is mapped to by any URL pattern for a JSP property group. 
+ * Resources handled by this servlet that are not directories will be passed
+ * directly to the JSP servlet.    Resources that are directories will be 
+ * passed directly to the default servlet.
+ */
+public class JspPropertyGroupServlet extends GenericServlet
+{
+    private static final long serialVersionUID = 3681783214726776945L;
+    
+    public final static String NAME = "__org.eclipse.jetty.servlet.JspPropertyGroupServlet__";
+    private final ServletHandler _servletHandler;
+    private final ContextHandler _contextHandler;
+    private ServletHolder _dftServlet;
+    private ServletHolder _jspServlet;
+    private boolean _starJspMapped;
+    
+    public JspPropertyGroupServlet(ContextHandler context, ServletHandler servletHandler)
+    {
+        _contextHandler=context;
+        _servletHandler=servletHandler;        
+    }
+    
+    @Override
+    public void init() throws ServletException
+    {            
+        String jsp_name = "jsp";
+        ServletMapping servlet_mapping =_servletHandler.getServletMapping("*.jsp");
+        if (servlet_mapping!=null)
+        {
+            _starJspMapped=true;
+           
+            //now find the jsp servlet, ignoring the mapping that is for ourself
+            ServletMapping[] mappings = _servletHandler.getServletMappings();
+            for (ServletMapping m:mappings)
+            {
+                String[] paths = m.getPathSpecs();
+                if (paths!=null)
+                {
+                    for (String path:paths)
+                    {
+                        if ("*.jsp".equals(path) && !NAME.equals(m.getServletName()))
+                            servlet_mapping = m;
+                    }
+                }
+            }
+            
+            jsp_name=servlet_mapping.getServletName();
+        }
+        _jspServlet=_servletHandler.getServlet(jsp_name);
+        
+        String dft_name="default";
+        ServletMapping default_mapping=_servletHandler.getServletMapping("/");
+        if (default_mapping!=null)
+            dft_name=default_mapping.getServletName();
+        _dftServlet=_servletHandler.getServlet(dft_name);
+    }
+
+    @Override
+    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
+    {           
+        HttpServletRequest request = null;
+        if (req instanceof HttpServletRequest)
+            request = (HttpServletRequest)req;
+        else
+            throw new ServletException("Request not HttpServletRequest");
+
+        String servletPath=null;
+        String pathInfo=null;
+        if (request.getAttribute(Dispatcher.INCLUDE_REQUEST_URI)!=null)
+        {
+            servletPath=(String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH);
+            pathInfo=(String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
+            if (servletPath==null)
+            {
+                servletPath=request.getServletPath();
+                pathInfo=request.getPathInfo();
+            }
+        }
+        else
+        {
+            servletPath = request.getServletPath();
+            pathInfo = request.getPathInfo();
+        }
+        
+        String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
+        
+        if (pathInContext.endsWith("/"))
+        {
+            _dftServlet.getServlet().service(req,res);
+        }
+        else if (_starJspMapped && pathInContext.toLowerCase().endsWith(".jsp"))
+        {
+            _jspServlet.getServlet().service(req,res);
+        }
+        else
+        {
+         
+            Resource resource = _contextHandler.getResource(pathInContext);
+            if (resource!=null && resource.isDirectory())
+                _dftServlet.getServlet().service(req,res);
+            else
+                _jspServlet.getServlet().service(req,res);
+        }
+        
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/ListenerHolder.java b/lib/jetty/org/eclipse/jetty/servlet/ListenerHolder.java
new file mode 100644 (file)
index 0000000..346d478
--- /dev/null
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.servlet;
+
+import java.util.EventListener;
+
+/**
+ * ListenerHolder
+ *
+ * Specialization of AbstractHolder for servlet listeners. This
+ * allows us to record where the listener originated - web.xml,
+ * annotation, api etc.
+ */
+public class ListenerHolder extends BaseHolder<EventListener>
+{
+    private EventListener _listener;
+    
+
+    public ListenerHolder(Source source)
+    {
+        super(source);
+    }
+   
+    
+    public void setListener(EventListener listener)
+    {
+        _listener = listener;
+        setClassName(listener.getClass().getName());
+        setHeldClass(listener.getClass());
+        _extInstance=true;
+    }
+
+    public EventListener getListener()
+    {
+        return _listener;
+    }
+
+
+    @Override
+    public void doStart() throws Exception
+    {
+        //Listeners always have an instance eagerly created, it cannot be deferred to the doStart method
+        if (_listener == null)
+            throw new IllegalStateException("No listener instance");
+        
+        super.doStart();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/NoJspServlet.java b/lib/jetty/org/eclipse/jetty/servlet/NoJspServlet.java
new file mode 100644 (file)
index 0000000..afd066b
--- /dev/null
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class NoJspServlet extends HttpServlet
+{
+    private boolean _warned;
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
+    {
+        if (!_warned)
+            getServletContext().log("No JSP support.  Check that JSP jars are in lib/jsp and that the JSP option has been specified to start.jar");
+        _warned=true;
+
+        response.sendError(500,"JSP support not configured");
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/ServletContextHandler.java b/lib/jetty/org/eclipse/jetty/servlet/ServletContextHandler.java
new file mode 100644 (file)
index 0000000..d01ddd2
--- /dev/null
@@ -0,0 +1,1367 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
+import javax.servlet.descriptor.JspPropertyGroupDescriptor;
+import javax.servlet.descriptor.TaglibDescriptor;
+
+import org.eclipse.jetty.security.ConstraintAware;
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.BaseHolder.Source;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+
+/* ------------------------------------------------------------ */
+/** Servlet Context.
+ * This extension to the ContextHandler allows for
+ * simple construction of a context with ServletHandler and optionally
+ * session and security handlers, et.<pre>
+ *   new ServletContext("/context",Context.SESSIONS|Context.NO_SECURITY);
+ * </pre>
+ * <p/>
+ * This class should have been called ServletContext, but this would have
+ * cause confusion with {@link ServletContext}.
+ */
+@ManagedObject("Servlet Context Handler")
+public class ServletContextHandler extends ContextHandler
+{
+    public final static int SESSIONS=1;
+    public final static int SECURITY=2;
+    public final static int NO_SESSIONS=0;
+    public final static int NO_SECURITY=0;
+    
+    public interface ServletContainerInitializerCaller extends LifeCycle {};
+
+    protected final List<Decorator> _decorators= new ArrayList<>();
+    protected Class<? extends SecurityHandler> _defaultSecurityHandlerClass=org.eclipse.jetty.security.ConstraintSecurityHandler.class;
+    protected SessionHandler _sessionHandler;
+    protected SecurityHandler _securityHandler;
+    protected ServletHandler _servletHandler;
+    protected int _options;
+    protected JspConfigDescriptor _jspConfig;
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler()
+    {
+        this(null,null,null,null,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(int options)
+    {
+        this(null,null,options);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(HandlerContainer parent, String contextPath)
+    {
+        this(parent,contextPath,null,null,null,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(HandlerContainer parent, String contextPath, int options)
+    {
+        this(parent,contextPath,null,null,null,null,options);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(HandlerContainer parent, String contextPath, boolean sessions, boolean security)
+    {
+        this(parent,contextPath,(sessions?SESSIONS:0)|(security?SECURITY:0));
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(HandlerContainer parent, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
+    {
+        this(parent,null,sessionHandler,securityHandler,servletHandler,errorHandler);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(HandlerContainer parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
+    {
+        this(parent,contextPath,sessionHandler,securityHandler,servletHandler,errorHandler,0);
+    }
+    
+    public ServletContextHandler(HandlerContainer parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler,int options)
+    {
+        super((ContextHandler.Context)null);
+        _options=options;
+        _scontext = new Context();
+        _sessionHandler = sessionHandler;
+        _securityHandler = securityHandler;
+        _servletHandler = servletHandler;
+
+        if (contextPath!=null)
+            setContextPath(contextPath);
+        
+        if (parent instanceof HandlerWrapper)
+            ((HandlerWrapper)parent).setHandler(this);
+        else if (parent instanceof HandlerCollection)
+            ((HandlerCollection)parent).addHandler(this);
+        
+        
+        // Link the handlers
+        relinkHandlers();
+        
+        if (errorHandler!=null)
+            setErrorHandler(errorHandler);
+
+    }
+    
+    /* ------------------------------------------------------------ */
+    private void relinkHandlers()
+    {
+        HandlerWrapper handler=this;
+        
+        // Skip any injected handlers
+        while (handler.getHandler() instanceof HandlerWrapper)
+        {
+            HandlerWrapper wrapper = (HandlerWrapper)handler.getHandler();
+            if (wrapper instanceof SessionHandler ||
+                wrapper instanceof SecurityHandler ||
+                wrapper instanceof ServletHandler)
+                break;
+            handler=wrapper;
+        }
+        
+        if (getSessionHandler()!=null)
+        {
+            if (handler==this)
+                super.setHandler(_sessionHandler);
+            else
+                handler.setHandler(_sessionHandler);
+            handler=_sessionHandler;
+        }
+
+        if (getSecurityHandler()!=null)
+        {
+            if (handler==this)
+                super.setHandler(_securityHandler);
+            else
+                handler.setHandler(_securityHandler);
+            handler=_securityHandler;
+        }
+
+        if (getServletHandler()!=null)
+        {
+            if (handler==this)
+                super.setHandler(_servletHandler);
+            else
+                handler.setHandler(_servletHandler);
+            handler=_servletHandler;
+        } 
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.handler.ContextHandler#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        if (_decorators != null)
+            _decorators.clear();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the defaultSecurityHandlerClass.
+     * @return the defaultSecurityHandlerClass
+     */
+    public Class<? extends SecurityHandler> getDefaultSecurityHandlerClass()
+    {
+        return _defaultSecurityHandlerClass;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the defaultSecurityHandlerClass.
+     * @param defaultSecurityHandlerClass the defaultSecurityHandlerClass to set
+     */
+    public void setDefaultSecurityHandlerClass(Class<? extends SecurityHandler> defaultSecurityHandlerClass)
+    {
+        _defaultSecurityHandlerClass = defaultSecurityHandlerClass;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected SessionHandler newSessionHandler()
+    {
+        return new SessionHandler();
+    }
+
+    /* ------------------------------------------------------------ */
+    protected SecurityHandler newSecurityHandler()
+    {
+        try
+        {
+            return (SecurityHandler)_defaultSecurityHandlerClass.newInstance();
+        }
+        catch(Exception e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected ServletHandler newServletHandler()
+    {
+        return new ServletHandler();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Finish constructing handlers and link them together.
+     *
+     * @see org.eclipse.jetty.server.handler.ContextHandler#startContext()
+     */
+    @Override
+    protected void startContext() throws Exception
+    {
+        ServletContainerInitializerCaller sciBean = getBean(ServletContainerInitializerCaller.class);
+        if (sciBean!=null)
+            sciBean.start();
+
+        if (_servletHandler != null)
+        {
+            //Call decorators on all holders, and also on any EventListeners before
+            //decorators are called on any other classes (like servlets and filters)
+            for (int i=_decorators.size()-1;i>=0; i--)
+            {
+                Decorator decorator = _decorators.get(i);
+                //Do any decorations on the ListenerHolders AND the listener instances first up
+                if (_servletHandler.getListeners()!=null)
+                {
+                    for (ListenerHolder holder:_servletHandler.getListeners())
+                    {             
+                        decorator.decorate(holder.getListener());
+                    }
+                }
+           }
+       }
+       
+        super.startContext();
+
+        // OK to Initialize servlet handler now that all relevant object trees have been started
+        if (_servletHandler != null)
+            _servletHandler.initialize();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the securityHandler.
+     */
+    @ManagedAttribute(value="context security handler", readonly=true)
+    public SecurityHandler getSecurityHandler()
+    {
+        if (_securityHandler==null && (_options&SECURITY)!=0 && !isStarted())
+            _securityHandler=newSecurityHandler();
+
+        return _securityHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletHandler.
+     */
+    @ManagedAttribute(value="context servlet handler", readonly=true)
+    public ServletHandler getServletHandler()
+    {
+        if (_servletHandler==null && !isStarted())
+            _servletHandler=newServletHandler();
+        return _servletHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionHandler.
+     */
+    @ManagedAttribute(value="context session handler", readonly=true)
+    public SessionHandler getSessionHandler()
+    {
+        if (_sessionHandler==null && (_options&SESSIONS)!=0 && !isStarted())
+            _sessionHandler=newSessionHandler();
+        return _sessionHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a servlet.
+     */
+    public ServletHolder addServlet(String className,String pathSpec)
+    {
+        return getServletHandler().addServletWithMapping(className, pathSpec);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a servlet.
+     */
+    public ServletHolder addServlet(Class<? extends Servlet> servlet,String pathSpec)
+    {
+        return getServletHandler().addServletWithMapping(servlet.getName(), pathSpec);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a servlet.
+     */
+    public void addServlet(ServletHolder servlet,String pathSpec)
+    {
+        getServletHandler().addServletWithMapping(servlet, pathSpec);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a filter
+     */
+    public void addFilter(FilterHolder holder,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        getServletHandler().addFilterWithMapping(holder,pathSpec,dispatches);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** convenience method to add a filter
+     */
+    public FilterHolder addFilter(Class<? extends Filter> filterClass,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        return getServletHandler().addFilterWithMapping(filterClass,pathSpec,dispatches);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** convenience method to add a filter
+     */
+    public FilterHolder addFilter(String filterClass,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        return getServletHandler().addFilterWithMapping(filterClass,pathSpec,dispatches);
+    }
+
+    /**
+     * notification that a ServletRegistration has been created so we can track the annotations
+     * @param holder new holder created through the api.
+     * @return the ServletRegistration.Dynamic
+     */
+    protected ServletRegistration.Dynamic dynamicHolderAdded(ServletHolder holder) {
+        return holder.getRegistration();
+    }
+
+    /**
+     * delegate for ServletContext.declareRole method
+     * @param roleNames role names to add
+     */
+    protected void addRoles(String... roleNames) {
+        //Get a reference to the SecurityHandler, which must be ConstraintAware
+        if (_securityHandler != null && _securityHandler instanceof ConstraintAware)
+        {
+            HashSet<String> union = new HashSet<String>();
+            Set<String> existing = ((ConstraintAware)_securityHandler).getRoles();
+            if (existing != null)
+                union.addAll(existing);
+            union.addAll(Arrays.asList(roleNames));
+            ((ConstraintSecurityHandler)_securityHandler).setRoles(union);
+        }
+    }
+
+    /**
+     * Delegate for ServletRegistration.Dynamic.setServletSecurity method
+     * @param registration ServletRegistration.Dynamic instance that setServletSecurity was called on
+     * @param servletSecurityElement new security info
+     * @return the set of exact URL mappings currently associated with the registration that are also present in the web.xml
+     * security constraints and thus will be unaffected by this call.
+     */
+    public Set<String> setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement)
+    {
+        //Default implementation is to just accept them all. If using a webapp, then this behaviour is overridden in WebAppContext.setServletSecurity       
+        Collection<String> pathSpecs = registration.getMappings();
+        if (pathSpecs != null)
+        {
+            for (String pathSpec:pathSpecs)
+            {
+                List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath(registration.getName(), pathSpec, servletSecurityElement);
+                for (ConstraintMapping m:mappings)
+                    ((ConstraintAware)getSecurityHandler()).addConstraintMapping(m);
+            }
+        }
+        return Collections.emptySet();
+    }
+
+    @Override
+    public void callContextInitialized(ServletContextListener l, ServletContextEvent e)
+    {
+        try
+        {
+            //toggle state of the dynamic API so that the listener cannot use it
+            if(isProgrammaticListener(l))
+                this.getServletContext().setEnabled(false);
+
+            super.callContextInitialized(l, e);
+        }
+        finally
+        {
+            //untoggle the state of the dynamic API
+            this.getServletContext().setEnabled(true);
+        }
+    }
+
+
+    @Override
+    public void callContextDestroyed(ServletContextListener l, ServletContextEvent e)
+    {
+        super.callContextDestroyed(l, e);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionHandler The sessionHandler to set.
+     */
+    public void setSessionHandler(SessionHandler sessionHandler)
+    {
+        if (isStarted())
+            throw new IllegalStateException("STARTED");
+
+        if (_sessionHandler!=null)
+            _sessionHandler.setHandler(null);
+
+        _sessionHandler = sessionHandler;
+        relinkHandlers();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param securityHandler The {@link SecurityHandler} to set on this context.
+     */
+    public void setSecurityHandler(SecurityHandler securityHandler)
+    {
+        if (isStarted())
+            throw new IllegalStateException("STARTED");
+
+        if (_securityHandler!=null)
+            _securityHandler.setHandler(null);
+        _securityHandler = securityHandler;
+        relinkHandlers();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletHandler The servletHandler to set.
+     */
+    public void setServletHandler(ServletHandler servletHandler)
+    {
+        if (isStarted())
+            throw new IllegalStateException("STARTED");
+
+        Handler next=null;
+        if (_servletHandler!=null)
+        {
+            next=_servletHandler.getHandler();
+            _servletHandler.setHandler(null);
+        }
+        _servletHandler = servletHandler;
+        relinkHandlers();
+        _servletHandler.setHandler(next);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setHandler(Handler handler)
+    {
+        if (handler instanceof ServletHandler)
+            setServletHandler((ServletHandler) handler);
+        else if (handler instanceof SessionHandler)
+            setSessionHandler((SessionHandler) handler);
+        else if (handler instanceof SecurityHandler)
+            setSecurityHandler((SecurityHandler)handler);
+        else if (handler == null || handler instanceof HandlerWrapper)
+        {
+            super.setHandler(handler);
+            relinkHandlers();
+        }
+        else
+            throw new IllegalArgumentException();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Insert a HandlerWrapper before the first Session,Security or ServletHandler
+     * but after any other HandlerWrappers.
+     */
+    public void insertHandler(HandlerWrapper handler)
+    {
+        HandlerWrapper h=this;
+        
+        // Skip any injected handlers
+        while (h.getHandler() instanceof HandlerWrapper)
+        {
+            HandlerWrapper wrapper = (HandlerWrapper)h.getHandler();
+            if (wrapper instanceof SessionHandler ||
+                wrapper instanceof SecurityHandler ||
+                wrapper instanceof ServletHandler)
+                break;
+            h=wrapper;
+        }
+        
+        h.setHandler(handler);
+        relinkHandlers();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The decorator list used to resource inject new Filters, Servlets and EventListeners
+     */
+    public List<Decorator> getDecorators()
+    {
+        return Collections.unmodifiableList(_decorators);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param decorators The lis of {@link Decorator}s
+     */
+    public void setDecorators(List<Decorator> decorators)
+    {
+        _decorators.clear();
+        _decorators.addAll(decorators);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param decorator The decorator to add
+     */
+    public void addDecorator(Decorator decorator)
+    {
+        _decorators.add(decorator);
+    }
+
+    /* ------------------------------------------------------------ */
+    void destroyServlet(Servlet servlet)
+    {
+        for (Decorator decorator : _decorators)
+            decorator.destroy(servlet);
+    }
+
+    /* ------------------------------------------------------------ */
+    void destroyFilter(Filter filter)
+    {
+        for (Decorator decorator : _decorators)
+            decorator.destroy(filter);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static class JspPropertyGroup implements JspPropertyGroupDescriptor
+    {
+        private List<String> _urlPatterns = new ArrayList<String>();
+        private String _elIgnored;
+        private String _pageEncoding;
+        private String _scriptingInvalid;
+        private String _isXml;
+        private List<String> _includePreludes = new ArrayList<String>();
+        private List<String> _includeCodas = new ArrayList<String>();
+        private String _deferredSyntaxAllowedAsLiteral;
+        private String _trimDirectiveWhitespaces;
+        private String _defaultContentType;
+        private String _buffer;
+        private String _errorOnUndeclaredNamespace;
+
+
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getUrlPatterns()
+         */
+        public Collection<String> getUrlPatterns()
+        {
+            return new ArrayList<String>(_urlPatterns); // spec says must be a copy
+        }
+
+        public void addUrlPattern (String s)
+        {
+            if (!_urlPatterns.contains(s))
+                _urlPatterns.add(s);
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getElIgnored()
+         */
+        public String getElIgnored()
+        {
+            return _elIgnored;
+        }
+
+        public void setElIgnored (String s)
+        {
+            _elIgnored = s;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getPageEncoding()
+         */
+        public String getPageEncoding()
+        {
+            return _pageEncoding;
+        }
+
+        public void setPageEncoding(String pageEncoding)
+        {
+            _pageEncoding = pageEncoding;
+        }
+
+        public void setScriptingInvalid(String scriptingInvalid)
+        {
+            _scriptingInvalid = scriptingInvalid;
+        }
+
+        public void setIsXml(String isXml)
+        {
+            _isXml = isXml;
+        }
+
+        public void setDeferredSyntaxAllowedAsLiteral(String deferredSyntaxAllowedAsLiteral)
+        {
+            _deferredSyntaxAllowedAsLiteral = deferredSyntaxAllowedAsLiteral;
+        }
+
+        public void setTrimDirectiveWhitespaces(String trimDirectiveWhitespaces)
+        {
+            _trimDirectiveWhitespaces = trimDirectiveWhitespaces;
+        }
+
+        public void setDefaultContentType(String defaultContentType)
+        {
+            _defaultContentType = defaultContentType;
+        }
+
+        public void setBuffer(String buffer)
+        {
+            _buffer = buffer;
+        }
+
+        public void setErrorOnUndeclaredNamespace(String errorOnUndeclaredNamespace)
+        {
+            _errorOnUndeclaredNamespace = errorOnUndeclaredNamespace;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getScriptingInvalid()
+         */
+        public String getScriptingInvalid()
+        {
+            return _scriptingInvalid;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getIsXml()
+         */
+        public String getIsXml()
+        {
+            return _isXml;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getIncludePreludes()
+         */
+        public Collection<String> getIncludePreludes()
+        {
+            return new ArrayList<String>(_includePreludes); //must be a copy
+        }
+
+        public void addIncludePrelude(String prelude)
+        {
+            if (!_includePreludes.contains(prelude))
+                _includePreludes.add(prelude);
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getIncludeCodas()
+         */
+        public Collection<String> getIncludeCodas()
+        {
+            return new ArrayList<String>(_includeCodas); //must be a copy
+        }
+
+        public void addIncludeCoda (String coda)
+        {
+            if (!_includeCodas.contains(coda))
+                _includeCodas.add(coda);
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getDeferredSyntaxAllowedAsLiteral()
+         */
+        public String getDeferredSyntaxAllowedAsLiteral()
+        {
+            return _deferredSyntaxAllowedAsLiteral;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getTrimDirectiveWhitespaces()
+         */
+        public String getTrimDirectiveWhitespaces()
+        {
+            return _trimDirectiveWhitespaces;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getDefaultContentType()
+         */
+        public String getDefaultContentType()
+        {
+            return _defaultContentType;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getBuffer()
+         */
+        public String getBuffer()
+        {
+            return _buffer;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getErrorOnUndeclaredNamespace()
+         */
+        public String getErrorOnUndeclaredNamespace()
+        {
+            return _errorOnUndeclaredNamespace;
+        }
+
+        public String toString ()
+        {
+            StringBuffer sb = new StringBuffer();
+            sb.append("JspPropertyGroupDescriptor:");
+            sb.append(" el-ignored="+_elIgnored);
+            sb.append(" is-xml="+_isXml);
+            sb.append(" page-encoding="+_pageEncoding);
+            sb.append(" scripting-invalid="+_scriptingInvalid);
+            sb.append(" deferred-syntax-allowed-as-literal="+_deferredSyntaxAllowedAsLiteral);
+            sb.append(" trim-directive-whitespaces"+_trimDirectiveWhitespaces);
+            sb.append(" default-content-type="+_defaultContentType);
+            sb.append(" buffer="+_buffer);
+            sb.append(" error-on-undeclared-namespace="+_errorOnUndeclaredNamespace);
+            for (String prelude:_includePreludes)
+                sb.append(" include-prelude="+prelude);
+            for (String coda:_includeCodas)
+                sb.append(" include-coda="+coda);
+            return sb.toString();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static class TagLib implements TaglibDescriptor
+    {
+        private String _uri;
+        private String _location;
+
+        /**
+         * @see javax.servlet.descriptor.TaglibDescriptor#getTaglibURI()
+         */
+        public String getTaglibURI()
+        {
+           return _uri;
+        }
+
+        public void setTaglibURI(String uri)
+        {
+            _uri = uri;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.TaglibDescriptor#getTaglibLocation()
+         */
+        public String getTaglibLocation()
+        {
+            return _location;
+        }
+
+        public void setTaglibLocation(String location)
+        {
+            _location = location;
+        }
+
+        public String toString()
+        {
+            return ("TagLibDescriptor: taglib-uri="+_uri+" location="+_location);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static class JspConfig implements JspConfigDescriptor
+    {
+        private List<TaglibDescriptor> _taglibs = new ArrayList<TaglibDescriptor>();
+        private List<JspPropertyGroupDescriptor> _jspPropertyGroups = new ArrayList<JspPropertyGroupDescriptor>();
+
+        public JspConfig() {}
+
+        /**
+         * @see javax.servlet.descriptor.JspConfigDescriptor#getTaglibs()
+         */
+        public Collection<TaglibDescriptor> getTaglibs()
+        {
+            return new ArrayList<TaglibDescriptor>(_taglibs);
+        }
+
+        public void addTaglibDescriptor (TaglibDescriptor d)
+        {
+            _taglibs.add(d);
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspConfigDescriptor#getJspPropertyGroups()
+         */
+        public Collection<JspPropertyGroupDescriptor> getJspPropertyGroups()
+        {
+           return new ArrayList<JspPropertyGroupDescriptor>(_jspPropertyGroups);
+        }
+
+        public void addJspPropertyGroup(JspPropertyGroupDescriptor g)
+        {
+            _jspPropertyGroups.add(g);
+        }
+
+        public String toString()
+        {
+            StringBuffer sb = new StringBuffer();
+            sb.append("JspConfigDescriptor: \n");
+            for (TaglibDescriptor taglib:_taglibs)
+                sb.append(taglib+"\n");
+            for (JspPropertyGroupDescriptor jpg:_jspPropertyGroups)
+                sb.append(jpg+"\n");
+            return sb.toString();
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public class Context extends ContextHandler.Context
+    {
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
+         */
+        @Override
+        public RequestDispatcher getNamedDispatcher(String name)
+        {
+            ContextHandler context=org.eclipse.jetty.servlet.ServletContextHandler.this;
+            if (_servletHandler==null)
+                return null;
+            ServletHolder holder = _servletHandler.getServlet(name);
+            if (holder==null || !holder.isEnabled())
+                return null;
+            return new Dispatcher(context, name);
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
+        {
+            if (isStarted())
+                throw new IllegalStateException();
+            
+            if (filterName == null || "".equals(filterName.trim()))
+                throw new IllegalStateException("Missing filter name");
+
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            FilterHolder holder = handler.getFilter(filterName);
+            if (holder == null)
+            {
+                //new filter
+                holder = handler.newFilterHolder(Source.JAVAX_API);
+                holder.setName(filterName);
+                holder.setHeldClass(filterClass);
+                handler.addFilter(holder);
+                return holder.getRegistration();
+            }
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                //preliminary filter registration completion
+                holder.setHeldClass(filterClass);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing filter
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public FilterRegistration.Dynamic addFilter(String filterName, String className)
+        {
+            if (isStarted())
+                throw new IllegalStateException();
+            
+            if (filterName == null || "".equals(filterName.trim()))
+                throw new IllegalStateException("Missing filter name");
+
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            FilterHolder holder = handler.getFilter(filterName);
+            if (holder == null)
+            {
+                //new filter
+                holder = handler.newFilterHolder(Source.JAVAX_API);
+                holder.setName(filterName);
+                holder.setClassName(className);
+                handler.addFilter(holder);
+                return holder.getRegistration();
+            }
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                //preliminary filter registration completion
+                holder.setClassName(className);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing filter
+        }
+
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public FilterRegistration.Dynamic addFilter(String filterName, Filter filter)
+        {
+            if (isStarted())
+                throw new IllegalStateException();
+
+            if (filterName == null || "".equals(filterName.trim()))
+                throw new IllegalStateException("Missing filter name");
+            
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            FilterHolder holder = handler.getFilter(filterName);
+            if (holder == null)
+            {
+                //new filter
+                holder = handler.newFilterHolder(Source.JAVAX_API);
+                holder.setName(filterName);
+                holder.setFilter(filter);
+                handler.addFilter(holder);
+                return holder.getRegistration();
+            }
+
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                //preliminary filter registration completion
+                holder.setFilter(filter);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing filter
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+
+            if (servletName == null || "".equals(servletName.trim()))
+                throw new IllegalStateException("Missing servlet name");
+            
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            ServletHolder holder = handler.getServlet(servletName);
+            if (holder == null)
+            {
+                //new servlet
+                holder = handler.newServletHolder(Source.JAVAX_API);
+                holder.setName(servletName);
+                holder.setHeldClass(servletClass);
+                handler.addServlet(holder);
+                return dynamicHolderAdded(holder);
+            }
+
+            //complete a partial registration
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                holder.setHeldClass(servletClass);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing completed registration for servlet name
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public ServletRegistration.Dynamic addServlet(String servletName, String className)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+
+            if (servletName == null || "".equals(servletName.trim()))
+                throw new IllegalStateException("Missing servlet name");
+            
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            ServletHolder holder = handler.getServlet(servletName);
+            if (holder == null)
+            {
+                //new servlet
+                holder = handler.newServletHolder(Source.JAVAX_API);
+                holder.setName(servletName);
+                holder.setClassName(className);
+                handler.addServlet(holder);
+                return dynamicHolderAdded(holder);
+            }
+
+            //complete a partial registration
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                holder.setClassName(className);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing completed registration for servlet name
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+            
+            if (servletName == null || "".equals(servletName.trim()))
+                throw new IllegalStateException("Missing servlet name");
+            
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            ServletHolder holder = handler.getServlet(servletName);
+            if (holder == null)
+            {
+                holder = handler.newServletHolder(Source.JAVAX_API);
+                holder.setName(servletName);
+                holder.setServlet(servlet);
+                handler.addServlet(holder);
+                return dynamicHolderAdded(holder);
+            }
+
+            //complete a partial registration
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                holder.setServlet(servlet);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing completed registration for servlet name
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public boolean setInitParameter(String name, String value)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            return super.setInitParameter(name,value);
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public <T extends Filter> T createFilter(Class<T> c) throws ServletException
+        {
+            try
+            {
+                T f = createInstance(c);
+                for (int i=_decorators.size()-1; i>=0; i--)
+                {
+                    Decorator decorator = _decorators.get(i);
+                    f=decorator.decorate(f);
+                }
+                return f;
+            }
+            catch (Exception e)
+            {
+                throw new ServletException(e);
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public <T extends Servlet> T createServlet(Class<T> c) throws ServletException
+        {
+            try
+            {
+                T s = createInstance(c);
+                for (int i=_decorators.size()-1; i>=0; i--)
+                {
+                    Decorator decorator = _decorators.get(i);
+                    s=decorator.decorate(s);
+                }
+                return s;
+            }
+            catch (Exception e)
+            {
+                throw new ServletException(e);
+            }
+        }
+        
+
+        @Override
+        public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+        {
+            if (_sessionHandler!=null)
+                return _sessionHandler.getSessionManager().getDefaultSessionTrackingModes();
+            return null;
+        }
+
+        @Override
+        public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+        {
+            if (_sessionHandler!=null)
+                return _sessionHandler.getSessionManager().getEffectiveSessionTrackingModes();
+            return null;
+        }
+
+        @Override
+        public FilterRegistration getFilterRegistration(String filterName)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final FilterHolder holder=ServletContextHandler.this.getServletHandler().getFilter(filterName);
+            return (holder==null)?null:holder.getRegistration();
+        }
+
+        @Override
+        public Map<String, ? extends FilterRegistration> getFilterRegistrations()
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            HashMap<String, FilterRegistration> registrations = new HashMap<String, FilterRegistration>();
+            ServletHandler handler=ServletContextHandler.this.getServletHandler();
+            FilterHolder[] holders=handler.getFilters();
+            if (holders!=null)
+            {
+                for (FilterHolder holder : holders)
+                    registrations.put(holder.getName(),holder.getRegistration());
+            }
+            return registrations;
+        }
+
+        @Override
+        public ServletRegistration getServletRegistration(String servletName)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final ServletHolder holder=ServletContextHandler.this.getServletHandler().getServlet(servletName);
+            return (holder==null)?null:holder.getRegistration();
+        }
+
+        @Override
+        public Map<String, ? extends ServletRegistration> getServletRegistrations()
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            HashMap<String, ServletRegistration> registrations = new HashMap<String, ServletRegistration>();
+            ServletHandler handler=ServletContextHandler.this.getServletHandler();
+            ServletHolder[] holders=handler.getServlets();
+            if (holders!=null)
+            {
+                for (ServletHolder holder : holders)
+                    registrations.put(holder.getName(),holder.getRegistration());
+            }
+            return registrations;
+        }
+
+        @Override
+        public SessionCookieConfig getSessionCookieConfig()
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            if (_sessionHandler!=null)
+                return _sessionHandler.getSessionManager().getSessionCookieConfig();
+            return null;
+        }
+
+        @Override
+        public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+
+            if (_sessionHandler!=null)
+                _sessionHandler.getSessionManager().setSessionTrackingModes(sessionTrackingModes);
+        }
+
+        @Override
+        public void addListener(String className)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            super.addListener(className);
+        }
+
+        @Override
+        public <T extends EventListener> void addListener(T t)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            super.addListener(t);
+            ListenerHolder holder = getServletHandler().newListenerHolder(Source.JAVAX_API);
+            holder.setListener(t);
+            getServletHandler().addListener(holder);
+        }
+
+        @Override
+        public void addListener(Class<? extends EventListener> listenerClass)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            super.addListener(listenerClass);
+        }
+
+        @Override
+        public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
+        {
+            try
+            {
+                T l = createInstance(clazz);
+                for (int i=_decorators.size()-1; i>=0; i--)
+                {
+                    Decorator decorator = _decorators.get(i);
+                    l=decorator.decorate(l);
+                }
+                return l;
+            }            
+            catch (Exception e)
+            {
+                throw new ServletException(e);
+            }
+        }
+
+
+        @Override
+        public JspConfigDescriptor getJspConfigDescriptor()
+        {
+            return _jspConfig;
+        }
+
+        @Override
+        public void setJspConfigDescriptor(JspConfigDescriptor d)
+        {
+            _jspConfig = d;
+        }
+
+
+        @Override
+        public void declareRoles(String... roleNames)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            addRoles(roleNames);
+
+
+        }
+
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /** Interface to decorate loaded classes.
+     */
+    public interface Decorator
+    {
+        <T> T decorate (T o);
+        void destroy (Object o);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/ServletHandler.java b/lib/jetty/org/eclipse/jetty/servlet/ServletHandler.java
new file mode 100644 (file)
index 0000000..81243e0
--- /dev/null
@@ -0,0 +1,1807 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.QuietServletException;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.ServletRequestHttpWrapper;
+import org.eclipse.jetty.server.ServletResponseHttpWrapper;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ScopedHandler;
+import org.eclipse.jetty.servlet.BaseHolder.Source;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* --------------------------------------------------------------------- */
+/** Servlet HttpHandler.
+ * This handler maps requests to servlets that implement the
+ * javax.servlet.http.HttpServlet API.
+ * <P>
+ * This handler does not implement the full J2EE features and is intended to
+ * be used directly when a full web application is not required.  If a Web application is required,
+ * then this handler should be used as part of a <code>org.eclipse.jetty.webapp.WebAppContext</code>.
+ * <p>
+ * Unless run as part of a {@link ServletContextHandler} or derivative, the {@link #initialize()}
+ * method must be called manually after start().
+ */
+
+/* ------------------------------------------------------------ */
+/**
+ */
+@ManagedObject("Servlet Handler")
+public class ServletHandler extends ScopedHandler
+{
+    private static final Logger LOG = Log.getLogger(ServletHandler.class);
+
+    /* ------------------------------------------------------------ */
+    public static final String __DEFAULT_SERVLET="default";
+
+    /* ------------------------------------------------------------ */
+    private ServletContextHandler _contextHandler;
+    private ServletContext _servletContext;
+    private FilterHolder[] _filters=new FilterHolder[0];
+    private FilterMapping[] _filterMappings;
+    private int _matchBeforeIndex = -1; //index of last programmatic FilterMapping with isMatchAfter=false
+    private int _matchAfterIndex = -1;  //index of 1st programmatic FilterMapping with isMatchAfter=true
+    private boolean _filterChainsCached=true;
+    private int _maxFilterChainsCacheSize=512;
+    private boolean _startWithUnavailable=false;
+    private boolean _ensureDefaultServlet=true;
+    private IdentityService _identityService;
+
+    private ServletHolder[] _servlets=new ServletHolder[0];
+    private ServletMapping[] _servletMappings;
+    private Map<String,ServletMapping> _servletPathMappings = new HashMap<String,ServletMapping>();
+
+    private final Map<String,FilterHolder> _filterNameMap= new HashMap<>();
+    private List<FilterMapping> _filterPathMappings;
+    private MultiMap<FilterMapping> _filterNameMappings;
+
+    private final Map<String,ServletHolder> _servletNameMap=new HashMap<>();
+    private PathMap<ServletHolder> _servletPathMap;
+    
+    private ListenerHolder[] _listeners=new ListenerHolder[0];
+
+    protected final ConcurrentMap<?, ?> _chainCache[] = new ConcurrentMap[FilterMapping.ALL];
+    protected final Queue<?>[] _chainLRU = new Queue[FilterMapping.ALL];
+    
+
+
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     */
+    public ServletHandler()
+    {
+    }
+
+    /* ----------------------------------------------------------------- */
+    @Override
+    protected synchronized void doStart()
+        throws Exception
+    {
+        ContextHandler.Context context=ContextHandler.getCurrentContext();
+        _servletContext=context==null?new ContextHandler.NoContext():context;
+        _contextHandler=(ServletContextHandler)(context==null?null:context.getContextHandler());
+
+        if (_contextHandler!=null)
+        {
+            SecurityHandler security_handler = _contextHandler.getChildHandlerByClass(SecurityHandler.class);
+            if (security_handler!=null)
+                _identityService=security_handler.getIdentityService();
+        }
+
+        updateNameMappings();
+        updateMappings();        
+        
+        if (getServletMapping("/")==null && _ensureDefaultServlet)
+        {
+            LOG.debug("Adding Default404Servlet to {}",this);
+            addServletWithMapping(Default404Servlet.class,"/");
+            updateMappings();  
+            getServletMapping("/").setDefault(true);
+        }
+
+        if(_filterChainsCached)
+        {
+            _chainCache[FilterMapping.REQUEST]=new ConcurrentHashMap<String,FilterChain>();
+            _chainCache[FilterMapping.FORWARD]=new ConcurrentHashMap<String,FilterChain>();
+            _chainCache[FilterMapping.INCLUDE]=new ConcurrentHashMap<String,FilterChain>();
+            _chainCache[FilterMapping.ERROR]=new ConcurrentHashMap<String,FilterChain>();
+            _chainCache[FilterMapping.ASYNC]=new ConcurrentHashMap<String,FilterChain>();
+
+            _chainLRU[FilterMapping.REQUEST]=new ConcurrentLinkedQueue<String>();
+            _chainLRU[FilterMapping.FORWARD]=new ConcurrentLinkedQueue<String>();
+            _chainLRU[FilterMapping.INCLUDE]=new ConcurrentLinkedQueue<String>();
+            _chainLRU[FilterMapping.ERROR]=new ConcurrentLinkedQueue<String>();
+            _chainLRU[FilterMapping.ASYNC]=new ConcurrentLinkedQueue<String>();
+        }
+
+        if (_contextHandler==null)
+            initialize();
+        
+        super.doStart();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if ServletHandler always has a default servlet, using {@link Default404Servlet} if no other
+     * default servlet is configured.
+     */
+    public boolean isEnsureDefaultServlet()
+    {
+        return _ensureDefaultServlet;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param ensureDefaultServlet true if ServletHandler always has a default servlet, using {@link Default404Servlet} if no other
+     * default servlet is configured.
+     */
+    public void setEnsureDefaultServlet(boolean ensureDefaultServlet)
+    {
+        _ensureDefaultServlet=ensureDefaultServlet;
+    }
+
+    /* ----------------------------------------------------------------- */
+    @Override
+    protected void start(LifeCycle l) throws Exception
+    {
+        //Don't start the whole object tree (ie all the servlet and filter Holders) when
+        //this handler starts. They have a slightly special lifecycle, and should only be
+        //started AFTER the handlers have all started (and the ContextHandler has called
+        //the context listeners).
+        if (!(l instanceof Holder))
+            super.start(l);
+    }
+
+    /* ----------------------------------------------------------------- */
+    @Override
+    protected synchronized void doStop()
+        throws Exception
+    {
+        super.doStop();
+
+        // Stop filters
+        List<FilterHolder> filterHolders = new ArrayList<FilterHolder>();
+        List<FilterMapping> filterMappings = ArrayUtil.asMutableList(_filterMappings);      
+        if (_filters!=null)
+        {
+            for (int i=_filters.length; i-->0;)
+            {
+                try 
+                {
+                    _filters[i].stop(); 
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(Log.EXCEPTION,e);
+                }
+                if (_filters[i].getSource() != Source.EMBEDDED)
+                {
+                    //remove all of the mappings that were for non-embedded filters
+                    _filterNameMap.remove(_filters[i].getName());
+                    //remove any mappings associated with this filter
+                    ListIterator<FilterMapping> fmitor = filterMappings.listIterator();
+                    while (fmitor.hasNext())
+                    {
+                        FilterMapping fm = fmitor.next();
+                        if (fm.getFilterName().equals(_filters[i].getName()))
+                            fmitor.remove();
+                    }
+                }
+                else
+                    filterHolders.add(_filters[i]); //only retain embedded
+            }
+        }
+        
+        //Retain only filters and mappings that were added using jetty api (ie Source.EMBEDDED)
+        FilterHolder[] fhs = (FilterHolder[]) LazyList.toArray(filterHolders, FilterHolder.class);
+        updateBeans(_filters, fhs);
+        _filters = fhs;
+        FilterMapping[] fms = (FilterMapping[]) LazyList.toArray(filterMappings, FilterMapping.class);
+        updateBeans(_filterMappings, fms);
+        _filterMappings = fms;
+        
+        _matchAfterIndex = (_filterMappings == null || _filterMappings.length == 0 ? -1 : _filterMappings.length-1);
+        _matchBeforeIndex = -1;
+
+        // Stop servlets
+        List<ServletHolder> servletHolders = new ArrayList<ServletHolder>();  //will be remaining servlets
+        List<ServletMapping> servletMappings = ArrayUtil.asMutableList(_servletMappings); //will be remaining mappings
+        if (_servlets!=null)
+        {
+            for (int i=_servlets.length; i-->0;)
+            {
+                try 
+                { 
+                    _servlets[i].stop(); 
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(Log.EXCEPTION,e);
+                }
+                
+                if (_servlets[i].getSource() != Source.EMBEDDED)
+                {
+                    //remove from servlet name map
+                    _servletNameMap.remove(_servlets[i].getName());
+                    //remove any mappings associated with this servlet
+                    ListIterator<ServletMapping> smitor = servletMappings.listIterator();
+                    while (smitor.hasNext())
+                    {
+                        ServletMapping sm = smitor.next();
+                        if (sm.getServletName().equals(_servlets[i].getName()))
+                            smitor.remove();
+                    }
+                }
+                else
+                    servletHolders.add(_servlets[i]); //only retain embedded 
+            }
+        }
+
+        //Retain only Servlets and mappings added via jetty apis (ie Source.EMBEDDED)
+        ServletHolder[] shs = (ServletHolder[]) LazyList.toArray(servletHolders, ServletHolder.class);
+        updateBeans(_servlets, shs);
+        _servlets = shs;
+        ServletMapping[] sms = (ServletMapping[])LazyList.toArray(servletMappings, ServletMapping.class); 
+        updateBeans(_servletMappings, sms);
+        _servletMappings = sms;
+
+        //Retain only Listeners added via jetty apis (is Source.EMBEDDED)
+        List<ListenerHolder> listenerHolders = new ArrayList<ListenerHolder>();
+        if (_listeners != null)
+        { 
+            for (int i=_listeners.length; i-->0;)
+            {
+                try
+                {
+                    _listeners[i].stop();
+                } 
+                catch(Exception e)
+                {
+                    LOG.warn(Log.EXCEPTION,e);
+                }
+                if (_listeners[i].getSource() == Source.EMBEDDED)
+                    listenerHolders.add(_listeners[i]);
+            }
+        }
+        ListenerHolder[] listeners = (ListenerHolder[])LazyList.toArray(listenerHolders, ListenerHolder.class);
+        updateBeans(_listeners, listeners);
+        _listeners = listeners;
+
+        //will be regenerated on next start
+        _filterPathMappings=null;
+        _filterNameMappings=null;
+        _servletPathMap=null;
+        _servletPathMappings=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected IdentityService getIdentityService()
+    {
+        return _identityService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the contextLog.
+     */
+    public Object getContextLog()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the filterMappings.
+     */
+    @ManagedAttribute(value="filters", readonly=true)
+    public FilterMapping[] getFilterMappings()
+    {
+        return _filterMappings;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get Filters.
+     * @return Array of defined servlets
+     */
+    @ManagedAttribute(value="filters", readonly=true)
+    public FilterHolder[] getFilters()
+    {
+        return _filters;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** ServletHolder matching path.
+     * @param pathInContext Path within _context.
+     * @return PathMap Entries pathspec to ServletHolder
+     */
+    public PathMap.MappedEntry<ServletHolder> getHolderEntry(String pathInContext)
+    {
+        if (_servletPathMap==null)
+            return null;
+        return _servletPathMap.getMatch(pathInContext);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContext getServletContext()
+    {
+        return _servletContext;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletMappings.
+     */
+    @ManagedAttribute(value="mappings of servlets", readonly=true)
+    public ServletMapping[] getServletMappings()
+    {
+        return _servletMappings;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the ServletMapping matching the path
+     * 
+     * @param pathSpec
+     * @return
+     */
+    public ServletMapping getServletMapping(String pathSpec)
+    {
+        if (pathSpec == null || _servletPathMappings == null)
+            return null;
+        
+        return _servletPathMappings.get(pathSpec);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get Servlets.
+     * @return Array of defined servlets
+     */
+    @ManagedAttribute(value="servlets", readonly=true)
+    public ServletHolder[] getServlets()
+    {
+        return _servlets;
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletHolder getServlet(String name)
+    {
+        return _servletNameMap.get(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        // Get the base requests
+        final String old_servlet_path=baseRequest.getServletPath();
+        final String old_path_info=baseRequest.getPathInfo();
+
+        DispatcherType type = baseRequest.getDispatcherType();
+
+        ServletHolder servlet_holder=null;
+        UserIdentity.Scope old_scope=null;
+
+        // find the servlet
+        if (target.startsWith("/"))
+        {
+            // Look for the servlet by path
+            PathMap.MappedEntry<ServletHolder> entry=getHolderEntry(target);
+            if (entry!=null)
+            {
+                servlet_holder=entry.getValue();
+
+                String servlet_path_spec= entry.getKey();
+                String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target);
+                String path_info=PathMap.pathInfo(servlet_path_spec,target);
+
+                if (DispatcherType.INCLUDE.equals(type))
+                {
+                    baseRequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH,servlet_path);
+                    baseRequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO, path_info);
+                }
+                else
+                {
+                    baseRequest.setServletPath(servlet_path);
+                    baseRequest.setPathInfo(path_info);
+                }
+            }
+        }
+        else
+        {
+            // look for a servlet by name!
+            servlet_holder= _servletNameMap.get(target);
+        }
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("servlet {}|{}|{} -> {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),servlet_holder);
+
+        try
+        {
+            // Do the filter/handling thang
+            old_scope=baseRequest.getUserIdentityScope();
+            baseRequest.setUserIdentityScope(servlet_holder);
+
+            // start manual inline of nextScope(target,baseRequest,request,response);
+            if (never())
+                nextScope(target,baseRequest,request,response);
+            else if (_nextScope!=null)
+                _nextScope.doScope(target,baseRequest,request, response);
+            else if (_outerScope!=null)
+                _outerScope.doHandle(target,baseRequest,request, response);
+            else
+                doHandle(target,baseRequest,request, response);
+            // end manual inline (pathentic attempt to reduce stack depth)
+        }
+        finally
+        {
+            if (old_scope!=null)
+                baseRequest.setUserIdentityScope(old_scope);
+
+            if (!(DispatcherType.INCLUDE.equals(type)))
+            {
+                baseRequest.setServletPath(old_servlet_path);
+                baseRequest.setPathInfo(old_path_info);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void doHandle(String target, Request baseRequest,HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException
+    {
+        DispatcherType type = baseRequest.getDispatcherType();
+
+        ServletHolder servlet_holder=(ServletHolder) baseRequest.getUserIdentityScope();
+        FilterChain chain=null;
+
+        // find the servlet
+        if (target.startsWith("/"))
+        {
+            if (servlet_holder!=null && _filterMappings!=null && _filterMappings.length>0)
+                chain=getFilterChain(baseRequest, target, servlet_holder);
+        }
+        else
+        {
+            if (servlet_holder!=null)
+            {
+                if (_filterMappings!=null && _filterMappings.length>0)
+                {
+                    chain=getFilterChain(baseRequest, null,servlet_holder);
+                }
+            }
+        }
+
+        LOG.debug("chain={}",chain);
+        Throwable th=null;
+        try
+        {
+            if (servlet_holder==null)
+                notFound(baseRequest,request, response);
+            else
+            {
+                // unwrap any tunnelling of base Servlet request/responses
+                ServletRequest req = request;
+                if (req instanceof ServletRequestHttpWrapper)
+                    req = ((ServletRequestHttpWrapper)req).getRequest();
+                ServletResponse res = response;
+                if (res instanceof ServletResponseHttpWrapper)
+                    res = ((ServletResponseHttpWrapper)res).getResponse();
+
+                // Do the filter/handling thang
+                if (chain!=null)
+                    chain.doFilter(req, res);
+                else
+                    servlet_holder.handle(baseRequest,req,res);
+            }
+        }
+        catch(EofException e)
+        {
+            throw e;
+        }
+        catch(RuntimeIOException e)
+        {
+            throw e;
+        }
+        catch(Exception e)
+        {
+            if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
+            {
+                if (e instanceof IOException)
+                    throw (IOException)e;
+                if (e instanceof RuntimeException)
+                    throw (RuntimeException)e;
+                if (e instanceof ServletException)
+                    throw (ServletException)e;
+            }
+
+            // unwrap cause
+            th=e;
+            if (th instanceof ServletException)
+            {
+                if (th instanceof QuietServletException)
+                { 
+                    LOG.warn(th.toString());
+                    LOG.debug(th);
+                }
+                else
+                    LOG.warn(th);
+            }
+            else if (th instanceof EofException)
+            {
+                throw (EofException)th;
+            }
+            else
+            {
+                LOG.warn(request.getRequestURI(),th);
+                if (LOG.isDebugEnabled())
+                    LOG.debug(request.toString());
+            }
+
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,th.getClass());
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,th);
+            if (!response.isCommitted())
+            {
+                baseRequest.getResponse().getHttpFields().put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
+                if (th instanceof UnavailableException)
+                {
+                    UnavailableException ue = (UnavailableException)th;
+                    if (ue.isPermanent())
+                        response.sendError(HttpServletResponse.SC_NOT_FOUND);
+                    else
+                        response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+                }
+                else
+                    response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            }
+            else
+            {
+                if (th instanceof IOException)
+                    throw (IOException)th;
+                if (th instanceof RuntimeException)
+                    throw (RuntimeException)th;
+                if (th instanceof ServletException)
+                    throw (ServletException)th;
+                throw new IllegalStateException("response already committed",th);
+            }
+        }
+        catch(Error e)
+        {
+            if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
+                throw e;
+            th=e;
+            if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
+                throw e;
+            LOG.warn("Error for "+request.getRequestURI(),e);
+            if(LOG.isDebugEnabled())
+                LOG.debug(request.toString());
+
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,e.getClass());
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
+            if (!response.isCommitted())
+            {
+                baseRequest.getResponse().getHttpFields().put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            }
+            else
+                LOG.debug("Response already committed for handling ",e);
+        }
+        finally
+        {
+            // Complete async errored requests 
+            if (th!=null && request.isAsyncStarted())
+                baseRequest.getHttpChannelState().errorComplete();
+            
+            if (servlet_holder!=null)
+                baseRequest.setHandled(true);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder)
+    {
+        String key=pathInContext==null?servletHolder.getName():pathInContext;
+        int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType());
+
+        if (_filterChainsCached && _chainCache!=null)
+        {
+            FilterChain chain = (FilterChain)_chainCache[dispatch].get(key);
+            if (chain!=null)
+                return chain;
+        }
+
+        // Build list of filters (list of FilterHolder objects)
+        List<FilterHolder> filters = new ArrayList<>();
+
+        // Path filters
+        if (pathInContext!=null && _filterPathMappings!=null)
+        {
+            for (FilterMapping filterPathMapping : _filterPathMappings)
+            {
+                if (filterPathMapping.appliesTo(pathInContext, dispatch))
+                    filters.add(filterPathMapping.getFilterHolder());
+            }
+        }
+
+        // Servlet name filters
+        if (servletHolder != null && _filterNameMappings!=null && _filterNameMappings.size() > 0)
+        {
+            // Servlet name filters
+            if (_filterNameMappings.size() > 0)
+            {
+                Object o= _filterNameMappings.get(servletHolder.getName());
+
+                for (int i=0; i<LazyList.size(o);i++)
+                {
+                    FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
+                    if (mapping.appliesTo(dispatch))
+                        filters.add(mapping.getFilterHolder());
+                }
+
+                o= _filterNameMappings.get("*");
+                for (int i=0; i<LazyList.size(o);i++)
+                {
+                    FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
+                    if (mapping.appliesTo(dispatch))
+                        filters.add(mapping.getFilterHolder());
+                }
+            }
+        }
+
+        if (filters.isEmpty())
+            return null;
+
+
+        FilterChain chain = null;
+        if (_filterChainsCached)
+        {
+            if (filters.size() > 0)
+                chain= new CachedChain(filters, servletHolder);
+
+            final Map<String,FilterChain> cache=(Map<String, FilterChain>)_chainCache[dispatch];
+            final Queue<String> lru=(Queue<String>)_chainLRU[dispatch];
+
+               // Do we have too many cached chains?
+               while (_maxFilterChainsCacheSize>0 && cache.size()>=_maxFilterChainsCacheSize)
+               {
+                   // The LRU list is not atomic with the cache map, so be prepared to invalidate if
+                   // a key is not found to delete.
+                   // Delete by LRU (where U==created)
+                   String k=lru.poll();
+                   if (k==null)
+                   {
+                       cache.clear();
+                       break;
+                   }
+                   cache.remove(k);
+               }
+
+               cache.put(key,chain);
+               lru.add(key);
+        }
+        else if (filters.size() > 0)
+            chain = new Chain(baseRequest,filters, servletHolder);
+
+        return chain;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void invalidateChainsCache()
+    {
+        if (_chainLRU[FilterMapping.REQUEST]!=null)
+        {
+            _chainLRU[FilterMapping.REQUEST].clear();
+            _chainLRU[FilterMapping.FORWARD].clear();
+            _chainLRU[FilterMapping.INCLUDE].clear();
+            _chainLRU[FilterMapping.ERROR].clear();
+            _chainLRU[FilterMapping.ASYNC].clear();
+
+            _chainCache[FilterMapping.REQUEST].clear();
+            _chainCache[FilterMapping.FORWARD].clear();
+            _chainCache[FilterMapping.INCLUDE].clear();
+            _chainCache[FilterMapping.ERROR].clear();
+            _chainCache[FilterMapping.ASYNC].clear();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the handler is started and there are no unavailable servlets
+     */
+    public boolean isAvailable()
+    {
+        if (!isStarted())
+            return false;
+        ServletHolder[] holders = getServlets();
+        for (ServletHolder holder : holders)
+        {
+            if (holder != null && !holder.isAvailable())
+                return false;
+        }
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param start True if this handler will start with unavailable servlets
+     */
+    public void setStartWithUnavailable(boolean start)
+    {
+        _startWithUnavailable=start;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if this handler will start with unavailable servlets
+     */
+    public boolean isStartWithUnavailable()
+    {
+        return _startWithUnavailable;
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /** Initialize filters and load-on-startup servlets.
+     */
+    public void initialize()
+        throws Exception
+    {
+        MultiException mx = new MultiException();
+
+        //start filter holders now
+        if (_filters != null)
+        {
+            for (FilterHolder f: _filters)
+            {
+                try
+                {
+                    f.start();
+                    f.initialize();
+                }
+                catch (Exception e)
+                {
+                    mx.add(e);
+                }
+            }
+        }
+        
+        // Sort and Initialize servlets
+        if (_servlets!=null)
+        {
+            ServletHolder[] servlets = _servlets.clone();
+            Arrays.sort(servlets);
+            for (ServletHolder servlet : servlets)
+            {
+                try
+                {
+                   /* if (servlet.getClassName() == null && servlet.getForcedPath() != null)
+                    {
+                        ServletHolder forced_holder = _servletPathMap.match(servlet.getForcedPath());
+                        if (forced_holder == null || forced_holder.getClassName() == null)
+                        {
+                            mx.add(new IllegalStateException("No forced path servlet for " + servlet.getForcedPath()));
+                            continue;
+                        }
+                        System.err.println("ServletHandler setting forced path classname to "+forced_holder.getClassName()+ " for "+servlet.getForcedPath());
+                        servlet.setClassName(forced_holder.getClassName());
+                    }*/
+                    
+                    servlet.start();
+                    servlet.initialize();
+                }
+                catch (Throwable e)
+                {
+                    LOG.debug(Log.EXCEPTION, e);
+                    mx.add(e);
+                }
+            }
+        }
+
+        //any other beans
+        for (Holder<?> h: getBeans(Holder.class))
+        {
+            try
+            {
+                if (!h.isStarted())
+                {
+                    h.start();
+                    h.initialize();
+                }
+            }
+            catch (Exception e)
+            {
+                mx.add(e);
+            }
+        }
+        
+        mx.ifExceptionThrow();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the filterChainsCached.
+     */
+    public boolean isFilterChainsCached()
+    {
+        return _filterChainsCached;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add a holder for a listener
+     * @param filter
+     */
+    public void addListener (ListenerHolder listener)
+    {
+        if (listener != null)
+            setListeners(ArrayUtil.addToArray(getListeners(), listener, ListenerHolder.class));
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public ListenerHolder[] getListeners()
+    {
+        return _listeners;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setListeners(ListenerHolder[] listeners)
+    {
+        if (listeners!=null)
+            for (ListenerHolder holder:listeners)
+                holder.setServletHandler(this);
+
+        updateBeans(_listeners,listeners);
+        _listeners = listeners;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ListenerHolder newListenerHolder(Holder.Source source)
+    {
+        return new ListenerHolder(source);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * see also newServletHolder(Class)
+     */
+    public ServletHolder newServletHolder(Holder.Source source)
+    {
+        return new ServletHolder(source);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a servlet.
+     * @return The servlet holder.
+     */
+    public ServletHolder addServletWithMapping (String className,String pathSpec)
+    {
+        ServletHolder holder = newServletHolder(Source.EMBEDDED);
+        holder.setClassName(className);
+        addServletWithMapping(holder,pathSpec);
+        return holder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a servlet.
+     * @return The servlet holder.
+     */
+    public ServletHolder addServletWithMapping (Class<? extends Servlet> servlet,String pathSpec)
+    {
+        ServletHolder holder = newServletHolder(Source.EMBEDDED);
+        holder.setHeldClass(servlet);
+        addServletWithMapping(holder,pathSpec);
+
+        return holder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a servlet.
+     * @param servlet servlet holder to add
+     * @param pathSpec servlet mappings for the servletHolder
+     */
+    public void addServletWithMapping (ServletHolder servlet,String pathSpec)
+    {
+        ServletHolder[] holders=getServlets();
+        if (holders!=null)
+            holders = holders.clone();
+
+        try
+        {
+            setServlets(ArrayUtil.addToArray(holders, servlet, ServletHolder.class));
+
+            ServletMapping mapping = new ServletMapping();
+            mapping.setServletName(servlet.getName());
+            mapping.setPathSpec(pathSpec);
+            setServletMappings(ArrayUtil.addToArray(getServletMappings(), mapping, ServletMapping.class));
+        }
+        catch (Exception e)
+        {
+            setServlets(holders);
+            if (e instanceof RuntimeException)
+                throw (RuntimeException)e;
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**Convenience method to add a pre-constructed ServletHolder.
+     * @param holder
+     */
+    public void addServlet(ServletHolder holder)
+    {
+        setServlets(ArrayUtil.addToArray(getServlets(), holder, ServletHolder.class));
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a pre-constructed ServletMapping.
+     * @param mapping
+     */
+    public void addServletMapping (ServletMapping mapping)
+    {
+        setServletMappings(ArrayUtil.addToArray(getServletMappings(), mapping, ServletMapping.class));
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Set<String>  setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement) 
+    {
+        if (_contextHandler != null) 
+        {
+            return _contextHandler.setServletSecurity(registration, servletSecurityElement);
+        }
+        return Collections.emptySet();
+    }
+
+    /* ------------------------------------------------------------ */
+    public FilterHolder newFilterHolder(Holder.Source source)
+    {
+        return new FilterHolder(source);
+    }
+
+    /* ------------------------------------------------------------ */
+    public FilterHolder getFilter(String name)
+    {
+        return _filterNameMap.get(name);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param filter  class of filter to create
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     * @return The filter holder.
+     */
+    public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        FilterHolder holder = newFilterHolder(Source.EMBEDDED);
+        holder.setHeldClass(filter);
+        addFilterWithMapping(holder,pathSpec,dispatches);
+
+        return holder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param className of filter
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     * @return The filter holder.
+     */
+    public FilterHolder addFilterWithMapping (String className,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        FilterHolder holder = newFilterHolder(Source.EMBEDDED);
+        holder.setClassName(className);
+
+        addFilterWithMapping(holder,pathSpec,dispatches);
+        return holder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param holder filter holder to add
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     */
+    public void addFilterWithMapping (FilterHolder holder,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        FilterHolder[] holders = getFilters();
+        if (holders!=null)
+            holders = holders.clone();
+
+        try
+        {
+            setFilters(ArrayUtil.addToArray(holders, holder, FilterHolder.class));
+
+            FilterMapping mapping = new FilterMapping();
+            mapping.setFilterName(holder.getName());
+            mapping.setPathSpec(pathSpec);
+            mapping.setDispatcherTypes(dispatches);
+            addFilterMapping(mapping);
+            
+        }
+        catch (RuntimeException e)
+        {
+            setFilters(holders);
+            throw e;
+        }
+        catch (Error e)
+        {
+            setFilters(holders);
+            throw e;
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param filter  class of filter to create
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     * @return The filter holder.
+     */
+    public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,int dispatches)
+    {
+        FilterHolder holder = newFilterHolder(Source.EMBEDDED);
+        holder.setHeldClass(filter);
+        addFilterWithMapping(holder,pathSpec,dispatches);
+
+        return holder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param className of filter
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     * @return The filter holder.
+     */
+    public FilterHolder addFilterWithMapping (String className,String pathSpec,int dispatches)
+    {
+        FilterHolder holder = newFilterHolder(Source.EMBEDDED);
+        holder.setClassName(className);
+
+        addFilterWithMapping(holder,pathSpec,dispatches);
+        return holder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param holder filter holder to add
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     */
+    public void addFilterWithMapping (FilterHolder holder,String pathSpec,int dispatches)
+    {
+        FilterHolder[] holders = getFilters();
+        if (holders!=null)
+            holders = holders.clone();
+
+        try
+        {
+            setFilters(ArrayUtil.addToArray(holders, holder, FilterHolder.class));
+
+            FilterMapping mapping = new FilterMapping();
+            mapping.setFilterName(holder.getName());
+            mapping.setPathSpec(pathSpec);
+            mapping.setDispatches(dispatches);
+            addFilterMapping(mapping);
+        }
+        catch (RuntimeException e)
+        {
+            setFilters(holders);
+            throw e;
+        }
+        catch (Error e)
+        {
+            setFilters(holders);
+            throw e;
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter with a mapping
+     * @param className
+     * @param pathSpec
+     * @param dispatches
+     * @return the filter holder created
+     * @deprecated use {@link #addFilterWithMapping(Class, String, EnumSet)} instead
+     */
+    public FilterHolder addFilter (String className,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        return addFilterWithMapping(className, pathSpec, dispatches);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * convenience method to add a filter and mapping
+     * @param filter
+     * @param filterMapping
+     */
+    public void addFilter (FilterHolder filter, FilterMapping filterMapping)
+    {
+        if (filter != null)
+            setFilters(ArrayUtil.addToArray(getFilters(), filter, FilterHolder.class));
+        if (filterMapping != null)
+            addFilterMapping(filterMapping);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a preconstructed FilterHolder
+     * @param filter
+     */
+    public void addFilter (FilterHolder filter)
+    {
+        if (filter != null)
+            setFilters(ArrayUtil.addToArray(getFilters(), filter, FilterHolder.class));
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a preconstructed FilterMapping
+     * @param mapping
+     */
+    public void addFilterMapping (FilterMapping mapping)
+    {
+        if (mapping != null)
+        {
+            Source source = (mapping.getFilterHolder()==null?null:mapping.getFilterHolder().getSource());
+            FilterMapping[] mappings =getFilterMappings();
+            if (mappings==null || mappings.length==0)
+            {
+                setFilterMappings(insertFilterMapping(mapping,0,false));
+                if (source != null && source == Source.JAVAX_API)
+                    _matchAfterIndex = 0;
+            }
+            else
+            {
+                //there are existing entries. If this is a programmatic filtermapping, it is added at the end of the list.
+                //If this is a normal filtermapping, it is inserted after all the other filtermappings (matchBefores and normals), 
+                //but before the first matchAfter filtermapping.
+                if (source != null && Source.JAVAX_API == source)
+                {
+                    setFilterMappings(insertFilterMapping(mapping,mappings.length-1, false));
+                    if (_matchAfterIndex < 0)
+                        _matchAfterIndex = getFilterMappings().length-1;
+                }
+                else
+                {
+                    //insert non-programmatic filter mappings before any matchAfters, if any
+                    if (_matchAfterIndex < 0)
+                        setFilterMappings(insertFilterMapping(mapping,mappings.length-1, false));
+                    else
+                    {
+                        FilterMapping[] new_mappings = insertFilterMapping(mapping, _matchAfterIndex, true);
+                        ++_matchAfterIndex;
+                        setFilterMappings(new_mappings);
+                    }
+                }
+            }
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a preconstructed FilterMapping
+     * @param mapping
+     */
+    public void prependFilterMapping (FilterMapping mapping)
+    {
+        if (mapping != null)
+        {
+            Source source = mapping.getFilterHolder().getSource();
+            
+            FilterMapping[] mappings = getFilterMappings();
+            if (mappings==null || mappings.length==0)
+            {
+                setFilterMappings(insertFilterMapping(mapping, 0, false));
+                if (source != null && Source.JAVAX_API == source)
+                    _matchBeforeIndex = 0;
+            }
+            else
+            {
+                if (source != null && Source.JAVAX_API == source)
+                {
+                    //programmatically defined filter mappings are prepended to mapping list in the order
+                    //in which they were defined. In other words, insert this mapping at the tail of the 
+                    //programmatically prepended filter mappings, BEFORE the first web.xml defined filter mapping.
+
+                    if (_matchBeforeIndex < 0)
+                    { 
+                        //no programmatically defined prepended filter mappings yet, prepend this one
+                        _matchBeforeIndex = 0;
+                        FilterMapping[] new_mappings = insertFilterMapping(mapping, 0, true);
+                        setFilterMappings(new_mappings);
+                    }
+                    else
+                    {
+                        FilterMapping[] new_mappings = insertFilterMapping(mapping,_matchBeforeIndex, false);
+                        ++_matchBeforeIndex;
+                        setFilterMappings(new_mappings);
+                    }
+                }
+                else
+                {
+                    //non programmatically defined, just prepend to list
+                    FilterMapping[] new_mappings = insertFilterMapping(mapping, 0, true);
+                    setFilterMappings(new_mappings);
+                }
+                
+                //adjust matchAfterIndex ptr to take account of the mapping we just prepended
+                if (_matchAfterIndex >= 0)
+                    ++_matchAfterIndex;
+            }
+        }
+    }
+    
+    
+    
+    /**
+     * Insert a filtermapping in the list
+     * @param mapping the FilterMapping to add
+     * @param pos the position in the existing arry at which to add it
+     * @param before if true, insert before  pos, if false insert after it
+     * @return
+     */
+    protected FilterMapping[] insertFilterMapping (FilterMapping mapping, int pos, boolean before)
+    {
+        if (pos < 0)
+            throw new IllegalArgumentException("FilterMapping insertion pos < 0");
+        FilterMapping[] mappings = getFilterMappings();
+        
+        if (mappings==null || mappings.length==0)
+        {
+            return new FilterMapping[] {mapping};
+        }
+        FilterMapping[] new_mappings = new FilterMapping[mappings.length+1];
+
+    
+        if (before)
+        {
+            //copy existing filter mappings up to but not including the pos
+            System.arraycopy(mappings,0,new_mappings,0,pos);
+
+            //add in the new mapping
+            new_mappings[pos] = mapping; 
+
+            //copy the old pos mapping and any remaining existing mappings
+            System.arraycopy(mappings,pos,new_mappings,pos+1, mappings.length-pos);
+
+        }
+        else
+        {
+            //copy existing filter mappings up to and including the pos
+            System.arraycopy(mappings,0,new_mappings,0,pos+1);
+            //add in the new mapping after the pos
+            new_mappings[pos+1] = mapping;   
+
+            //copy the remaining existing mappings
+            if (mappings.length > pos+1)
+                System.arraycopy(mappings,pos+1,new_mappings,pos+2, mappings.length-(pos+1));
+        }
+        return new_mappings;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    protected synchronized void updateNameMappings()
+    {
+        // update filter name map
+        _filterNameMap.clear();
+        if (_filters!=null)
+        {
+            for (FilterHolder filter : _filters)
+            {
+                _filterNameMap.put(filter.getName(), filter);
+                filter.setServletHandler(this);
+            }
+        }
+
+        // Map servlet names to holders
+        _servletNameMap.clear();
+        if (_servlets!=null)
+        {
+            // update the maps
+            for (ServletHolder servlet : _servlets)
+            {
+                _servletNameMap.put(servlet.getName(), servlet);
+                servlet.setServletHandler(this);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected synchronized void updateMappings()
+    {
+        // update filter mappings
+        if (_filterMappings==null)
+        {
+            _filterPathMappings=null;
+            _filterNameMappings=null;
+        }
+        else
+        {
+            _filterPathMappings=new ArrayList<>();
+            _filterNameMappings=new MultiMap<FilterMapping>();
+            for (FilterMapping filtermapping : _filterMappings)
+            {
+                FilterHolder filter_holder = _filterNameMap.get(filtermapping.getFilterName());
+                if (filter_holder == null)
+                    throw new IllegalStateException("No filter named " + filtermapping.getFilterName());
+                filtermapping.setFilterHolder(filter_holder);
+                if (filtermapping.getPathSpecs() != null)
+                    _filterPathMappings.add(filtermapping);
+
+                if (filtermapping.getServletNames() != null)
+                {
+                    String[] names = filtermapping.getServletNames();
+                    for (String name : names)
+                    {
+                        if (name != null)
+                            _filterNameMappings.add(name, filtermapping);
+                    }
+                }
+            }
+        }
+
+        // Map servlet paths to holders
+        if (_servletMappings==null || _servletNameMap==null)
+        {
+            _servletPathMap=null;
+        }
+        else
+        {
+            PathMap<ServletHolder> pm = new PathMap<>();
+            Map<String,ServletMapping> servletPathMappings = new HashMap<String,ServletMapping>();
+            
+            //create a map of paths to set of ServletMappings that define that mapping
+            HashMap<String, Set<ServletMapping>> sms = new HashMap<String, Set<ServletMapping>>();
+            for (ServletMapping servletMapping : _servletMappings)
+            {
+                String[] pathSpecs = servletMapping.getPathSpecs();
+                if (pathSpecs != null)
+                {
+                    for (String pathSpec : pathSpecs)
+                    {
+                        Set<ServletMapping> mappings = sms.get(pathSpec);
+                        if (mappings == null)
+                        {
+                            mappings = new HashSet<ServletMapping>();
+                            sms.put(pathSpec, mappings);
+                        }
+                        mappings.add(servletMapping);
+                    }
+                }
+            }
+         
+            //evaluate path to servlet map based on servlet mappings
+            for (String pathSpec : sms.keySet())
+            {
+                //for each path, look at the mappings where it is referenced
+                //if a mapping is for a servlet that is not enabled, skip it
+                Set<ServletMapping> mappings = sms.get(pathSpec);
+                
+                
+           
+                ServletMapping finalMapping = null;
+                for (ServletMapping mapping : mappings)
+                {
+                    //Get servlet associated with the mapping and check it is enabled
+                    ServletHolder servlet_holder = _servletNameMap.get(mapping.getServletName());
+                    if (servlet_holder == null)
+                        throw new IllegalStateException("No such servlet: " + mapping.getServletName());
+                    //if the servlet related to the mapping is not enabled, skip it from consideration
+                    if (!servlet_holder.isEnabled())
+                        continue;
+
+                    //only accept a default mapping if we don't have any other 
+                    if (finalMapping == null)
+                        finalMapping = mapping;
+                    else
+                    {
+                        //already have a candidate - only accept another one if the candidate is a default
+                        if (finalMapping.isDefault())
+                            finalMapping = mapping;
+                        else
+                        {
+                            //existing candidate isn't a default, if the one we're looking at isn't a default either, then its an error
+                            if (!mapping.isDefault())
+                                throw new IllegalStateException("Multiple servlets map to path: "+pathSpec+": "+finalMapping.getServletName()+","+mapping.getServletName());
+                        }
+                    }
+                }
+                if (finalMapping == null)
+                    throw new IllegalStateException ("No acceptable servlet mappings for "+pathSpec);
+           
+                if (LOG.isDebugEnabled()) LOG.debug("Chose path={} mapped to servlet={} from default={}", pathSpec, finalMapping.getServletName(), finalMapping.isDefault());
+               
+                servletPathMappings.put(pathSpec, finalMapping);
+                pm.put(pathSpec,_servletNameMap.get(finalMapping.getServletName()));
+            }
+     
+            _servletPathMap=pm;
+            _servletPathMappings=servletPathMappings;
+        }
+
+        // flush filter chain cache
+        if (_chainCache!=null)
+        {
+            for (int i=_chainCache.length;i-->0;)
+            {
+                if (_chainCache[i]!=null)
+                    _chainCache[i].clear();
+            }
+        }
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("filterNameMap="+_filterNameMap);
+            LOG.debug("pathFilters="+_filterPathMappings);
+            LOG.debug("servletFilterMap="+_filterNameMappings);
+            LOG.debug("servletPathMap="+_servletPathMap);
+            LOG.debug("servletNameMap="+_servletNameMap);
+        }
+
+        try
+        {
+            if (_contextHandler!=null && _contextHandler.isStarted() || _contextHandler==null && isStarted())
+                initialize();
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void notFound(Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        LOG.debug("Not Found {}",request.getRequestURI());
+        if (getHandler()!=null)
+            nextHandle(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()),baseRequest,request,response);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filterChainsCached The filterChainsCached to set.
+     */
+    public void setFilterChainsCached(boolean filterChainsCached)
+    {
+        _filterChainsCached = filterChainsCached;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filterMappings The filterMappings to set.
+     */
+    public void setFilterMappings(FilterMapping[] filterMappings)
+    {
+        updateBeans(_filterMappings,filterMappings);
+        _filterMappings = filterMappings;
+        updateMappings();
+        invalidateChainsCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void setFilters(FilterHolder[] holders)
+    {
+        if (holders!=null)
+            for (FilterHolder holder:holders)
+                holder.setServletHandler(this);
+        
+        updateBeans(_filters,holders);
+        _filters=holders;
+        updateNameMappings();
+        invalidateChainsCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletMappings The servletMappings to set.
+     */
+    public void setServletMappings(ServletMapping[] servletMappings)
+    {
+        updateBeans(_servletMappings,servletMappings);
+        _servletMappings = servletMappings;
+        updateMappings();
+        invalidateChainsCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set Servlets.
+     * @param holders Array of servlets to define
+     */
+    public synchronized void setServlets(ServletHolder[] holders)
+    {
+        if (holders!=null)
+            for (ServletHolder holder:holders)
+                holder.setServletHandler(this);
+        
+        updateBeans(_servlets,holders);
+        _servlets=holders;
+        updateNameMappings();
+        invalidateChainsCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class CachedChain implements FilterChain
+    {
+        FilterHolder _filterHolder;
+        CachedChain _next;
+        ServletHolder _servletHolder;
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @param filters list of {@link FilterHolder} objects
+         * @param servletHolder
+         */
+        CachedChain(List<FilterHolder> filters, ServletHolder servletHolder)
+        {
+            if (filters.size()>0)
+            {
+                _filterHolder=filters.get(0);
+                filters.remove(0);
+                _next=new CachedChain(filters,servletHolder);
+            }
+            else
+                _servletHolder=servletHolder;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public void doFilter(ServletRequest request, ServletResponse response)
+            throws IOException, ServletException
+        {
+            final Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
+
+            // pass to next filter
+            if (_filterHolder!=null)
+            {
+                LOG.debug("call filter {}", _filterHolder);
+                Filter filter= _filterHolder.getFilter();
+                if (_filterHolder.isAsyncSupported())
+                    filter.doFilter(request, response, _next);
+                else
+                {
+                    final boolean suspendable=baseRequest.isAsyncSupported();
+                    if (suspendable)
+                    {
+                        try
+                        {
+                            baseRequest.setAsyncSupported(false);
+                            filter.doFilter(request, response, _next);
+                        }
+                        finally
+                        {
+                            baseRequest.setAsyncSupported(true);
+                        }
+                    }
+                    else
+                        filter.doFilter(request, response, _next);
+                }
+                return;
+            }
+
+            // Call servlet
+            HttpServletRequest srequest = (HttpServletRequest)request;
+            if (_servletHolder == null)
+                notFound(baseRequest, srequest, (HttpServletResponse)response);
+            else
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("call servlet " + _servletHolder);
+                _servletHolder.handle(baseRequest,request, response);
+            }
+        }
+
+        @Override
+        public String toString()
+        {
+            if (_filterHolder!=null)
+                return _filterHolder+"->"+_next.toString();
+            if (_servletHolder!=null)
+                return _servletHolder.toString();
+            return "null";
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class Chain implements FilterChain
+    {
+        final Request _baseRequest;
+        final List<FilterHolder> _chain;
+        final ServletHolder _servletHolder;
+        int _filter= 0;
+
+        /* ------------------------------------------------------------ */
+        Chain(Request baseRequest, List<FilterHolder> filters, ServletHolder servletHolder)
+        {
+            _baseRequest=baseRequest;
+            _chain= filters;
+            _servletHolder= servletHolder;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public void doFilter(ServletRequest request, ServletResponse response)
+            throws IOException, ServletException
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("doFilter " + _filter);
+
+            // pass to next filter
+            if (_filter < _chain.size())
+            {
+                FilterHolder holder= _chain.get(_filter++);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("call filter " + holder);
+                Filter filter= holder.getFilter();
+
+                if (holder.isAsyncSupported() || !_baseRequest.isAsyncSupported())
+                {
+                    filter.doFilter(request, response, this);
+                }
+                else
+                {
+                    try
+                    {
+                        _baseRequest.setAsyncSupported(false);
+                        filter.doFilter(request, response, this);
+                    }
+                    finally
+                    {
+                        _baseRequest.setAsyncSupported(true);
+                    }
+                }
+
+                return;
+            }
+
+            // Call servlet
+            HttpServletRequest srequest = (HttpServletRequest)request;
+            if (_servletHolder == null)
+                notFound((request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest(), srequest, (HttpServletResponse)response);
+            else
+            {
+                LOG.debug("call servlet {}", _servletHolder);
+                _servletHolder.handle(_baseRequest,request, response);
+            }    
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString()
+        {
+            StringBuilder b = new StringBuilder();
+            for(FilterHolder f: _chain)
+            {
+                b.append(f.toString());
+                b.append("->");
+            }
+            b.append(_servletHolder);
+            return b.toString();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The maximum entries in a filter chain cache.
+     */
+    public int getMaxFilterChainsCacheSize()
+    {
+        return _maxFilterChainsCacheSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the maximum filter chain cache size.
+     * Filter chains are cached if {@link #isFilterChainsCached()} is true. If the max cache size
+     * is greater than zero, then the cache is flushed whenever it grows to be this size.
+     *
+     * @param maxFilterChainsCacheSize  the maximum number of entries in a filter chain cache.
+     */
+    public void setMaxFilterChainsCacheSize(int maxFilterChainsCacheSize)
+    {
+        _maxFilterChainsCacheSize = maxFilterChainsCacheSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    void destroyServlet(Servlet servlet)
+    {
+        if (_contextHandler!=null)
+            _contextHandler.destroyServlet(servlet);
+    }
+
+    /* ------------------------------------------------------------ */
+    void destroyFilter(Filter filter)
+    {
+        if (_contextHandler!=null)
+            _contextHandler.destroyFilter(filter);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class Default404Servlet extends HttpServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException
+        {
+            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/ServletHolder.java b/lib/jetty/org/eclipse/jetty/servlet/ServletHolder.java
new file mode 100644 (file)
index 0000000..1df08b3
--- /dev/null
@@ -0,0 +1,1124 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.SingleThreadModel;
+import javax.servlet.UnavailableException;
+
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.RunAsToken;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+
+/* --------------------------------------------------------------------- */
+/** Servlet Instance and Context Holder.
+ * Holds the name, params and some state of a javax.servlet.Servlet
+ * instance. It implements the ServletConfig interface.
+ * This class will organise the loading of the servlet when needed or
+ * requested.
+ *
+ *
+ */
+@ManagedObject("Servlet Holder")
+public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope, Comparable<ServletHolder>
+{
+    private static final Logger LOG = Log.getLogger(ServletHolder.class);
+
+    /* ---------------------------------------------------------------- */
+    private int _initOrder = -1;
+    private boolean _initOnStartup=false;
+    private Map<String, String> _roleMap;
+    private String _forcedPath;
+    private String _runAsRole;
+    private RunAsToken _runAsToken;
+    private IdentityService _identityService;
+    private ServletRegistration.Dynamic _registration;
+
+
+    private transient Servlet _servlet;
+    private transient Config _config;
+    private transient long _unavailable;
+    private transient boolean _enabled = true;
+    private transient UnavailableException _unavailableEx;
+
+    public static final  String JSP_GENERATED_PACKAGE_NAME = "org.eclipse.jetty.servlet.jspPackagePrefix";
+    public static final Map<String,String> NO_MAPPED_ROLES = Collections.emptyMap();
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor .
+     */
+    public ServletHolder()
+    {
+        this(Source.EMBEDDED);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor .
+     */
+    public ServletHolder(Holder.Source creator)
+    {
+        super(creator);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor for existing servlet.
+     */
+    public ServletHolder(Servlet servlet)
+    {
+        this(Source.EMBEDDED);
+        setServlet(servlet);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor for servlet class.
+     */
+    public ServletHolder(String name, Class<? extends Servlet> servlet)
+    {
+        this(Source.EMBEDDED);
+        setName(name);
+        setHeldClass(servlet);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor for servlet class.
+     */
+    public ServletHolder(String name, Servlet servlet)
+    {
+        this(Source.EMBEDDED);
+        setName(name);
+        setServlet(servlet);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor for servlet class.
+     */
+    public ServletHolder(Class<? extends Servlet> servlet)
+    {
+        this(Source.EMBEDDED);
+        setHeldClass(servlet);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /**
+     * @return The unavailable exception or null if not unavailable
+     */
+    public UnavailableException getUnavailableException()
+    {
+        return _unavailableEx;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void setServlet(Servlet servlet)
+    {
+        if (servlet==null || servlet instanceof SingleThreadModel)
+            throw new IllegalArgumentException();
+
+        _extInstance=true;
+        _servlet=servlet;
+        setHeldClass(servlet.getClass());
+        if (getName()==null)
+            setName(servlet.getClass().getName()+"-"+super.hashCode());
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute(value="initialization order", readonly=true)
+    public int getInitOrder()
+    {
+        return _initOrder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the initialize order.
+     * Holders with order<0, are initialized on use. Those with
+     * order>=0 are initialized in increasing order when the handler
+     * is started.
+     */
+    public void setInitOrder(int order)
+    {
+        _initOnStartup=order>=0;
+        _initOrder = order;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Comparitor by init order.
+     */
+    @Override
+    public int compareTo(ServletHolder sh)
+    {
+        if (sh==this)
+            return 0;
+        if (sh._initOrder<_initOrder)
+            return 1;
+        if (sh._initOrder>_initOrder)
+            return -1;
+
+        int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0;
+        if (c==0)
+            c=_name.compareTo(sh._name);
+            return c;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean equals(Object o)
+    {
+        return o instanceof ServletHolder && compareTo((ServletHolder)o)==0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int hashCode()
+    {
+        return _name==null?System.identityHashCode(this):_name.hashCode();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Link a user role.
+     * Translate the role name used by a servlet, to the link name
+     * used by the container.
+     * @param name The role name as used by the servlet
+     * @param link The role name as used by the container.
+     */
+    public synchronized void setUserRoleLink(String name,String link)
+    {
+        if (_roleMap==null)
+            _roleMap=new HashMap<String, String>();
+        _roleMap.put(name,link);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** get a user role link.
+     * @param name The name of the role
+     * @return The name as translated by the link. If no link exists,
+     * the name is returned.
+     */
+    public String getUserRoleLink(String name)
+    {
+        if (_roleMap==null)
+            return name;
+        String link= _roleMap.get(name);
+        return (link==null)?name:link;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the forcedPath.
+     */
+    @ManagedAttribute(value="forced servlet path", readonly=true)
+    public String getForcedPath()
+    {
+        return _forcedPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forcedPath The forcedPath to set.
+     */
+    public void setForcedPath(String forcedPath)
+    {
+        _forcedPath = forcedPath;
+    }
+
+    public boolean isEnabled()
+    {
+        return _enabled;
+    }
+
+
+    public void setEnabled(boolean enabled)
+    {
+        _enabled = enabled;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    public void doStart()
+        throws Exception
+    {
+        _unavailable=0;
+        if (!_enabled)
+            return;
+        
+        // Handle JSP file forced paths
+        if (_forcedPath != null)
+        {
+            // Look for a precompiled JSP Servlet
+            String precompiled=getClassNameForJsp(_forcedPath);
+            LOG.debug("Checking for precompiled servlet {} for jsp {}", precompiled, _forcedPath);
+            ServletHolder jsp=getServletHandler().getServlet(precompiled);
+            if (jsp!=null)
+            {
+                LOG.debug("JSP file {} for {} mapped to Servlet {}",_forcedPath, getName(),jsp.getClassName());
+                // set the className for this servlet to the precompiled one
+                setClassName(jsp.getClassName());
+            }
+            else
+            { 
+                if (getClassName() == null)
+                {
+                    // Look for normal JSP servlet
+                    jsp=getServletHandler().getServlet("jsp");
+                    if (jsp!=null)
+                    {
+                        LOG.debug("JSP file {} for {} mapped to Servlet {}",_forcedPath, getName(),jsp.getClassName());
+                        setClassName(jsp.getClassName());
+                        //copy jsp init params that don't exist for this servlet
+                        for (Map.Entry<String, String> entry:jsp.getInitParameters().entrySet())
+                        {
+                            if (!_initParams.containsKey(entry.getKey()))
+                                setInitParameter(entry.getKey(), entry.getValue());
+                        }
+                        //jsp specific: set up the jsp-file on the JspServlet so it can precompile iff load-on-startup is >=0
+                        if (_initOnStartup)
+                            setInitParameter("jspFile", _forcedPath);
+                    }                       
+                }
+            }
+        }
+        
+        
+        //check servlet has a class (ie is not a preliminary registration). If preliminary, fail startup.
+        try
+        {
+            super.doStart();
+        }
+        catch (UnavailableException ue)
+        {
+            makeUnavailable(ue);
+            if (_servletHandler.isStartWithUnavailable())
+            {
+                LOG.ignore(ue);
+                return;
+            }
+            else
+                throw ue;
+        }
+
+
+        //servlet is not an instance of javax.servlet.Servlet
+        try
+        {
+            checkServletType();
+        }
+        catch (UnavailableException ue)
+        {
+            makeUnavailable(ue);
+            if (_servletHandler.isStartWithUnavailable())
+            {
+                LOG.ignore(ue);
+                return;
+            }
+            else
+                throw ue;
+        }
+
+        //check if we need to forcibly set load-on-startup
+        checkInitOnStartup();
+
+        _identityService = _servletHandler.getIdentityService();
+        if (_identityService!=null && _runAsRole!=null)
+            _runAsToken=_identityService.newRunAsToken(_runAsRole);
+
+        _config=new Config();
+
+        if (_class!=null && javax.servlet.SingleThreadModel.class.isAssignableFrom(_class))
+            _servlet = new SingleThreadedWrapper();
+     
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void initialize ()
+    throws Exception
+    {
+        super.initialize();
+        if (_extInstance || _initOnStartup)
+        {
+            try
+            {
+                initServlet();
+            }
+            catch(Exception e)
+            {
+                if (_servletHandler.isStartWithUnavailable())
+                    LOG.ignore(e);
+                else
+                    throw e;
+            }
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public void doStop()
+        throws Exception
+    {
+        Object old_run_as = null;
+        if (_servlet!=null)
+        {
+            try
+            {
+                if (_identityService!=null)
+                    old_run_as=_identityService.setRunAs(_identityService.getSystemUserIdentity(),_runAsToken);
+
+                destroyInstance(_servlet);
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+            finally
+            {
+                if (_identityService!=null)
+                    _identityService.unsetRunAs(old_run_as);
+            }
+        }
+
+        if (!_extInstance)
+            _servlet=null;
+
+        _config=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroyInstance (Object o)
+    throws Exception
+    {
+        if (o==null)
+            return;
+        Servlet servlet =  ((Servlet)o);
+        getServletHandler().destroyServlet(servlet);
+        servlet.destroy();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the servlet.
+     * @return The servlet
+     */
+    public synchronized Servlet getServlet()
+        throws ServletException
+    {
+        // Handle previous unavailability
+        if (_unavailable!=0)
+        {
+            if (_unavailable<0 || _unavailable>0 && System.currentTimeMillis()<_unavailable)
+                throw _unavailableEx;
+            _unavailable=0;
+            _unavailableEx=null;
+        }
+
+        if (_servlet==null)
+            initServlet();
+        return _servlet;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the servlet instance (no initialization done).
+     * @return The servlet or null
+     */
+    public Servlet getServletInstance()
+    {
+        return _servlet;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Check to ensure class of servlet is acceptable.
+     * @throws UnavailableException
+     */
+    public void checkServletType ()
+        throws UnavailableException
+    {
+        if (_class==null || !javax.servlet.Servlet.class.isAssignableFrom(_class))
+        {
+            throw new UnavailableException("Servlet "+_class+" is not a javax.servlet.Servlet");
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the holder is started and is not unavailable
+     */
+    public boolean isAvailable()
+    {
+        if (isStarted()&& _unavailable==0)
+            return true;
+        try
+        {
+            getServlet();
+        }
+        catch(Exception e)
+        {
+            LOG.ignore(e);
+        }
+
+        return isStarted()&& _unavailable==0;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Check if there is a javax.servlet.annotation.ServletSecurity
+     * annotation on the servlet class. If there is, then we force
+     * it to be loaded on startup, because all of the security 
+     * constraints must be calculated as the container starts.
+     * 
+     */
+    private void checkInitOnStartup()
+    {
+        if (_class==null)
+            return;
+        
+        if ((_class.getAnnotation(javax.servlet.annotation.ServletSecurity.class) != null) && !_initOnStartup)
+            setInitOrder(Integer.MAX_VALUE);    
+    }
+
+    /* ------------------------------------------------------------ */
+    private void makeUnavailable(UnavailableException e)
+    {
+        if (_unavailableEx==e && _unavailable!=0)
+            return;
+
+        _servletHandler.getServletContext().log("unavailable",e);
+
+        _unavailableEx=e;
+        _unavailable=-1;
+        if (e.isPermanent())
+            _unavailable=-1;
+        else
+        {
+            if (_unavailableEx.getUnavailableSeconds()>0)
+                _unavailable=System.currentTimeMillis()+1000*_unavailableEx.getUnavailableSeconds();
+            else
+                _unavailable=System.currentTimeMillis()+5000; // TODO configure
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+
+    private void makeUnavailable(final Throwable e)
+    {
+        if (e instanceof UnavailableException)
+            makeUnavailable((UnavailableException)e);
+        else
+        {
+            ServletContext ctx = _servletHandler.getServletContext();
+            if (ctx==null)
+                LOG.info("unavailable",e);
+            else
+                ctx.log("unavailable",e);
+            _unavailableEx=new UnavailableException(String.valueOf(e),-1)
+            {
+                {
+                    initCause(e);
+                }
+            };
+            _unavailable=-1;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void initServlet()
+       throws ServletException
+    {
+        Object old_run_as = null;
+        try
+        {
+            if (_servlet==null)
+                _servlet=newInstance();
+            if (_config==null)
+                _config=new Config();
+            
+          
+
+            // Handle run as
+            if (_identityService!=null)
+            {
+                old_run_as=_identityService.setRunAs(_identityService.getSystemUserIdentity(),_runAsToken);
+            }
+
+            // Handle configuring servlets that implement org.apache.jasper.servlet.JspServlet
+            if (isJspServlet())
+            {
+                initJspServlet();
+            }
+
+            initMultiPart();
+
+            LOG.debug("Filter.init {}",_servlet);
+            _servlet.init(_config);
+        }
+        catch (UnavailableException e)
+        {
+            makeUnavailable(e);
+            _servlet=null;
+            _config=null;
+            throw e;
+        }
+        catch (ServletException e)
+        {
+            makeUnavailable(e.getCause()==null?e:e.getCause());
+            _servlet=null;
+            _config=null;
+            throw e;
+        }
+        catch (Exception e)
+        {
+            makeUnavailable(e);
+            _servlet=null;
+            _config=null;
+            throw new ServletException(this.toString(),e);
+        }
+        finally
+        {
+            // pop run-as role
+            if (_identityService!=null)
+                _identityService.unsetRunAs(old_run_as);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @throws Exception
+     */
+    protected void initJspServlet () throws Exception
+    {
+        ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext());
+            
+        /* Set the webapp's classpath for Jasper */
+        ch.setAttribute("org.apache.catalina.jsp_classpath", ch.getClassPath());
+
+        /* Set the system classpath for Jasper */
+        setInitParameter("com.sun.appserv.jsp.classpath", Loader.getClassPath(ch.getClassLoader().getParent()));
+
+        /* Set up other classpath attribute */
+        if ("?".equals(getInitParameter("classpath")))
+        {
+            String classpath = ch.getClassPath();
+            LOG.debug("classpath=" + classpath);
+            if (classpath != null)
+                setInitParameter("classpath", classpath);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Register a ServletRequestListener that will ensure tmp multipart
+     * files are deleted when the request goes out of scope.
+     * 
+     * @throws Exception
+     */
+    protected void initMultiPart () throws Exception
+    {
+        //if this servlet can handle multipart requests, ensure tmp files will be
+        //cleaned up correctly
+        if (((Registration)getRegistration()).getMultipartConfig() != null)
+        {
+            //Register a listener to delete tmp files that are created as a result of this
+            //servlet calling Request.getPart() or Request.getParts()
+
+            ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext());
+            ch.addEventListener(new Request.MultiPartCleanerListener());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.UserIdentity.Scope#getContextPath()
+     */
+    @Override
+    public String getContextPath()
+    {
+        return _config.getServletContext().getContextPath();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.UserIdentity.Scope#getRoleRefMap()
+     */
+    @Override
+    public Map<String, String> getRoleRefMap()
+    {
+        return _roleMap;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute(value="role to run servlet as", readonly=true)
+    public String getRunAsRole()
+    {
+        return _runAsRole;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRunAsRole(String role)
+    {
+        _runAsRole = role;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Service a request with this servlet.
+     */
+    public void handle(Request baseRequest,
+                       ServletRequest request,
+                       ServletResponse response)
+        throws ServletException,
+               UnavailableException,
+               IOException
+    {
+        if (_class==null)
+            throw new UnavailableException("Servlet Not Initialized");
+
+        Servlet servlet=_servlet;
+        synchronized(this)
+        {
+            if (!isStarted())
+                throw new UnavailableException("Servlet not initialized", -1);
+            if (_unavailable!=0 || (!_initOnStartup && servlet==null))
+                servlet=getServlet();
+            if (servlet==null)
+                throw new UnavailableException("Could not instantiate "+_class);
+        }
+
+        // Service the request
+        boolean servlet_error=true;
+        Object old_run_as = null;
+        boolean suspendable = baseRequest.isAsyncSupported();
+        try
+        {
+            // Handle aliased path
+            if (_forcedPath!=null)
+                // TODO complain about poor naming to the Jasper folks
+                request.setAttribute("org.apache.catalina.jsp_file",_forcedPath);
+
+            // Handle run as
+            if (_identityService!=null)
+                old_run_as=_identityService.setRunAs(baseRequest.getResolvedUserIdentity(),_runAsToken);
+
+            if (!isAsyncSupported())
+                baseRequest.setAsyncSupported(false);
+
+            MultipartConfigElement mpce = ((Registration)getRegistration()).getMultipartConfig();
+            if (mpce != null)
+                request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
+
+            servlet.service(request,response);
+            servlet_error=false;
+        }
+        catch(UnavailableException e)
+        {
+            makeUnavailable(e);
+            throw _unavailableEx;
+        }
+        finally
+        {
+            baseRequest.setAsyncSupported(suspendable);
+
+            // pop run-as role
+            if (_identityService!=null)
+                _identityService.unsetRunAs(old_run_as);
+
+            // Handle error params.
+            if (servlet_error)
+                request.setAttribute("javax.servlet.error.servlet_name",getName());
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    private boolean isJspServlet ()
+    {
+        if (_servlet == null)
+            return false;
+
+        Class c = _servlet.getClass();
+
+        boolean result = false;
+        while (c != null && !result)
+        {
+            result = isJspServlet(c.getName());
+            c = c.getSuperclass();
+        }
+
+        return result;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    private boolean isJspServlet (String classname)
+    {
+        if (classname == null)
+            return false;
+        return ("org.apache.jasper.servlet.JspServlet".equals(classname));
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private String getNameOfJspClass (String jsp)
+    {
+        if (jsp == null)
+            return "";
+        
+        int i = jsp.lastIndexOf('/') + 1;
+        jsp = jsp.substring(i);
+        try
+        {
+            Class jspUtil = Loader.loadClass(Holder.class, "org.apache.jasper.compiler.JspUtil");
+            Method makeJavaIdentifier = jspUtil.getMethod("makeJavaIdentifier", String.class);
+            return (String)makeJavaIdentifier.invoke(null, jsp);
+        }
+        catch (Exception e)
+        {
+            String tmp = jsp.replace('.','_');
+            LOG.warn("Unable to make identifier for jsp "+jsp +" trying "+tmp+" instead");
+            if (LOG.isDebugEnabled())
+                LOG.warn(e);
+            return tmp;
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private String getPackageOfJspClass (String jsp)
+    {
+        if (jsp == null)
+            return "";
+        
+        int i = jsp.lastIndexOf('/');
+        if (i <= 0)
+            return "";
+        try
+        {
+            Class jspUtil = Loader.loadClass(Holder.class, "org.apache.jasper.compiler.JspUtil");
+            Method makeJavaPackage = jspUtil.getMethod("makeJavaPackage", String.class);
+            return (String)makeJavaPackage.invoke(null, jsp.substring(0,i));
+        } 
+        catch (Exception e)
+        {
+            String tmp = jsp.substring(1).replace('/','.');
+            LOG.warn("Unable to make package for jsp "+jsp +" trying "+tmp+" instead");
+            if (LOG.isDebugEnabled())
+                LOG.warn(e);
+            return tmp;
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private String getJspPackagePrefix ()
+    {
+        String jspPackageName = (String)getServletHandler().getServletContext().getInitParameter(JSP_GENERATED_PACKAGE_NAME );
+        if (jspPackageName == null)
+            jspPackageName = "org.apache.jsp";
+        
+        return jspPackageName;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private String getClassNameForJsp (String jsp)
+    {
+        if (jsp == null)
+            return null; 
+        
+        return getJspPackagePrefix() + "." +getPackageOfJspClass(jsp) + "." + getNameOfJspClass(jsp);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected class Config extends HolderConfig implements ServletConfig
+    {
+        /* -------------------------------------------------------- */
+        @Override
+        public String getServletName()
+        {
+            return getName();
+        }
+
+    }
+
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    public class Registration extends HolderRegistration implements ServletRegistration.Dynamic
+    {
+        protected MultipartConfigElement _multipartConfig;
+
+        @Override
+        public Set<String> addMapping(String... urlPatterns)
+        {
+            illegalStateIfContextStarted();
+            Set<String> clash=null;
+            for (String pattern : urlPatterns)
+            {
+                ServletMapping mapping = _servletHandler.getServletMapping(pattern);
+                if (mapping!=null)
+                {
+                    //if the servlet mapping was from a default descriptor, then allow it to be overridden
+                    if (!mapping.isDefault())
+                    {
+                        if (clash==null)
+                            clash=new HashSet<String>();
+                        clash.add(pattern);
+                    }
+                }
+            }
+
+            //if there were any clashes amongst the urls, return them
+            if (clash!=null)
+                return clash;
+
+            //otherwise apply all of them
+            ServletMapping mapping = new ServletMapping();
+            mapping.setServletName(ServletHolder.this.getName());
+            mapping.setPathSpecs(urlPatterns);
+            _servletHandler.addServletMapping(mapping);
+
+            return Collections.emptySet();
+        }
+
+        @Override
+        public Collection<String> getMappings()
+        {
+            ServletMapping[] mappings =_servletHandler.getServletMappings();
+            List<String> patterns=new ArrayList<String>();
+            if (mappings!=null)
+            {
+                for (ServletMapping mapping : mappings)
+                {
+                    if (!mapping.getServletName().equals(getName()))
+                        continue;
+                    String[] specs=mapping.getPathSpecs();
+                    if (specs!=null && specs.length>0)
+                        patterns.addAll(Arrays.asList(specs));
+                }
+            }
+            return patterns;
+        }
+
+        @Override
+        public String getRunAsRole()
+        {
+            return _runAsRole;
+        }
+
+        @Override
+        public void setLoadOnStartup(int loadOnStartup)
+        {
+            illegalStateIfContextStarted();
+            ServletHolder.this.setInitOrder(loadOnStartup);
+        }
+
+        public int getInitOrder()
+        {
+            return ServletHolder.this.getInitOrder();
+        }
+
+        @Override
+        public void setMultipartConfig(MultipartConfigElement element)
+        {
+            _multipartConfig = element;
+        }
+
+        public MultipartConfigElement getMultipartConfig()
+        {
+            return _multipartConfig;
+        }
+
+        @Override
+        public void setRunAsRole(String role)
+        {
+            _runAsRole = role;
+        }
+
+        @Override
+        public Set<String> setServletSecurity(ServletSecurityElement securityElement)
+        {
+            return _servletHandler.setServletSecurity(this, securityElement);
+        }
+    }
+
+    public ServletRegistration.Dynamic getRegistration()
+    {
+        if (_registration == null)
+            _registration = new Registration();
+        return _registration;
+    }
+
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    private class SingleThreadedWrapper implements Servlet
+    {
+        Stack<Servlet> _stack=new Stack<Servlet>();
+
+        @Override
+        public void destroy()
+        {
+            synchronized(this)
+            {
+                while(_stack.size()>0)
+                    try { (_stack.pop()).destroy(); } catch (Exception e) { LOG.warn(e); }
+            }
+        }
+
+        @Override
+        public ServletConfig getServletConfig()
+        {
+            return _config;
+        }
+
+        @Override
+        public String getServletInfo()
+        {
+            return null;
+        }
+
+        @Override
+        public void init(ServletConfig config) throws ServletException
+        {
+            synchronized(this)
+            {
+                if(_stack.size()==0)
+                {
+                    try
+                    {
+                        Servlet s = newInstance();
+                        s.init(config);
+                        _stack.push(s);
+                    }
+                    catch (ServletException e)
+                    {
+                        throw e;
+                    }
+                    catch (Exception e)
+                    {
+                        throw new ServletException(e);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
+        {
+            Servlet s;
+            synchronized(this)
+            {
+                if(_stack.size()>0)
+                    s=(Servlet)_stack.pop();
+                else
+                {
+                    try
+                    {
+                        s = newInstance();
+                        s.init(_config);
+                    }
+                    catch (ServletException e)
+                    {
+                        throw e;
+                    }
+                    catch (Exception e)
+                    {
+                        throw new ServletException(e);
+                    }
+                }
+            }
+
+            try
+            {
+                s.service(req,res);
+            }
+            finally
+            {
+                synchronized(this)
+                {
+                    _stack.push(s);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the newly created Servlet instance
+     * @throws ServletException
+     * @throws IllegalAccessException
+     * @throws InstantiationException
+     */
+    protected Servlet newInstance() throws ServletException, IllegalAccessException, InstantiationException
+    {
+        try
+        {
+            ServletContext ctx = getServletHandler().getServletContext();
+            if (ctx instanceof ServletContextHandler.Context)
+                return ((ServletContextHandler.Context)ctx).createServlet(getHeldClass());
+            return getHeldClass().newInstance();
+        }
+        catch (ServletException se)
+        {
+            Throwable cause = se.getRootCause();
+            if (cause instanceof InstantiationException)
+                throw (InstantiationException)cause;
+            if (cause instanceof IllegalAccessException)
+                throw (IllegalAccessException)cause;
+            throw se;
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x==%s,%d,%b",_name,hashCode(),_className,_initOrder,_servlet!=null);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/ServletMapping.java b/lib/jetty/org/eclipse/jetty/servlet/ServletMapping.java
new file mode 100644 (file)
index 0000000..df026df
--- /dev/null
@@ -0,0 +1,119 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+@ManagedObject("Servlet Mapping")
+public class ServletMapping
+{
+    private String[] _pathSpecs;
+    private String _servletName;
+    private boolean _default;
+    
+
+    /* ------------------------------------------------------------ */
+    public ServletMapping()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the pathSpecs.
+     */
+    @ManagedAttribute(value="url patterns", readonly=true)
+    public String[] getPathSpecs()
+    {
+        return _pathSpecs;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletName.
+     */
+    @ManagedAttribute(value="servlet name", readonly=true)
+    public String getServletName()
+    {
+        return _servletName;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpecs The pathSpecs to set.
+     */
+    public void setPathSpecs(String[] pathSpecs)
+    {
+        _pathSpecs = pathSpecs;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpec The pathSpec to set.
+     */
+    public void setPathSpec(String pathSpec)
+    {
+        _pathSpecs = new String[]{pathSpec};
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletName The servletName to set.
+     */
+    public void setServletName(String servletName)
+    {
+        _servletName = servletName;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return
+     */
+    @ManagedAttribute(value="default", readonly=true)
+    public boolean isDefault()
+    {
+        return _default;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param fromDefault
+     */
+    public void setDefault(boolean fromDefault)
+    {
+        _default = fromDefault;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toString()
+    {
+        return (_pathSpecs==null?"[]":Arrays.asList(_pathSpecs).toString())+"=>"+_servletName; 
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        out.append(String.valueOf(this)).append("\n");
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/ServletTester.java b/lib/jetty/org/eclipse/jetty/servlet/ServletTester.java
new file mode 100644 (file)
index 0000000..2317195
--- /dev/null
@@ -0,0 +1,231 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.Map;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.resource.Resource;
+
+public class ServletTester extends ContainerLifeCycle
+{
+    private final Server _server=new Server();
+    private final LocalConnector _connector=new LocalConnector(_server);
+    private final ServletContextHandler _context;
+    
+    public Server getServer()
+    {
+        return _server;
+    }
+    
+    public LocalConnector getConnector()
+    {
+        return _connector;
+    }
+    
+    public void setVirtualHosts(String[] vhosts)
+    {
+        _context.setVirtualHosts(vhosts);
+    }
+
+    public void addVirtualHosts(String[] virtualHosts)
+    {
+        _context.addVirtualHosts(virtualHosts);
+    }
+
+    public ServletHolder addServlet(String className, String pathSpec)
+    {
+        return _context.addServlet(className,pathSpec);
+    }
+
+    public ServletHolder addServlet(Class<? extends Servlet> servlet, String pathSpec)
+    {
+        return _context.addServlet(servlet,pathSpec);
+    }
+
+    public void addServlet(ServletHolder servlet, String pathSpec)
+    {
+        _context.addServlet(servlet,pathSpec);
+    }
+
+    public void addFilter(FilterHolder holder, String pathSpec, EnumSet<DispatcherType> dispatches)
+    {
+        _context.addFilter(holder,pathSpec,dispatches);
+    }
+
+    public FilterHolder addFilter(Class<? extends Filter> filterClass, String pathSpec, EnumSet<DispatcherType> dispatches)
+    {
+        return _context.addFilter(filterClass,pathSpec,dispatches);
+    }
+
+    public FilterHolder addFilter(String filterClass, String pathSpec, EnumSet<DispatcherType> dispatches)
+    {
+        return _context.addFilter(filterClass,pathSpec,dispatches);
+    }
+
+    public Object getAttribute(String name)
+    {
+        return _context.getAttribute(name);
+    }
+
+    public Enumeration getAttributeNames()
+    {
+        return _context.getAttributeNames();
+    }
+
+    public Attributes getAttributes()
+    {
+        return _context.getAttributes();
+    }
+
+    public String getContextPath()
+    {
+        return _context.getContextPath();
+    }
+
+    public String getInitParameter(String name)
+    {
+        return _context.getInitParameter(name);
+    }
+
+    public String setInitParameter(String name, String value)
+    {
+        return _context.setInitParameter(name,value);
+    }
+
+    public Enumeration getInitParameterNames()
+    {
+        return _context.getInitParameterNames();
+    }
+
+    public Map<String, String> getInitParams()
+    {
+        return _context.getInitParams();
+    }
+
+    public void removeAttribute(String name)
+    {
+        _context.removeAttribute(name);
+    }
+
+    public void setAttribute(String name, Object value)
+    {
+        _context.setAttribute(name,value);
+    }
+
+    public void setContextPath(String contextPath)
+    {
+        _context.setContextPath(contextPath);
+    }
+
+    public Resource getBaseResource()
+    {
+        return _context.getBaseResource();
+    }
+
+    public String getResourceBase()
+    {
+        return _context.getResourceBase();
+    }
+
+    public void setResourceBase(String resourceBase)
+    {
+        _context.setResourceBase(resourceBase);
+    }
+
+    private final ServletHandler _handler;
+
+    public ServletTester()
+    {
+        this("/",ServletContextHandler.SECURITY|ServletContextHandler.SESSIONS);
+    }
+
+    public ServletTester(String ctxPath)
+    {
+        this(ctxPath,ServletContextHandler.SECURITY|ServletContextHandler.SESSIONS);
+    }
+
+    public ServletTester(String contextPath,int options)
+    {
+        _context=new ServletContextHandler(_server,contextPath,options);
+        _handler=_context.getServletHandler();
+        _server.setConnectors(new Connector[]{_connector});
+        addBean(_server);
+    }
+
+    public ServletContextHandler getContext()
+    {
+        return _context;
+    }
+
+    public String getResponses(String request) throws Exception
+    {
+        return _connector.getResponses(request);
+    }
+
+    public ByteBuffer getResponses(ByteBuffer request) throws Exception
+    {
+        return _connector.getResponses(request);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Create a port based connector.
+     * This methods adds a port connector to the server
+     * @return A URL to access the server via the connector.
+     * @throws Exception
+     */
+    public String createConnector(boolean localhost) throws Exception
+    {
+        ServerConnector connector = new ServerConnector(_server);
+        if (localhost)
+            connector.setHost("127.0.0.1");
+        _server.addConnector(connector);
+        if (_server.isStarted())
+            connector.start();
+        else
+            connector.open();
+
+        return "http://"+(localhost?"127.0.0.1":
+            InetAddress.getLocalHost().getHostAddress()
+        )+":"+connector.getLocalPort();
+    }
+
+    public LocalConnector createLocalConnector()
+    {
+        LocalConnector connector = new LocalConnector(_server);
+        _server.addConnector(connector);
+        return connector;
+    }
+
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/StatisticsServlet.java b/lib/jetty/org/eclipse/jetty/servlet/StatisticsServlet.java
new file mode 100644 (file)
index 0000000..6d1bf7d
--- /dev/null
@@ -0,0 +1,290 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.AbstractConnector;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.ConnectorStatistics;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.StatisticsHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * StatisticsServlet
+ *
+ *
+ */
+public class StatisticsServlet extends HttpServlet
+{
+    private static final Logger LOG = Log.getLogger(StatisticsServlet.class);
+
+    boolean _restrictToLocalhost = true; // defaults to true
+    private StatisticsHandler _statsHandler;
+    private MemoryMXBean _memoryBean;
+    private Connector[] _connectors;
+
+    
+    
+    /** 
+     * @see javax.servlet.GenericServlet#init()
+     */
+    public void init() throws ServletException
+    {
+        ServletContext context = getServletContext();
+        ContextHandler.Context scontext = (ContextHandler.Context) context;
+        Server _server = scontext.getContextHandler().getServer();
+
+        Handler handler = _server.getChildHandlerByClass(StatisticsHandler.class);
+
+        if (handler != null)
+        {
+            _statsHandler = (StatisticsHandler) handler;
+        }
+        else
+        {
+            LOG.warn("Statistics Handler not installed!");
+            return;
+        }
+        
+        _memoryBean = ManagementFactory.getMemoryMXBean();
+        _connectors = _server.getConnectors();
+
+        if (getInitParameter("restrictToLocalhost") != null)
+        {
+            _restrictToLocalhost = "true".equals(getInitParameter("restrictToLocalhost"));
+        }
+    }
+
+    
+    
+    /** 
+     * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    public void doPost(HttpServletRequest sreq, HttpServletResponse sres) throws ServletException, IOException
+    {
+        doGet(sreq, sres);
+    }
+
+    
+    
+    /** 
+     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+    {
+        if (_statsHandler == null)
+        {
+            LOG.warn("Statistics Handler not installed!");
+            resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+            return;
+        }
+        if (_restrictToLocalhost)
+        {
+            if (!isLoopbackAddress(req.getRemoteAddr()))
+            {
+                resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+                return;
+            }
+        }
+
+        String wantXml = req.getParameter("xml");
+        if (wantXml == null)
+          wantXml = req.getParameter("XML");
+
+        if (wantXml != null && "true".equalsIgnoreCase(wantXml))
+        {
+            sendXmlResponse(resp);
+        }
+        else
+        {
+            sendTextResponse(resp);
+        }
+
+    }
+
+    private boolean isLoopbackAddress(String address)
+    {
+        try
+        {
+            InetAddress addr = InetAddress.getByName(address); 
+            return addr.isLoopbackAddress();
+        }
+        catch (UnknownHostException e )
+        {
+            LOG.warn("Warning: attempt to access statistics servlet from " + address, e);
+            return false;
+        }
+    }
+
+    private void sendXmlResponse(HttpServletResponse response) throws IOException
+    {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("<statistics>\n");
+
+        sb.append("  <requests>\n");
+        sb.append("    <statsOnMs>").append(_statsHandler.getStatsOnMs()).append("</statsOnMs>\n");
+        
+        sb.append("    <requests>").append(_statsHandler.getRequests()).append("</requests>\n");
+        sb.append("    <requestsActive>").append(_statsHandler.getRequestsActive()).append("</requestsActive>\n");
+        sb.append("    <requestsActiveMax>").append(_statsHandler.getRequestsActiveMax()).append("</requestsActiveMax>\n");
+        sb.append("    <requestsTimeTotal>").append(_statsHandler.getRequestTimeTotal()).append("</requestsTimeTotal>\n");
+        sb.append("    <requestsTimeMean>").append(_statsHandler.getRequestTimeMean()).append("</requestsTimeMean>\n");
+        sb.append("    <requestsTimeMax>").append(_statsHandler.getRequestTimeMax()).append("</requestsTimeMax>\n");
+        sb.append("    <requestsTimeStdDev>").append(_statsHandler.getRequestTimeStdDev()).append("</requestsTimeStdDev>\n");
+
+        sb.append("    <dispatched>").append(_statsHandler.getDispatched()).append("</dispatched>\n");
+        sb.append("    <dispatchedActive>").append(_statsHandler.getDispatchedActive()).append("</dispatchedActive>\n");
+        sb.append("    <dispatchedActiveMax>").append(_statsHandler.getDispatchedActiveMax()).append("</dispatchedActiveMax>\n");
+        sb.append("    <dispatchedTimeTotalMs>").append(_statsHandler.getDispatchedTimeTotal()).append("</dispatchedTimeTotalMs>\n");
+        sb.append("    <dispatchedTimeMeanMs>").append(_statsHandler.getDispatchedTimeMean()).append("</dispatchedTimeMeanMs>\n");
+        sb.append("    <dispatchedTimeMaxMs>").append(_statsHandler.getDispatchedTimeMax()).append("</dispatchedTimeMaxMs>\n");
+        sb.append("    <dispatchedTimeStdDevMs>").append(_statsHandler.getDispatchedTimeStdDev()).append("</dispatchedTimeStdDevMs>\n");
+        sb.append("    <asyncRequests>").append(_statsHandler.getAsyncRequests()).append("</asyncRequests>\n");
+        sb.append("    <requestsSuspended>").append(_statsHandler.getAsyncRequestsWaiting()).append("</requestsSuspended>\n");
+        sb.append("    <requestsSuspendedMax>").append(_statsHandler.getAsyncRequestsWaitingMax()).append("</requestsSuspendedMax>\n");
+        sb.append("    <requestsResumed>").append(_statsHandler.getAsyncDispatches()).append("</requestsResumed>\n");
+        sb.append("    <requestsExpired>").append(_statsHandler.getExpires()).append("</requestsExpired>\n");
+        sb.append("  </requests>\n");
+
+        sb.append("  <responses>\n");
+        sb.append("    <responses1xx>").append(_statsHandler.getResponses1xx()).append("</responses1xx>\n");
+        sb.append("    <responses2xx>").append(_statsHandler.getResponses2xx()).append("</responses2xx>\n");
+        sb.append("    <responses3xx>").append(_statsHandler.getResponses3xx()).append("</responses3xx>\n");
+        sb.append("    <responses4xx>").append(_statsHandler.getResponses4xx()).append("</responses4xx>\n");
+        sb.append("    <responses5xx>").append(_statsHandler.getResponses5xx()).append("</responses5xx>\n");
+        sb.append("    <responsesBytesTotal>").append(_statsHandler.getResponsesBytesTotal()).append("</responsesBytesTotal>\n");
+        sb.append("  </responses>\n");
+
+        sb.append("  <connections>\n");
+        for (Connector connector : _connectors)
+        {
+            sb.append("    <connector>\n");
+            sb.append("      <name>").append(connector.getClass().getName()).append("@").append(connector.hashCode()).append("</name>\n");
+            sb.append("      <protocols>\n");
+            for (String protocol:connector.getProtocols())
+                sb.append("      <protocol>").append(protocol).append("</protocol>\n");
+            sb.append("      </protocols>\n");
+
+            ConnectorStatistics connectorStats = null;
+
+            if (connector instanceof AbstractConnector)
+                connectorStats = ((AbstractConnector)connector).getBean(ConnectorStatistics.class);
+            if (connectorStats == null)
+                sb.append("      <statsOn>false</statsOn>\n");
+            else
+            {
+                sb.append("      <statsOn>true</statsOn>\n");
+                sb.append("      <connections>").append(connectorStats.getConnections()).append("</connections>\n");
+                sb.append("      <connectionsOpen>").append(connectorStats.getConnectionsOpen()).append("</connectionsOpen>\n");
+                sb.append("      <connectionsOpenMax>").append(connectorStats.getConnectionsOpenMax()).append("</connectionsOpenMax>\n");
+                sb.append("      <connectionsDurationMean>").append(connectorStats.getConnectionDurationMean()).append("</connectionsDurationMean>\n");
+                sb.append("      <connectionsDurationMax>").append(connectorStats.getConnectionDurationMax()).append("</connectionsDurationMax>\n");
+                sb.append("      <connectionsDurationStdDev>").append(connectorStats.getConnectionDurationStdDev()).append("</connectionsDurationStdDev>\n");
+                sb.append("      <messagesIn>").append(connectorStats.getMessagesIn()).append("</messagesIn>\n");
+                sb.append("      <messagesOut>").append(connectorStats.getMessagesIn()).append("</messagesOut>\n");
+                sb.append("      <elapsedMs>").append(connectorStats.getStartedMillis()).append("</elapsedMs>\n");
+            }
+            sb.append("    </connector>\n");
+        }
+        sb.append("  </connections>\n");
+
+        sb.append("  <memory>\n");
+        sb.append("    <heapMemoryUsage>").append(_memoryBean.getHeapMemoryUsage().getUsed()).append("</heapMemoryUsage>\n");
+        sb.append("    <nonHeapMemoryUsage>").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append("</nonHeapMemoryUsage>\n");
+        sb.append("  </memory>\n");
+        
+        sb.append("</statistics>\n");
+
+        response.setContentType("text/xml");
+        PrintWriter pout = response.getWriter();
+        pout.write(sb.toString());
+    }
+
+    
+    
+    /**
+     * @param response
+     * @throws IOException
+     */
+    private void sendTextResponse(HttpServletResponse response) throws IOException
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append(_statsHandler.toStatsHTML());
+
+        sb.append("<h2>Connections:</h2>\n");
+        for (Connector connector : _connectors)
+        {
+            sb.append("<h3>").append(connector.getClass().getName()).append("@").append(connector.hashCode()).append("</h3>");
+            sb.append("Protocols:");
+            for (String protocol:connector.getProtocols())
+                sb.append(protocol).append("&nbsp;");
+            sb.append("    <br />\n");
+
+            ConnectorStatistics connectorStats = null;
+
+            if (connector instanceof AbstractConnector)
+                connectorStats = ((AbstractConnector)connector).getBean(ConnectorStatistics.class);
+
+            if (connectorStats != null)
+            {
+                sb.append("Statistics gathering started ").append(connectorStats.getStartedMillis()).append("ms ago").append("<br />\n");
+                sb.append("Total connections: ").append(connectorStats.getConnections()).append("<br />\n");
+                sb.append("Current connections open: ").append(connectorStats.getConnectionsOpen()).append("<br />\n");;
+                sb.append("Max concurrent connections open: ").append(connectorStats.getConnectionsOpenMax()).append("<br />\n");
+                sb.append("Mean connection duration: ").append(connectorStats.getConnectionDurationMean()).append("<br />\n");
+                sb.append("Max connection duration: ").append(connectorStats.getConnectionDurationMax()).append("<br />\n");
+                sb.append("Connection duration standard deviation: ").append(connectorStats.getConnectionDurationStdDev()).append("<br />\n");
+                sb.append("Total messages in: ").append(connectorStats.getMessagesIn()).append("<br />\n");                
+                sb.append("Total messages out: ").append(connectorStats.getMessagesOut()).append("<br />\n");
+            }
+            else
+            {
+                sb.append("Statistics gathering off.\n");
+            }
+
+        }
+
+        sb.append("<h2>Memory:</h2>\n");
+        sb.append("Heap memory usage: ").append(_memoryBean.getHeapMemoryUsage().getUsed()).append(" bytes").append("<br />\n");
+        sb.append("Non-heap memory usage: ").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append(" bytes").append("<br />\n");
+
+        response.setContentType("text/html");
+        PrintWriter pout = response.getWriter();
+        pout.write(sb.toString());
+
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/listener/ELContextCleaner.java b/lib/jetty/org/eclipse/jetty/servlet/listener/ELContextCleaner.java
new file mode 100644 (file)
index 0000000..81b7528
--- /dev/null
@@ -0,0 +1,126 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet.listener;
+
+import java.lang.reflect.Field;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * ELContextCleaner
+ *
+ * Clean up BeanELResolver when the context is going out
+ * of service:
+ *
+ * See http://java.net/jira/browse/GLASSFISH-1649
+ * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=353095
+ */
+public class ELContextCleaner implements ServletContextListener
+{
+    private static final Logger LOG = Log.getLogger(ELContextCleaner.class);
+
+
+    public void contextInitialized(ServletContextEvent sce)
+    {
+    }
+
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        try
+        {
+            //Check that the BeanELResolver class is on the classpath
+            Class beanELResolver = Loader.loadClass(this.getClass(), "javax.el.BeanELResolver");
+
+            //Get a reference via reflection to the properties field which is holding class references
+            Field field = getField(beanELResolver);
+
+            //Get rid of references
+            purgeEntries(field);
+
+            LOG.debug("javax.el.BeanELResolver purged");
+        }
+
+        catch (ClassNotFoundException e)
+        {
+            //BeanELResolver not on classpath, ignore
+        }
+        catch (SecurityException e)
+        {
+            LOG.warn("Cannot purge classes from javax.el.BeanELResolver", e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            LOG.warn("Cannot purge classes from javax.el.BeanELResolver", e);
+        }
+        catch (IllegalAccessException e)
+        {
+            LOG.warn("Cannot purge classes from javax.el.BeanELResolver", e);
+        }
+        catch (NoSuchFieldException e)
+        {
+            LOG.debug("Not cleaning cached beans: no such field javax.el.BeanELResolver.properties");
+        }
+
+    }
+
+
+    protected Field getField (Class beanELResolver)
+    throws SecurityException, NoSuchFieldException
+    {
+        if (beanELResolver == null)
+            return  null;
+
+        return beanELResolver.getDeclaredField("properties");
+    }
+
+    protected void purgeEntries (Field properties)
+    throws IllegalArgumentException, IllegalAccessException
+    {
+        if (properties == null)
+            return;
+
+        if (!properties.isAccessible())
+            properties.setAccessible(true);
+
+        ConcurrentHashMap map = (ConcurrentHashMap) properties.get(null);
+        if (map == null)
+            return;
+
+        Iterator<Class> itor = map.keySet().iterator();
+        while (itor.hasNext())
+        {
+            Class clazz = itor.next();
+            LOG.debug("Clazz: "+clazz+" loaded by "+clazz.getClassLoader());
+            if (Thread.currentThread().getContextClassLoader().equals(clazz.getClassLoader()))
+            {
+                itor.remove();
+                LOG.debug("removed");
+            }
+            else
+                LOG.debug("not removed: "+"contextclassloader="+Thread.currentThread().getContextClassLoader()+"clazz's classloader="+clazz.getClassLoader());
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/listener/IntrospectorCleaner.java b/lib/jetty/org/eclipse/jetty/servlet/listener/IntrospectorCleaner.java
new file mode 100644 (file)
index 0000000..72a6f0d
--- /dev/null
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet.listener;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+/**
+ * IntrospectorCleaner
+ *
+ * Cleans a static cache of Methods held by java.beans.Introspector
+ * class when a context is undeployed.
+ * 
+ * @see java.beans.Introspector
+ */
+public class IntrospectorCleaner implements ServletContextListener
+{
+
+    public void contextInitialized(ServletContextEvent sce)
+    {
+        
+    }
+
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        java.beans.Introspector.flushCaches();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/listener/package-info.java b/lib/jetty/org/eclipse/jetty/servlet/listener/package-info.java
new file mode 100644 (file)
index 0000000..2cb69e3
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Servlet : Useful Servlet Listeners
+ */
+package org.eclipse.jetty.servlet.listener;
+
diff --git a/lib/jetty/org/eclipse/jetty/servlet/package-info.java b/lib/jetty/org/eclipse/jetty/servlet/package-info.java
new file mode 100644 (file)
index 0000000..f7a9cd5
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Modular Servlet Integration
+ */
+package org.eclipse.jetty.servlet;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/AbstractTrie.java b/lib/jetty/org/eclipse/jetty/util/AbstractTrie.java
new file mode 100644 (file)
index 0000000..b49bec1
--- /dev/null
@@ -0,0 +1,85 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+
+/* ------------------------------------------------------------ */
+/** Abstract Trie implementation.
+ * <p>Provides some common implementations, which may not be the most
+ * efficient. For byte operations, the assumption is made that the charset
+ * is ISO-8859-1</p>
+ * @param <V>
+ */
+public abstract class AbstractTrie<V> implements Trie<V>
+{
+    final boolean _caseInsensitive;
+    
+    protected AbstractTrie(boolean insensitive)
+    {
+        _caseInsensitive=insensitive;
+    }
+
+    @Override
+    public boolean put(V v)
+    {
+        return put(v.toString(),v);
+    }
+
+    @Override
+    public V remove(String s)
+    {
+        V o=get(s);
+        put(s,null);
+        return o;
+    }
+
+    @Override
+    public V get(String s)
+    {
+        return get(s,0,s.length());
+    }
+
+    @Override
+    public V get(ByteBuffer b)
+    {
+        return get(b,0,b.remaining());
+    }
+
+    @Override
+    public V getBest(String s)
+    {
+        return getBest(s,0,s.length());
+    }
+    
+    @Override
+    public V getBest(byte[] b, int offset, int len)
+    {
+        return getBest(new String(b,offset,len,StandardCharsets.ISO_8859_1));
+    }
+
+    @Override
+    public boolean isCaseInsensitive()
+    {
+        return _caseInsensitive;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ArrayQueue.java b/lib/jetty/org/eclipse/jetty/util/ArrayQueue.java
new file mode 100644 (file)
index 0000000..671439e
--- /dev/null
@@ -0,0 +1,408 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.AbstractList;
+import java.util.NoSuchElementException;
+import java.util.Queue;
+
+/* ------------------------------------------------------------ */
+/**
+ * Queue backed by circular array.
+ * <p/>
+ * This partial Queue implementation (also with {@link #remove()} for stack operation)
+ * is backed by a growable circular array.
+ *
+ * @param <E>
+ */
+public class ArrayQueue<E> extends AbstractList<E> implements Queue<E>
+{
+    public static final int DEFAULT_CAPACITY = 64;
+    public static final int DEFAULT_GROWTH = 32;
+
+    protected final Object _lock;
+    protected final int _growCapacity;
+    protected Object[] _elements;
+    protected int _nextE;
+    protected int _nextSlot;
+    protected int _size;
+
+    /* ------------------------------------------------------------ */
+    public ArrayQueue()
+    {
+        this(DEFAULT_CAPACITY, -1);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ArrayQueue(Object lock)
+    {
+        this(DEFAULT_CAPACITY, -1,lock);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ArrayQueue(int capacity)
+    {
+        this(capacity, -1);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ArrayQueue(int initCapacity, int growBy)
+    {
+        this(initCapacity, growBy, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ArrayQueue(int initCapacity, int growBy, Object lock)
+    {
+        _lock = lock == null ? this : lock;
+        _growCapacity = growBy;
+        _elements = new Object[initCapacity];
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Object lock()
+    {
+        return _lock;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int getCapacity()
+    {
+        synchronized (_lock)
+        {
+            return _elements.length;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean add(E e)
+    {
+        if (!offer(e))
+            throw new IllegalStateException("Full");
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean offer(E e)
+    {
+        synchronized (_lock)
+        {
+            return enqueue(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private boolean enqueue(E e)
+    {
+        if (_size == _elements.length && !grow())
+            return false;
+
+        _size++;
+        _elements[_nextSlot++] = e;
+        if (_nextSlot == _elements.length)
+            _nextSlot = 0;
+
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add without synchronization or bounds checking
+     *
+     * @param e the element to add
+     * @see #add(Object)
+     */
+    public void addUnsafe(E e)
+    {
+        if (!enqueue(e))
+            throw new IllegalStateException("Full");
+    }
+
+    /* ------------------------------------------------------------ */
+    public E element()
+    {
+        synchronized (_lock)
+        {
+            if (isEmpty())
+                throw new NoSuchElementException();
+            return at(_nextE);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    private E at(int index)
+    {
+        return (E)_elements[index];
+    }
+
+    /* ------------------------------------------------------------ */
+    public E peek()
+    {
+        synchronized (_lock)
+        {
+            if (_size == 0)
+                return null;
+            return at(_nextE);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public E peekUnsafe()
+    {
+        if (_size == 0)
+            return null;
+        return at(_nextE);
+    }
+
+    /* ------------------------------------------------------------ */
+    public E poll()
+    {
+        synchronized (_lock)
+        {
+            if (_size == 0)
+                return null;
+            return dequeue();
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public E pollUnsafe()
+    {
+        if (_size == 0)
+            return null;
+        return dequeue();
+    }
+
+    /* ------------------------------------------------------------ */
+    private E dequeue()
+    {
+        E e = at(_nextE);
+        _elements[_nextE] = null;
+        _size--;
+        if (++_nextE == _elements.length)
+            _nextE = 0;
+        return e;
+    }
+
+    /* ------------------------------------------------------------ */
+    public E remove()
+    {
+        synchronized (_lock)
+        {
+            if (_size == 0)
+                throw new NoSuchElementException();
+            return dequeue();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void clear()
+    {
+        synchronized (_lock)
+        {
+            _size = 0;
+            _nextE = 0;
+            _nextSlot = 0;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isEmpty()
+    {
+        synchronized (_lock)
+        {
+            return _size == 0;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int size()
+    {
+        synchronized (_lock)
+        {
+            return _size;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public E get(int index)
+    {
+        synchronized (_lock)
+        {
+            if (index < 0 || index >= _size)
+                throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+            return getUnsafe(index);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get without synchronization or bounds checking.
+     *
+     * @param  index index of the element to return
+     * @return the element at the specified index
+     * @see #get(int)
+     */
+    public E getUnsafe(int index)
+    {
+        int i = (_nextE + index) % _elements.length;
+        return at(i);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public E remove(int index)
+    {
+        synchronized (_lock)
+        {
+            if (index < 0 || index >= _size)
+                throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+            int i = (_nextE + index) % _elements.length;
+            E old = at(i);
+
+            if (i < _nextSlot)
+            {
+                // 0                         _elements.length
+                //       _nextE........._nextSlot
+                System.arraycopy(_elements, i + 1, _elements, i, _nextSlot - i);
+                _nextSlot--;
+                _size--;
+            }
+            else
+            {
+                // 0                         _elements.length
+                // ......_nextSlot   _nextE..........
+                System.arraycopy(_elements, i + 1, _elements, i, _elements.length - i - 1);
+                if (_nextSlot > 0)
+                {
+                    _elements[_elements.length - 1] = _elements[0];
+                    System.arraycopy(_elements, 1, _elements, 0, _nextSlot - 1);
+                    _nextSlot--;
+                }
+                else
+                    _nextSlot = _elements.length - 1;
+
+                _size--;
+            }
+
+            return old;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public E set(int index, E element)
+    {
+        synchronized (_lock)
+        {
+            if (index < 0 || index >= _size)
+                throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+            int i = _nextE + index;
+            if (i >= _elements.length)
+                i -= _elements.length;
+            E old = at(i);
+            _elements[i] = element;
+            return old;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void add(int index, E element)
+    {
+        synchronized (_lock)
+        {
+            if (index < 0 || index > _size)
+                throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+            if (_size == _elements.length && !grow())
+                throw new IllegalStateException("Full");
+
+            if (index == _size)
+            {
+                add(element);
+            }
+            else
+            {
+                int i = _nextE + index;
+                if (i >= _elements.length)
+                    i -= _elements.length;
+
+                _size++;
+                _nextSlot++;
+                if (_nextSlot == _elements.length)
+                    _nextSlot = 0;
+
+                if (i < _nextSlot)
+                {
+                    // 0                         _elements.length
+                    //       _nextE.....i..._nextSlot
+                    // 0                         _elements.length
+                    // ..i..._nextSlot   _nextE..........
+                    System.arraycopy(_elements, i, _elements, i + 1, _nextSlot - i);
+                    _elements[i] = element;
+                }
+                else
+                {
+                    // 0                         _elements.length
+                    // ......_nextSlot   _nextE.....i....
+                    if (_nextSlot > 0)
+                    {
+                        System.arraycopy(_elements, 0, _elements, 1, _nextSlot);
+                        _elements[0] = _elements[_elements.length - 1];
+                    }
+
+                    System.arraycopy(_elements, i, _elements, i + 1, _elements.length - i - 1);
+                    _elements[i] = element;
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected boolean grow()
+    {
+        synchronized (_lock)
+        {
+            if (_growCapacity <= 0)
+                return false;
+
+            Object[] elements = new Object[_elements.length + _growCapacity];
+
+            int split = _elements.length - _nextE;
+            if (split > 0)
+                System.arraycopy(_elements, _nextE, elements, 0, split);
+            if (_nextE != 0)
+                System.arraycopy(_elements, 0, elements, split, _nextSlot);
+
+            _elements = elements;
+            _nextE = 0;
+            _nextSlot = _size;
+            return true;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ArrayTernaryTrie.java b/lib/jetty/org/eclipse/jetty/util/ArrayTernaryTrie.java
new file mode 100644 (file)
index 0000000..fdca4ce
--- /dev/null
@@ -0,0 +1,473 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+
+/* ------------------------------------------------------------ */
+/** A Ternary Trie String lookup data structure.
+ * This Trie is of a fixed size and cannot grow (which can be a good thing with regards to DOS when used as a cache).
+ * <p>
+ * The Trie is stored in 3 arrays:<dl>
+ * <dt>char[] _tree</dt><dd>This is semantically 2 dimensional array flattened into a 1 dimensional char array. The second dimension
+ * is that every 4 sequential elements represents a row of: character; hi index; eq index; low index, used to build a
+ * ternary trie of key strings.</dd>
+ * <dt>String[] _key<dt><dd>An array of key values where each element matches a row in the _tree array. A non zero key element 
+ * indicates that the _tree row is a complete key rather than an intermediate character of a longer key.</dd>
+ * <dt>V[] _value</dt><dd>An array of values corresponding to the _key array</dd>
+ * </dl>
+ * <p>The lookup of a value will iterate through the _tree array matching characters. If the equal tree branch is followed,
+ * then the _key array is looked up to see if this is a complete match.  If a match is found then the _value array is looked up
+ * to return the matching value.
+ * </p>
+ * @param <V>
+ */
+public class ArrayTernaryTrie<V> extends AbstractTrie<V>
+{
+    private static int LO=1;
+    private static int EQ=2;
+    private static int HI=3;
+    
+    /**
+     * The Size of a Trie row is the char, and the low, equal and high
+     * child pointers
+     */
+    private static final int ROW_SIZE = 4;
+    
+    /**
+     * The Trie rows in a single array which allows a lookup of row,character
+     * to the next row in the Trie.  This is actually a 2 dimensional
+     * array that has been flattened to achieve locality of reference.
+     */
+    private final char[] _tree;
+    
+    /**
+     * The key (if any) for a Trie row. 
+     * A row may be a leaf, a node or both in the Trie tree.
+     */
+    private final String[] _key;
+    
+    /**
+     * The value (if any) for a Trie row. 
+     * A row may be a leaf, a node or both in the Trie tree.
+     */
+    private final Object[] _value;
+    
+    /**
+     * The number of rows allocated
+     */
+    private char _rows;
+
+    public ArrayTernaryTrie()
+    {
+        this(128);
+    }
+    
+    public ArrayTernaryTrie(boolean insensitive)
+    {
+        this(insensitive,128);
+    }
+
+    public ArrayTernaryTrie(int capacityInNodes)
+    {
+        this(true,capacityInNodes);
+    }
+    
+    public ArrayTernaryTrie(boolean insensitive, int capacityInNodes)
+    {
+        super(insensitive);
+        _value=new Object[capacityInNodes];
+        _tree=new char[capacityInNodes*ROW_SIZE];
+        _key=new String[capacityInNodes];
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Copy Trie and change capacity by a factor
+     * @param trie
+     * @param factor
+     */
+    public ArrayTernaryTrie(ArrayTernaryTrie<V> trie, double factor)
+    {
+        super(trie.isCaseInsensitive());
+        int capacity=(int)(trie._value.length*factor);
+        _rows=trie._rows;
+        _value=Arrays.copyOf(trie._value, capacity);
+        _tree=Arrays.copyOf(trie._tree, capacity*ROW_SIZE);
+        _key=Arrays.copyOf(trie._key, capacity);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean put(String s, V v)
+    {
+        int t=0;
+        int limit = s.length();
+        int last=0;
+        for(int k=0; k < limit; k++)
+        {
+            char c=s.charAt(k);
+            if(isCaseInsensitive() && c<128)
+                c=StringUtil.lowercases[c];
+            
+            while (true)
+            {
+                int row=ROW_SIZE*t;
+                
+                // Do we need to create the new row?
+                if (t==_rows)
+                {
+                    _rows++;
+                    if (_rows>=_key.length)
+                    {
+                        _rows--;
+                        return false;
+                    }
+                    _tree[row]=c;
+                }
+
+                char n=_tree[row];
+                int diff=n-c;
+                if (diff==0)
+                    t=_tree[last=(row+EQ)];
+                else if (diff<0)
+                    t=_tree[last=(row+LO)];
+                else
+                    t=_tree[last=(row+HI)];
+                
+                // do we need a new row?
+                if (t==0)
+                {
+                    t=_rows;
+                    _tree[last]=(char)t;
+                }
+                
+                if (diff==0)
+                    break;
+            }
+        }
+
+        // Do we need to create the new row?
+        if (t==_rows)
+        {
+            _rows++;
+            if (_rows>=_key.length)
+            {
+                _rows--;
+                return false;
+            }
+        }
+
+        // Put the key and value
+        _key[t]=v==null?null:s;
+        _value[t] = v;
+                
+        return true;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V get(String s,int offset, int len)
+    {
+        int t = 0;
+        for(int i=0; i < len;)
+        {
+            char c=s.charAt(offset+i++);
+            if(isCaseInsensitive() && c<128)
+                c=StringUtil.lowercases[c];
+            
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        return null;
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    return null;
+            }
+        }
+        
+        return (V)_value[t];
+    }
+
+    
+    @Override
+    public V get(ByteBuffer b, int offset, int len)
+    {
+        int t = 0;
+        offset+=b.position();
+        
+        for(int i=0; i < len;)
+        {
+            byte c=(byte)(b.get(offset+i++)&0x7f);
+            if(isCaseInsensitive())
+                c=(byte)StringUtil.lowercases[c];
+            
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        return null;
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    return null;
+            }
+        }
+
+        return (V)_value[t];
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(String s)
+    {
+        return getBest(0,s,0,s.length());
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(String s, int offset, int length)
+    {
+        return getBest(0,s,offset,length);
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,String s,int offset,int len)
+    {
+        int node=t;
+        loop: for(int i=0; i<len; i++)
+        {
+            char c=s.charAt(offset+i);
+            if(isCaseInsensitive() && c<128)
+                c=StringUtil.lowercases[c];
+
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        break loop;
+                    
+                    // if this node is a match, recurse to remember 
+                    if (_key[t]!=null)
+                    {
+                        node=t;
+                        V best=getBest(t,s,offset+i+1,len-i-1);
+                        if (best!=null)
+                            return best;
+                    }
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    break loop;
+            }
+        }
+        return (V)_value[node];
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(ByteBuffer b, int offset, int len)
+    {
+        if (b.hasArray())
+            return getBest(0,b.array(),b.arrayOffset()+b.position()+offset,len);
+        return getBest(0,b,offset,len);
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,byte[] b, int offset, int len)
+    {
+        int node=t;
+        loop: for(int i=0; i<len; i++)
+        {
+            byte c=(byte)(b[offset+i]&0x7f);
+            if(isCaseInsensitive())
+                c=(byte)StringUtil.lowercases[c];
+
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        break loop;
+                    
+                    // if this node is a match, recurse to remember 
+                    if (_key[t]!=null)
+                    {
+                        node=t;
+                        V best=getBest(t,b,offset+i+1,len-i-1);
+                        if (best!=null)
+                            return best;
+                    }
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    break loop;
+            }
+        }
+        return (V)_value[node];
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,ByteBuffer b, int offset, int len)
+    {
+        int node=t;
+        int o= offset+b.position();
+        
+        loop: for(int i=0; i<len; i++)
+        {
+            byte c=(byte)(b.get(o+i)&0x7f);
+            if(isCaseInsensitive())
+                c=(byte)StringUtil.lowercases[c];
+
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        break loop;
+                    
+                    // if this node is a match, recurse to remember 
+                    if (_key[t]!=null)
+                    {
+                        node=t;
+                        V best=getBest(t,b,offset+i+1,len-i-1);
+                        if (best!=null)
+                            return best;
+                    }
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    break loop;
+            }
+        }
+        return (V)_value[node];
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        for (int r=0;r<=_rows;r++)
+        {
+            if (_key[r]!=null && _value[r]!=null)
+            {
+                buf.append(',');
+                buf.append(_key[r]);
+                buf.append('=');
+                buf.append(_value[r].toString());
+            }
+        }
+        if (buf.length()==0)
+            return "{}";
+        
+        buf.setCharAt(0,'{');
+        buf.append('}');
+        return buf.toString();
+    }
+
+
+
+    @Override
+    public Set<String> keySet()
+    {
+        Set<String> keys = new HashSet<>();
+
+        for (int r=0;r<=_rows;r++)
+        {
+            if (_key[r]!=null && _value[r]!=null)
+                keys.add(_key[r]);
+        }
+        return keys;
+    }
+
+    @Override
+    public boolean isFull()
+    {
+        return _rows+1==_key.length;
+    }
+    
+    public static int hilo(int diff)
+    {
+        // branchless equivalent to return ((diff<0)?LO:HI);
+        // return 3+2*((diff&Integer.MIN_VALUE)>>Integer.SIZE-1);
+        return 1+(diff|Integer.MAX_VALUE)/(Integer.MAX_VALUE/2);
+    }
+    
+    public void dump()
+    {
+        for (int r=0;r<_rows;r++)
+        {
+            char c=_tree[r*ROW_SIZE+0];
+            System.err.printf("%4d [%s,%d,%d,%d] '%s':%s%n",
+                r,
+                (c<' '||c>127)?(""+(int)c):"'"+c+"'",
+                (int)_tree[r*ROW_SIZE+LO],
+                (int)_tree[r*ROW_SIZE+EQ],
+                (int)_tree[r*ROW_SIZE+HI],
+                _key[r],
+                _value[r]);
+        }
+        
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ArrayTrie.java b/lib/jetty/org/eclipse/jetty/util/ArrayTrie.java
new file mode 100644 (file)
index 0000000..73ccc42
--- /dev/null
@@ -0,0 +1,445 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashSet;
+import java.util.Set;
+
+/* ------------------------------------------------------------ */
+/** A Trie String lookup data structure using a fixed size array.
+ * <p>This implementation is always case insensitive and is optimal for
+ * a small number of fixed strings with few special characters.
+ * </p>
+ * @param <V>
+ */
+public class ArrayTrie<V> extends AbstractTrie<V>
+{
+    /**
+     * The Size of a Trie row is how many characters can be looked
+     * up directly without going to a big index.  This is set at 
+     * 32 to cover case insensitive alphabet and a few other common
+     * characters. 
+     */
+    private static final int ROW_SIZE = 32;
+    
+    /**
+     * The index lookup table, this maps a character as a byte 
+     * (ISO-8859-1 or UTF8) to an index within a Trie row
+     */
+    private static final int[] __lookup = 
+    { // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+   /*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+   /*1*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 30, 
+   /*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, -1, -1,
+   /*3*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28, 29, -1, -1, -1, -1,
+   /*4*/-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+   /*5*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+   /*6*/-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+   /*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+    };
+    
+    /**
+     * The Trie rows in a single array which allows a lookup of row,character
+     * to the next row in the Trie.  This is actually a 2 dimensional
+     * array that has been flattened to achieve locality of reference.
+     * The first ROW_SIZE entries are for row 0, then next ROW_SIZE 
+     * entries are for row 1 etc.   So in general instead of using
+     * _rows[row][index], we use _rows[row*ROW_SIZE+index] to look up
+     * the next row for a given character.
+     * 
+     * The array is of characters rather than integers to save space. 
+     */
+    private final char[] _rowIndex;
+    
+    /**
+     * The key (if any) for a Trie row. 
+     * A row may be a leaf, a node or both in the Trie tree.
+     */
+    private final String[] _key;
+    
+    /**
+     * The value (if any) for a Trie row. 
+     * A row may be a leaf, a node or both in the Trie tree.
+     */
+    private final Object[] _value;
+    
+    /**
+     * A big index for each row.
+     * If a character outside of the lookup map is needed,
+     * then a big index will be created for the row, with
+     * 256 entries, one for each possible byte.
+     */
+    private char[][] _bigIndex;
+    
+    /**
+     * The number of rows allocated
+     */
+    private char _rows;
+
+    public ArrayTrie()
+    {
+        this(128);
+    }
+    
+    public ArrayTrie(int capacityInNodes)
+    {
+        super(true);
+        _value=new Object[capacityInNodes];
+        _rowIndex=new char[capacityInNodes*32];
+        _key=new String[capacityInNodes];
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean put(String s, V v)
+    {
+        int t=0;
+        int k;
+        int limit = s.length();
+        for(k=0; k < limit; k++)
+        {
+            char c=s.charAt(k);
+            
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                t=_rowIndex[idx];
+                if (t==0)
+                {
+                    if (++_rows>=_value.length)
+                        return false;
+                    t=_rowIndex[idx]=_rows;
+                }
+            }
+            else if (c>127)
+                throw new IllegalArgumentException("non ascii character");
+            else
+            {
+                if (_bigIndex==null)
+                    _bigIndex=new char[_value.length][];
+                if (t>=_bigIndex.length)
+                    return false;
+                char[] big=_bigIndex[t];
+                if (big==null)
+                    big=_bigIndex[t]=new char[128];
+                t=big[c];
+                if (t==0)
+                {
+                    if (_rows==_value.length)
+                        return false;
+                    t=big[c]=++_rows;
+                }
+            }
+        }
+        _key[t]=v==null?null:s;
+        V old=(V)_value[t];
+        _value[t] = v;
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V get(String s, int offset, int len)
+    {
+        int t = 0;
+        for(int i=0; i < len; i++)
+        {
+            char c=s.charAt(offset+i);
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                t=_rowIndex[idx];
+                if (t==0)
+                    return null;
+            }
+            else
+            {
+                char[] big = _bigIndex==null?null:_bigIndex[t];
+                if (big==null)
+                    return null;
+                t=big[c];
+                if (t==0)
+                    return null;
+            }
+        }
+        return (V)_value[t];
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V get(ByteBuffer b,int offset,int len)
+    {
+        int t = 0;
+        for(int i=0; i < len; i++)
+        {
+            byte c=b.get(offset+i);
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                t=_rowIndex[idx];
+                if (t==0)
+                    return null;
+            }
+            else
+            {
+                char[] big = _bigIndex==null?null:_bigIndex[t];
+                if (big==null)
+                    return null;
+                t=big[c];
+                if (t==0)
+                    return null;
+            }
+        }
+        return (V)_value[t];
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(byte[] b,int offset,int len)
+    {
+        return getBest(0,b,offset,len);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(ByteBuffer b,int offset,int len)
+    {
+        if (b.hasArray())
+            return getBest(0,b.array(),b.arrayOffset()+b.position()+offset,len);
+        return getBest(0,b,offset,len);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(String s, int offset, int len)
+    {
+        return getBest(0,s,offset,len);
+    }
+    
+    /* ------------------------------------------------------------ */
+    private V getBest(int t, String s, int offset, int len)
+    {
+        int pos=offset;
+        for(int i=0; i < len; i++)
+        {
+            char c=s.charAt(pos++);
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                int nt=_rowIndex[idx];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            else
+            {
+                char[] big = _bigIndex==null?null:_bigIndex[t];
+                if (big==null)
+                    return null;
+                int nt=big[c];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            
+            // Is the next Trie is a match
+            if (_key[t]!=null)
+            {
+                // Recurse so we can remember this possibility
+                V best=getBest(t,s,offset+i+1,len-i-1);
+                if (best!=null)
+                    return best;
+                return (V)_value[t];
+            }
+        }
+        return (V)_value[t];
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,byte[] b,int offset,int len)
+    {
+        for(int i=0; i < len; i++)
+        {
+            byte c=b[offset+i];
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                int nt=_rowIndex[idx];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            else
+            {
+                char[] big = _bigIndex==null?null:_bigIndex[t];
+                if (big==null)
+                    return null;
+                int nt=big[c];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            
+            // Is the next Trie is a match
+            if (_key[t]!=null)
+            {
+                // Recurse so we can remember this possibility
+                V best=getBest(t,b,offset+i+1,len-i-1);
+                if (best!=null)
+                    return best;
+                break;
+            }
+        }
+        return (V)_value[t];
+    }
+    
+    private V getBest(int t,ByteBuffer b,int offset,int len)
+    {
+        int pos=b.position()+offset;
+        for(int i=0; i < len; i++)
+        {
+            byte c=b.get(pos++);
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                int nt=_rowIndex[idx];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            else
+            {
+                char[] big = _bigIndex==null?null:_bigIndex[t];
+                if (big==null)
+                    return null;
+                int nt=big[c];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            
+            // Is the next Trie is a match
+            if (_key[t]!=null)
+            {
+                // Recurse so we can remember this possibility
+                V best=getBest(t,b,offset+i+1,len-i-1);
+                if (best!=null)
+                    return best;
+                break;
+            }
+        }
+        return (V)_value[t];
+    }
+    
+    
+    
+
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        toString(buf,0);
+        
+        if (buf.length()==0)
+            return "{}";
+        
+        buf.setCharAt(0,'{');
+        buf.append('}');
+        return buf.toString();
+    }
+
+
+    private <V> void toString(Appendable out, int t)
+    {
+        if (_value[t]!=null)
+        {
+            try
+            {
+                out.append(',');
+                out.append(_key[t]);
+                out.append('=');
+                out.append(_value[t].toString());
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        for(int i=0; i < ROW_SIZE; i++)
+        {
+            int idx=t*ROW_SIZE+i;
+            if (_rowIndex[idx] != 0)
+                toString(out,_rowIndex[idx]);
+        }
+
+        char[] big = _bigIndex==null?null:_bigIndex[t];
+        if (big!=null)
+        {
+            for (int i:big)
+                if (i!=0)
+                    toString(out,i);
+        }
+
+    }
+
+    @Override
+    public Set<String> keySet()
+    {
+        Set<String> keys = new HashSet<>();
+        keySet(keys,0);
+        return keys;
+    }
+    
+    private void keySet(Set<String> set, int t)
+    {
+        if (t<_value.length&&_value[t]!=null)
+            set.add(_key[t]);
+
+        for(int i=0; i < ROW_SIZE; i++)
+        {
+            int idx=t*ROW_SIZE+i;
+            if (idx<_rowIndex.length && _rowIndex[idx] != 0)
+                keySet(set,_rowIndex[idx]);
+        }
+        
+        char[] big = _bigIndex==null||t>=_bigIndex.length?null:_bigIndex[t];
+        if (big!=null)
+        {
+            for (int i:big)
+                if (i!=0)
+                    keySet(set,i);
+        }
+    }
+    
+    @Override
+    public boolean isFull()
+    {
+        return _rows+1==_key.length;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ArrayUtil.java b/lib/jetty/org/eclipse/jetty/util/ArrayUtil.java
new file mode 100644 (file)
index 0000000..76b195d
--- /dev/null
@@ -0,0 +1,142 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+/* ------------------------------------------------------------ */
+/**
+ */
+public class ArrayUtil
+    implements Cloneable, Serializable
+{
+
+    /* ------------------------------------------------------------ */
+    public static<T> T[] removeFromArray(T[] array, Object item)
+    {
+        if (item==null || array==null)
+            return array;
+        for (int i=array.length;i-->0;)
+        {
+            if (item.equals(array[i]))
+            {
+                Class<?> c = array==null?item.getClass():array.getClass().getComponentType();
+                @SuppressWarnings("unchecked")
+                T[] na = (T[])Array.newInstance(c, Array.getLength(array)-1);
+                if (i>0)
+                    System.arraycopy(array, 0, na, 0, i);
+                if (i+1<array.length)
+                    System.arraycopy(array, i+1, na, i, array.length-(i+1));
+                return na;
+            }
+        }
+        return array;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add element to an array
+     * @param array The array to add to (or null)
+     * @param item The item to add
+     * @param type The type of the array (in case of null array)
+     * @return new array with contents of array plus item
+     */
+    public static<T> T[] addToArray(T[] array, T item, Class<?> type)
+    {
+        if (array==null)
+        {
+            if (type==null && item!=null)
+                type= item.getClass();
+            @SuppressWarnings("unchecked")
+            T[] na = (T[])Array.newInstance(type, 1);
+            na[0]=item;
+            return na;
+        }
+        else
+        {
+            T[] na = Arrays.copyOf(array,array.length+1);
+            na[array.length]=item;
+            return na;
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add element to the start of an array
+     * @param array The array to add to (or null)
+     * @param item The item to add
+     * @param type The type of the array (in case of null array)
+     * @return new array with contents of array plus item
+     */
+    public static<T> T[] prependToArray(T item, T[] array, Class<?> type)
+    {
+        if (array==null)
+        {
+            if (type==null && item!=null)
+                type= item.getClass();
+            @SuppressWarnings("unchecked")
+            T[] na = (T[])Array.newInstance(type, 1);
+            na[0]=item;
+            return na;
+        }
+        else
+        {
+            Class<?> c = array.getClass().getComponentType();
+            @SuppressWarnings("unchecked")
+            T[] na = (T[])Array.newInstance(c, Array.getLength(array)+1);
+            System.arraycopy(array, 0, na, 1, array.length);
+            na[0]=item;
+            return na;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param array Any array of object
+     * @return A new <i>modifiable</i> list initialised with the elements from <code>array</code>.
+     */
+    public static<E> List<E> asMutableList(E[] array)
+    {  
+        if (array==null || array.length==0)
+            return new ArrayList<E>();
+        return new ArrayList<E>(Arrays.asList(array));
+    }
+
+    /* ------------------------------------------------------------ */
+    public static <T> T[] removeNulls(T[] array)
+    {
+        for (T t : array)
+        {
+            if (t==null)
+            {
+                List<T> list = new ArrayList<>();
+                for (T t2:array)
+                    if (t2!=null)
+                        list.add(t2);
+                return list.toArray(Arrays.copyOf(array,list.size()));
+            }
+        }
+        return array;
+    }
+    
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/util/Atomics.java b/lib/jetty/org/eclipse/jetty/util/Atomics.java
new file mode 100644 (file)
index 0000000..42fb489
--- /dev/null
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class Atomics
+{
+    private Atomics()
+    {
+    }
+
+    public static void updateMin(AtomicLong currentMin, long newValue)
+    {
+        long oldValue = currentMin.get();
+        while (newValue < oldValue)
+        {
+            if (currentMin.compareAndSet(oldValue, newValue))
+                break;
+            oldValue = currentMin.get();
+        }
+    }
+
+    public static void updateMax(AtomicLong currentMax, long newValue)
+    {
+        long oldValue = currentMax.get();
+        while (newValue > oldValue)
+        {
+            if (currentMax.compareAndSet(oldValue, newValue))
+                break;
+            oldValue = currentMax.get();
+        }
+    }
+
+    public static void updateMin(AtomicInteger currentMin, int newValue)
+    {
+        int oldValue = currentMin.get();
+        while (newValue < oldValue)
+        {
+            if (currentMin.compareAndSet(oldValue, newValue))
+                break;
+            oldValue = currentMin.get();
+        }
+    }
+
+    public static void updateMax(AtomicInteger currentMax, int newValue)
+    {
+        int oldValue = currentMax.get();
+        while (newValue > oldValue)
+        {
+            if (currentMax.compareAndSet(oldValue, newValue))
+                break;
+            oldValue = currentMax.get();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Attributes.java b/lib/jetty/org/eclipse/jetty/util/Attributes.java
new file mode 100644 (file)
index 0000000..c65abc2
--- /dev/null
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.Enumeration;
+
+/* ------------------------------------------------------------ */
+/** Attributes.
+ * Interface commonly used for storing attributes.
+ * 
+ *
+ */
+public interface Attributes
+{
+    public void removeAttribute(String name);
+    public void setAttribute(String name, Object attribute);
+    public Object getAttribute(String name);
+    public Enumeration<String> getAttributeNames();
+    public void clearAttributes();
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/AttributesMap.java b/lib/jetty/org/eclipse/jetty/util/AttributesMap.java
new file mode 100644 (file)
index 0000000..c605e09
--- /dev/null
@@ -0,0 +1,151 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class AttributesMap implements Attributes
+{
+    private final AtomicReference<ConcurrentMap<String, Object>> _map = new AtomicReference<>();
+
+    public AttributesMap()
+    {
+    }
+
+    public AttributesMap(AttributesMap attributes)
+    {
+        ConcurrentMap<String, Object> map = attributes.map();
+        if (map != null)
+            _map.set(new ConcurrentHashMap<>(map));
+    }
+
+    private ConcurrentMap<String, Object> map()
+    {
+        return _map.get();
+    }
+
+    private ConcurrentMap<String, Object> ensureMap()
+    {
+        while (true)
+        {
+            ConcurrentMap<String, Object> map = map();
+            if (map != null)
+                return map;
+            map = new ConcurrentHashMap<>();
+            if (_map.compareAndSet(null, map))
+                return map;
+        }
+    }
+
+    @Override
+    public void removeAttribute(String name)
+    {
+        Map<String, Object> map = map();
+        if (map != null)
+            map.remove(name);
+    }
+
+    @Override
+    public void setAttribute(String name, Object attribute)
+    {
+        if (attribute == null)
+            removeAttribute(name);
+        else
+            ensureMap().put(name, attribute);
+    }
+
+    @Override
+    public Object getAttribute(String name)
+    {
+        Map<String, Object> map = map();
+        return map == null ? null : map.get(name);
+    }
+
+    @Override
+    public Enumeration<String> getAttributeNames()
+    {
+        return Collections.enumeration(getAttributeNameSet());
+    }
+
+    public Set<String> getAttributeNameSet()
+    {
+        return keySet();
+    }
+
+    public Set<Map.Entry<String, Object>> getAttributeEntrySet()
+    {
+        Map<String, Object> map = map();
+        return map == null ? Collections.<Map.Entry<String, Object>>emptySet() : map.entrySet();
+    }
+
+    public static Enumeration<String> getAttributeNamesCopy(Attributes attrs)
+    {
+        if (attrs instanceof AttributesMap)
+            return Collections.enumeration(((AttributesMap)attrs).keySet());
+
+        List<String> names = new ArrayList<>();
+        names.addAll(Collections.list(attrs.getAttributeNames()));
+        return Collections.enumeration(names);
+    }
+
+    @Override
+    public void clearAttributes()
+    {
+        Map<String, Object> map = map();
+        if (map != null)
+            map.clear();
+    }
+
+    public int size()
+    {
+        Map<String, Object> map = map();
+        return map == null ? 0 : map.size();
+    }
+
+    @Override
+    public String toString()
+    {
+        Map<String, Object> map = map();
+        return map == null ? "{}" : map.toString();
+    }
+
+    private Set<String> keySet()
+    {
+        Map<String, Object> map = map();
+        return map == null ? Collections.<String>emptySet() : map.keySet();
+    }
+
+    public void addAll(Attributes attributes)
+    {
+        Enumeration<String> e = attributes.getAttributeNames();
+        while (e.hasMoreElements())
+        {
+            String name = e.nextElement();
+            setAttribute(name, attributes.getAttribute(name));
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/B64Code.java b/lib/jetty/org/eclipse/jetty/util/B64Code.java
new file mode 100644 (file)
index 0000000..7fbbb18
--- /dev/null
@@ -0,0 +1,464 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+
+
+/** Fast B64 Encoder/Decoder as described in RFC 1421.
+ * <p>Does not insert or interpret whitespace as described in RFC
+ * 1521. If you require this you must pre/post process your data.
+ * <p> Note that in a web context the usual case is to not want
+ * linebreaks or other white space in the encoded output.
+ *
+ */
+public class B64Code
+{
+    private static final char __pad='=';
+    private static final char[] __rfc1421alphabet=
+            {
+                'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
+                'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
+                'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
+                'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
+            };
+
+    private static final byte[] __rfc1421nibbles;
+    static
+    {
+        __rfc1421nibbles=new byte[256];
+        for (int i=0;i<256;i++)
+            __rfc1421nibbles[i]=-1;
+        for (byte b=0;b<64;b++)
+            __rfc1421nibbles[(byte)__rfc1421alphabet[b]]=b;
+        __rfc1421nibbles[(byte)__pad]=0;
+    }
+
+    private B64Code()
+    {
+    }
+
+    /**
+     * Base 64 encode as described in RFC 1421.
+     * <p>Does not insert whitespace as described in RFC 1521.
+     * @param s String to encode.
+     * @return String containing the encoded form of the input.
+     */
+    public static String encode(String s)
+    {
+        return encode(s, (Charset)null);
+    }
+
+    /**
+     * Base 64 encode as described in RFC 1421.
+     * <p>Does not insert whitespace as described in RFC 1521.
+     * @param s String to encode.
+     * @param charEncoding String representing the name of
+     *        the character encoding of the provided input String.
+     * @return String containing the encoded form of the input.
+     */
+    public static String encode(String s,String charEncoding)
+    {
+        byte[] bytes;
+        if (charEncoding==null)
+            bytes=s.getBytes(StandardCharsets.ISO_8859_1);
+        else
+            bytes=s.getBytes(Charset.forName(charEncoding));
+        return new String(encode(bytes));
+    }
+
+    /**
+     * Base 64 encode as described in RFC 1421.
+     * <p>Does not insert whitespace as described in RFC 1521.
+     * @param s String to encode.
+     * @param charEncoding The character encoding of the provided input String.
+     * @return String containing the encoded form of the input.
+     */
+    public static String encode(String s, Charset charEncoding)
+    {
+        byte[] bytes=s.getBytes(charEncoding==null ? StandardCharsets.ISO_8859_1 : charEncoding);
+        return new String(encode(bytes));
+    }
+
+    /**
+     * Fast Base 64 encode as described in RFC 1421.
+     * <p>Does not insert whitespace as described in RFC 1521.
+     * <p> Avoids creating extra copies of the input/output.
+     * @param b byte array to encode.
+     * @return char array containing the encoded form of the input.
+     */
+    public static char[] encode(byte[] b)
+    {
+        if (b==null)
+            return null;
+
+        int bLen=b.length;
+        int cLen=((bLen+2)/3)*4;
+        char c[]=new char[cLen];
+        int ci=0;
+        int bi=0;
+        byte b0, b1, b2;
+        int stop=(bLen/3)*3;
+        while (bi<stop)
+        {
+            b0=b[bi++];
+            b1=b[bi++];
+            b2=b[bi++];
+            c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+            c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+            c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
+            c[ci++]=__rfc1421alphabet[b2&0x3f];
+        }
+
+        if (bLen!=bi)
+        {
+            switch (bLen%3)
+            {
+                case 2:
+                    b0=b[bi++];
+                    b1=b[bi++];
+                    c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+                    c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+                    c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f];
+                    c[ci++]=__pad;
+                    break;
+
+                case 1:
+                    b0=b[bi++];
+                    c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+                    c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f];
+                    c[ci++]=__pad;
+                    c[ci++]=__pad;
+                    break;
+
+                default:
+                    break;
+            }
+        }
+
+        return c;
+    }
+
+    /**
+     * Fast Base 64 encode as described in RFC 1421 and RFC2045
+     * <p>Does not insert whitespace as described in RFC 1521, unless rfc2045 is passed as true.
+     * <p> Avoids creating extra copies of the input/output.
+     * @param b byte array to encode.
+     * @param rfc2045 If true, break lines at 76 characters with CRLF
+     * @return char array containing the encoded form of the input.
+     */
+    public static char[] encode(byte[] b, boolean rfc2045)
+    {
+        if (b==null)
+            return null;
+        if (!rfc2045)
+            return encode(b);
+
+        int bLen=b.length;
+        int cLen=((bLen+2)/3)*4;
+        cLen+=2+2*(cLen/76);
+        char c[]=new char[cLen];
+        int ci=0;
+        int bi=0;
+        byte b0, b1, b2;
+        int stop=(bLen/3)*3;
+        int l=0;
+        while (bi<stop)
+        {
+            b0=b[bi++];
+            b1=b[bi++];
+            b2=b[bi++];
+            c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+            c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+            c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
+            c[ci++]=__rfc1421alphabet[b2&0x3f];
+            l+=4;
+            if (l%76==0)
+            {
+                c[ci++]=13;
+                c[ci++]=10;
+            }
+        }
+
+        if (bLen!=bi)
+        {
+            switch (bLen%3)
+            {
+                case 2:
+                    b0=b[bi++];
+                    b1=b[bi++];
+                    c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+                    c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+                    c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f];
+                    c[ci++]=__pad;
+                    break;
+
+                case 1:
+                    b0=b[bi++];
+                    c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+                    c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f];
+                    c[ci++]=__pad;
+                    c[ci++]=__pad;
+                    break;
+
+                default:
+                    break;
+            }
+        }
+
+        c[ci++]=13;
+        c[ci++]=10;
+        return c;
+    }
+
+    /**
+     * Base 64 decode as described in RFC 2045.
+     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+     * @param encoded String to decode.
+     * @param charEncoding String representing the character encoding
+     *        used to map the decoded bytes into a String.
+     * @return String decoded byte array.
+     * @throws UnsupportedCharsetException if the encoding is not supported
+     * @throws IllegalArgumentException if the input is not a valid
+     *         B64 encoding.
+     */
+    public static String decode(String encoded,String charEncoding)
+    {
+        byte[] decoded=decode(encoded);
+        if (charEncoding==null)
+            return new String(decoded);
+        return new String(decoded,Charset.forName(charEncoding));
+    }
+
+    /**
+     * Base 64 decode as described in RFC 2045.
+     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+     * @param encoded String to decode.
+     * @param charEncoding Character encoding
+     *        used to map the decoded bytes into a String.
+     * @return String decoded byte array.
+     * @throws IllegalArgumentException if the input is not a valid
+     *         B64 encoding.
+     */
+    public static String decode(String encoded, Charset charEncoding)
+    {
+        byte[] decoded=decode(encoded);
+        if (charEncoding==null)
+            return new String(decoded);
+        return new String(decoded, charEncoding);
+    }
+
+    /**
+     * Fast Base 64 decode as described in RFC 1421.
+     *
+     * <p>Unlike other decode methods, this does not attempt to
+     * cope with extra whitespace as described in RFC 1521/2045.
+     * <p> Avoids creating extra copies of the input/output.
+     * <p> Note this code has been flattened for performance.
+     * @param b char array to decode.
+     * @return byte array containing the decoded form of the input.
+     * @throws IllegalArgumentException if the input is not a valid
+     *         B64 encoding.
+     */
+    public static byte[] decode(char[] b)
+    {
+        if (b==null)
+            return null;
+
+        int bLen=b.length;
+        if (bLen%4!=0)
+            throw new IllegalArgumentException("Input block size is not 4");
+
+        int li=bLen-1;
+        while (li>=0 && b[li]==(byte)__pad)
+            li--;
+
+        if (li<0)
+            return new byte[0];
+
+        // Create result array of exact required size.
+        int rLen=((li+1)*3)/4;
+        byte r[]=new byte[rLen];
+        int ri=0;
+        int bi=0;
+        int stop=(rLen/3)*3;
+        byte b0,b1,b2,b3;
+        try
+        {
+            while (ri<stop)
+            {
+                b0=__rfc1421nibbles[b[bi++]];
+                b1=__rfc1421nibbles[b[bi++]];
+                b2=__rfc1421nibbles[b[bi++]];
+                b3=__rfc1421nibbles[b[bi++]];
+                if (b0<0 || b1<0 || b2<0 || b3<0)
+                    throw new IllegalArgumentException("Not B64 encoded");
+
+                r[ri++]=(byte)(b0<<2|b1>>>4);
+                r[ri++]=(byte)(b1<<4|b2>>>2);
+                r[ri++]=(byte)(b2<<6|b3);
+            }
+
+            if (rLen!=ri)
+            {
+                switch (rLen%3)
+                {
+                    case 2:
+                        b0=__rfc1421nibbles[b[bi++]];
+                        b1=__rfc1421nibbles[b[bi++]];
+                        b2=__rfc1421nibbles[b[bi++]];
+                        if (b0<0 || b1<0 || b2<0)
+                            throw new IllegalArgumentException("Not B64 encoded");
+                        r[ri++]=(byte)(b0<<2|b1>>>4);
+                        r[ri++]=(byte)(b1<<4|b2>>>2);
+                        break;
+
+                    case 1:
+                        b0=__rfc1421nibbles[b[bi++]];
+                        b1=__rfc1421nibbles[b[bi++]];
+                        if (b0<0 || b1<0)
+                            throw new IllegalArgumentException("Not B64 encoded");
+                        r[ri++]=(byte)(b0<<2|b1>>>4);
+                        break;
+
+                    default:
+                        break;
+                }
+            }
+        }
+        catch (IndexOutOfBoundsException e)
+        {
+            throw new IllegalArgumentException("char "+bi
+                    +" was not B64 encoded");
+        }
+
+        return r;
+    }
+
+    /**
+     * Base 64 decode as described in RFC 2045.
+     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+     * @param encoded String to decode.
+     * @return byte array containing the decoded form of the input.
+     * @throws IllegalArgumentException if the input is not a valid
+     *         B64 encoding.
+     */
+    public static byte[] decode(String encoded)
+    {
+        if (encoded==null)
+            return null;
+
+        ByteArrayOutputStream bout = new ByteArrayOutputStream(4*encoded.length()/3);        
+        decode(encoded, bout);
+        return bout.toByteArray();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Base 64 decode as described in RFC 2045.
+     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+     * @param encoded String to decode.
+     * @param bout stream for decoded bytes
+     * @throws IllegalArgumentException if the input is not a valid
+     *         B64 encoding.
+     */
+    static public void decode (String encoded, ByteArrayOutputStream bout)
+    {
+        if (encoded==null)
+            return;
+        
+        if (bout == null)
+            throw new IllegalArgumentException("No outputstream for decoded bytes");
+        
+        int ci=0;
+        byte nibbles[] = new byte[4];
+        int s=0;
+  
+        while (ci<encoded.length())
+        {
+            char c=encoded.charAt(ci++);
+
+            if (c==__pad)
+                break;
+
+            if (Character.isWhitespace(c))
+                continue;
+
+            byte nibble=__rfc1421nibbles[c];
+            if (nibble<0)
+                throw new IllegalArgumentException("Not B64 encoded");
+
+            nibbles[s++]=__rfc1421nibbles[c];
+
+            switch(s)
+            {
+                case 1:
+                    break;
+                case 2:
+                    bout.write(nibbles[0]<<2|nibbles[1]>>>4);
+                    break;
+                case 3:
+                    bout.write(nibbles[1]<<4|nibbles[2]>>>2);
+                    break;
+                case 4:
+                    bout.write(nibbles[2]<<6|nibbles[3]);
+                    s=0;
+                    break;
+            }
+
+        }
+
+        return;
+    }
+    
+
+    public static void encode(int value,Appendable buf) throws IOException
+    {
+        buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x03F00000&value)>>20)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x000FC000&value)>>14)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x00003F00&value)>>8)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x000000FC&value)>>2)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4)]);
+        buf.append('=');
+    }
+
+    public static void encode(long lvalue,Appendable buf) throws IOException
+    {
+        int value=(int)(0xFFFFFFFC&(lvalue>>32));
+        buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x03F00000&value)>>20)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x000FC000&value)>>14)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x00003F00&value)>>8)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x000000FC&value)>>2)]);
+
+        buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4) + (0xf&(int)(lvalue>>28))]);
+
+        value=0x0FFFFFFF&(int)lvalue;
+        buf.append(__rfc1421alphabet[0x3f&((0x0FC00000&value)>>22)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x003F0000&value)>>16)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x0000FC00&value)>>10)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x000003F0&value)>>4)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x0000000F&value)<<2)]);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/BlockingArrayQueue.java b/lib/jetty/org/eclipse/jetty/util/BlockingArrayQueue.java
new file mode 100644 (file)
index 0000000..7acbdaf
--- /dev/null
@@ -0,0 +1,880 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.AbstractList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A BlockingQueue backed by a circular array capable or growing.
+ * <p/>
+ * This queue is uses a variant of the two lock queue algorithm to provide an efficient queue or list backed by a growable circular array.
+ * <p/>
+ * Unlike {@link java.util.concurrent.ArrayBlockingQueue}, this class is able to grow and provides a blocking put call.
+ * <p/>
+ * The queue has both a capacity (the size of the array currently allocated) and a max capacity (the maximum size that may be allocated), which defaults to
+ * {@link Integer#MAX_VALUE}.
+ * 
+ * @param <E>
+ *            The element type
+ */
+public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQueue<E>
+{
+    /**
+     * The head offset in the {@link #_indexes} array, displaced by 15 slots to avoid false sharing with the array length (stored before the first element of
+     * the array itself).
+     */
+    private static final int HEAD_OFFSET = MemoryUtils.getIntegersPerCacheLine() - 1;
+    /**
+     * The tail offset in the {@link #_indexes} array, displaced by 16 slots from the head to avoid false sharing with it.
+     */
+    private static final int TAIL_OFFSET = HEAD_OFFSET + MemoryUtils.getIntegersPerCacheLine();
+    /**
+     * Default initial capacity, 128.
+     */
+    public static final int DEFAULT_CAPACITY = 128;
+    /**
+     * Default growth factor, 64.
+     */
+    public static final int DEFAULT_GROWTH = 64;
+
+    private final int _maxCapacity;
+    private final int _growCapacity;
+    /**
+     * Array that holds the head and tail indexes, separated by a cache line to avoid false sharing
+     */
+    private final int[] _indexes = new int[TAIL_OFFSET + 1];
+    private final Lock _tailLock = new ReentrantLock();
+    private final AtomicInteger _size = new AtomicInteger();
+    private final Lock _headLock = new ReentrantLock();
+    private final Condition _notEmpty = _headLock.newCondition();
+    private Object[] _elements;
+
+    /**
+     * Creates an unbounded {@link BlockingArrayQueue} with default initial capacity and grow factor.
+     * 
+     * @see #DEFAULT_CAPACITY
+     * @see #DEFAULT_GROWTH
+     */
+    public BlockingArrayQueue()
+    {
+        _elements = new Object[DEFAULT_CAPACITY];
+        _growCapacity = DEFAULT_GROWTH;
+        _maxCapacity = Integer.MAX_VALUE;
+    }
+
+    /**
+     * Creates a bounded {@link BlockingArrayQueue} that does not grow. The capacity of the queue is fixed and equal to the given parameter.
+     * 
+     * @param maxCapacity
+     *            the maximum capacity
+     */
+    public BlockingArrayQueue(int maxCapacity)
+    {
+        _elements = new Object[maxCapacity];
+        _growCapacity = -1;
+        _maxCapacity = maxCapacity;
+    }
+
+    /**
+     * Creates an unbounded {@link BlockingArrayQueue} that grows by the given parameter.
+     * 
+     * @param capacity
+     *            the initial capacity
+     * @param growBy
+     *            the growth factor
+     */
+    public BlockingArrayQueue(int capacity, int growBy)
+    {
+        _elements = new Object[capacity];
+        _growCapacity = growBy;
+        _maxCapacity = Integer.MAX_VALUE;
+    }
+
+    /**
+     * Create a bounded {@link BlockingArrayQueue} that grows by the given parameter.
+     * 
+     * @param capacity
+     *            the initial capacity
+     * @param growBy
+     *            the growth factor
+     * @param maxCapacity
+     *            the maximum capacity
+     */
+    public BlockingArrayQueue(int capacity, int growBy, int maxCapacity)
+    {
+        if (capacity > maxCapacity)
+            throw new IllegalArgumentException();
+        _elements = new Object[capacity];
+        _growCapacity = growBy;
+        _maxCapacity = maxCapacity;
+    }
+
+    /*----------------------------------------------------------------------------*/
+    /* Collection methods */
+    /*----------------------------------------------------------------------------*/
+
+    @Override
+    public void clear()
+    {
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                _indexes[HEAD_OFFSET] = 0;
+                _indexes[TAIL_OFFSET] = 0;
+                _size.set(0);
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    @Override
+    public int size()
+    {
+        return _size.get();
+    }
+
+    @Override
+    public Iterator<E> iterator()
+    {
+        return listIterator();
+    }
+
+    /*----------------------------------------------------------------------------*/
+    /* Queue methods */
+    /*----------------------------------------------------------------------------*/
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public E poll()
+    {
+        if (_size.get() == 0)
+            return null;
+
+        E e = null;
+
+        _headLock.lock(); // Size cannot shrink
+        try
+        {
+            if (_size.get() > 0)
+            {
+                final int head = _indexes[HEAD_OFFSET];
+                e = (E)_elements[head];
+                _elements[head] = null;
+                _indexes[HEAD_OFFSET] = (head + 1) % _elements.length;
+                if (_size.decrementAndGet() > 0)
+                    _notEmpty.signal();
+            }
+        }
+        finally
+        {
+            _headLock.unlock();
+        }
+        return e;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public E peek()
+    {
+        if (_size.get() == 0)
+            return null;
+
+        E e = null;
+
+        _headLock.lock(); // Size cannot shrink
+        try
+        {
+            if (_size.get() > 0)
+                e = (E)_elements[_indexes[HEAD_OFFSET]];
+        }
+        finally
+        {
+            _headLock.unlock();
+        }
+        return e;
+    }
+
+    @Override
+    public E remove()
+    {
+        E e = poll();
+        if (e == null)
+            throw new NoSuchElementException();
+        return e;
+    }
+
+    @Override
+    public E element()
+    {
+        E e = peek();
+        if (e == null)
+            throw new NoSuchElementException();
+        return e;
+    }
+
+    /*----------------------------------------------------------------------------*/
+    /* BlockingQueue methods */
+    /*----------------------------------------------------------------------------*/
+
+    @Override
+    public boolean offer(E e)
+    {
+        Objects.requireNonNull(e);
+
+        boolean notEmpty = false;
+        _tailLock.lock(); // Size cannot grow... only shrink
+        try
+        {
+            int size = _size.get();
+            if (size >= _maxCapacity)
+                return false;
+
+            // Should we expand array?
+            if (size == _elements.length)
+            {
+                _headLock.lock();
+                try
+                {
+                    if (!grow())
+                        return false;
+                }
+                finally
+                {
+                    _headLock.unlock();
+                }
+            }
+
+            // Re-read head and tail after a possible grow
+            int tail = _indexes[TAIL_OFFSET];
+            _elements[tail] = e;
+            _indexes[TAIL_OFFSET] = (tail + 1) % _elements.length;
+            notEmpty = _size.getAndIncrement() == 0;
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+
+        if (notEmpty)
+        {
+            _headLock.lock();
+            try
+            {
+                _notEmpty.signal();
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean add(E e)
+    {
+        if (offer(e))
+            return true;
+        else
+            throw new IllegalStateException();
+    }
+
+    @Override
+    public void put(E o) throws InterruptedException
+    {
+        // The mechanism to await and signal when the queue is full is not implemented
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean offer(E o, long timeout, TimeUnit unit) throws InterruptedException
+    {
+        // The mechanism to await and signal when the queue is full is not implemented
+        throw new UnsupportedOperationException();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public E take() throws InterruptedException
+    {
+        E e = null;
+
+        _headLock.lockInterruptibly(); // Size cannot shrink
+        try
+        {
+            try
+            {
+                while (_size.get() == 0)
+                {
+                    _notEmpty.await();
+                }
+            }
+            catch (InterruptedException ie)
+            {
+                _notEmpty.signal();
+                throw ie;
+            }
+
+            final int head = _indexes[HEAD_OFFSET];
+            e = (E)_elements[head];
+            _elements[head] = null;
+            _indexes[HEAD_OFFSET] = (head + 1) % _elements.length;
+
+            if (_size.decrementAndGet() > 0)
+                _notEmpty.signal();
+        }
+        finally
+        {
+            _headLock.unlock();
+        }
+        return e;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public E poll(long time, TimeUnit unit) throws InterruptedException
+    {
+        long nanos = unit.toNanos(time);
+        E e = null;
+
+        _headLock.lockInterruptibly(); // Size cannot shrink
+        try
+        {
+            try
+            {
+                while (_size.get() == 0)
+                {
+                    if (nanos <= 0)
+                        return null;
+                    nanos = _notEmpty.awaitNanos(nanos);
+                }
+            }
+            catch (InterruptedException x)
+            {
+                _notEmpty.signal();
+                throw x;
+            }
+
+            int head = _indexes[HEAD_OFFSET];
+            e = (E)_elements[head];
+            _elements[head] = null;
+            _indexes[HEAD_OFFSET] = (head + 1) % _elements.length;
+
+            if (_size.decrementAndGet() > 0)
+                _notEmpty.signal();
+        }
+        finally
+        {
+            _headLock.unlock();
+        }
+        return e;
+    }
+
+    @Override
+    public boolean remove(Object o)
+    {
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                if (isEmpty())
+                    return false;
+
+                final int head = _indexes[HEAD_OFFSET];
+                final int tail = _indexes[TAIL_OFFSET];
+                final int capacity = _elements.length;
+
+                int i = head;
+                while (true)
+                {
+                    if (Objects.equals(_elements[i],o))
+                    {
+                        remove(i >= head?i - head:capacity - head + i);
+                        return true;
+                    }
+                    ++i;
+                    if (i == capacity)
+                        i = 0;
+                    if (i == tail)
+                        return false;
+                }
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    @Override
+    public int remainingCapacity()
+    {
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                return getCapacity() - size();
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    @Override
+    public int drainTo(Collection<? super E> c)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int drainTo(Collection<? super E> c, int maxElements)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /*----------------------------------------------------------------------------*/
+    /* List methods */
+    /*----------------------------------------------------------------------------*/
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public E get(int index)
+    {
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                if (index < 0 || index >= _size.get())
+                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+                int i = _indexes[HEAD_OFFSET] + index;
+                int capacity = _elements.length;
+                if (i >= capacity)
+                    i -= capacity;
+                return (E)_elements[i];
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    @Override
+    public void add(int index, E e)
+    {
+        if (e == null)
+            throw new NullPointerException();
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                final int size = _size.get();
+
+                if (index < 0 || index > size)
+                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+                if (index == size)
+                {
+                    add(e);
+                }
+                else
+                {
+                    if (_indexes[TAIL_OFFSET] == _indexes[HEAD_OFFSET])
+                        if (!grow())
+                            throw new IllegalStateException("full");
+
+                    // Re-read head and tail after a possible grow
+                    int i = _indexes[HEAD_OFFSET] + index;
+                    int capacity = _elements.length;
+
+                    if (i >= capacity)
+                        i -= capacity;
+
+                    _size.incrementAndGet();
+                    int tail = _indexes[TAIL_OFFSET];
+                    _indexes[TAIL_OFFSET] = tail = (tail + 1) % capacity;
+
+                    if (i < tail)
+                    {
+                        System.arraycopy(_elements,i,_elements,i + 1,tail - i);
+                        _elements[i] = e;
+                    }
+                    else
+                    {
+                        if (tail > 0)
+                        {
+                            System.arraycopy(_elements,0,_elements,1,tail);
+                            _elements[0] = _elements[capacity - 1];
+                        }
+
+                        System.arraycopy(_elements,i,_elements,i + 1,capacity - i - 1);
+                        _elements[i] = e;
+                    }
+                }
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public E set(int index, E e)
+    {
+        Objects.requireNonNull(e);
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                if (index < 0 || index >= _size.get())
+                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+                int i = _indexes[HEAD_OFFSET] + index;
+                int capacity = _elements.length;
+                if (i >= capacity)
+                    i -= capacity;
+                E old = (E)_elements[i];
+                _elements[i] = e;
+                return old;
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public E remove(int index)
+    {
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                if (index < 0 || index >= _size.get())
+                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+                int i = _indexes[HEAD_OFFSET] + index;
+                int capacity = _elements.length;
+                if (i >= capacity)
+                    i -= capacity;
+                E old = (E)_elements[i];
+
+                int tail = _indexes[TAIL_OFFSET];
+                if (i < tail)
+                {
+                    System.arraycopy(_elements,i + 1,_elements,i,tail - i);
+                    --_indexes[TAIL_OFFSET];
+                }
+                else
+                {
+                    System.arraycopy(_elements,i + 1,_elements,i,capacity - i - 1);
+                    _elements[capacity - 1] = _elements[0];
+                    if (tail > 0)
+                    {
+                        System.arraycopy(_elements,1,_elements,0,tail);
+                        --_indexes[TAIL_OFFSET];
+                    }
+                    else
+                    {
+                        _indexes[TAIL_OFFSET] = capacity - 1;
+                    }
+                    _elements[_indexes[TAIL_OFFSET]] = null;
+                }
+
+                _size.decrementAndGet();
+
+                return old;
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    @Override
+    public ListIterator<E> listIterator(int index)
+    {
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                Object[] elements = new Object[size()];
+                if (size() > 0)
+                {
+                    int head = _indexes[HEAD_OFFSET];
+                    int tail = _indexes[TAIL_OFFSET];
+                    if (head < tail)
+                    {
+                        System.arraycopy(_elements,head,elements,0,tail - head);
+                    }
+                    else
+                    {
+                        int chunk = _elements.length - head;
+                        System.arraycopy(_elements,head,elements,0,chunk);
+                        System.arraycopy(_elements,0,elements,chunk,tail);
+                    }
+                }
+                return new Itr(elements,index);
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    /*----------------------------------------------------------------------------*/
+    /* Additional methods */
+    /*----------------------------------------------------------------------------*/
+
+    /**
+     * @return the current capacity of this queue
+     */
+    public int getCapacity()
+    {
+        _tailLock.lock();
+        try
+        {
+            return _elements.length;
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    /**
+     * @return the max capacity of this queue, or -1 if this queue is unbounded
+     */
+    public int getMaxCapacity()
+    {
+        return _maxCapacity;
+    }
+
+    /*----------------------------------------------------------------------------*/
+    /* Implementation methods */
+    /*----------------------------------------------------------------------------*/
+
+    private boolean grow()
+    {
+        if (_growCapacity <= 0)
+            return false;
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                final int head = _indexes[HEAD_OFFSET];
+                final int tail = _indexes[TAIL_OFFSET];
+                final int newTail;
+                final int capacity = _elements.length;
+
+                Object[] elements = new Object[capacity + _growCapacity];
+
+                if (head < tail)
+                {
+                    newTail = tail - head;
+                    System.arraycopy(_elements,head,elements,0,newTail);
+                }
+                else if (head > tail || _size.get() > 0)
+                {
+                    newTail = capacity + tail - head;
+                    int cut = capacity - head;
+                    System.arraycopy(_elements,head,elements,0,cut);
+                    System.arraycopy(_elements,0,elements,cut,tail);
+                }
+                else
+                {
+                    newTail = 0;
+                }
+
+                _elements = elements;
+                _indexes[HEAD_OFFSET] = 0;
+                _indexes[TAIL_OFFSET] = newTail;
+                return true;
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    private class Itr implements ListIterator<E>
+    {
+        private final Object[] _elements;
+        private int _cursor;
+
+        public Itr(Object[] elements, int offset)
+        {
+            _elements = elements;
+            _cursor = offset;
+        }
+
+        @Override
+        public boolean hasNext()
+        {
+            return _cursor < _elements.length;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public E next()
+        {
+            return (E)_elements[_cursor++];
+        }
+
+        @Override
+        public boolean hasPrevious()
+        {
+            return _cursor > 0;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public E previous()
+        {
+            return (E)_elements[--_cursor];
+        }
+
+        @Override
+        public int nextIndex()
+        {
+            return _cursor + 1;
+        }
+
+        @Override
+        public int previousIndex()
+        {
+            return _cursor - 1;
+        }
+
+        @Override
+        public void remove()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void set(E e)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void add(E e)
+        {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/BlockingCallback.java b/lib/jetty/org/eclipse/jetty/util/BlockingCallback.java
new file mode 100644 (file)
index 0000000..a3cf9da
--- /dev/null
@@ -0,0 +1,105 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+
+/* ------------------------------------------------------------ */
+/**
+ * An implementation of Callback that blocks until success or failure.
+ */
+public class BlockingCallback implements Callback
+{
+    private static final Logger LOG = Log.getLogger(BlockingCallback.class);
+    
+    private static Throwable SUCCEEDED=new Throwable()
+    {
+        @Override
+        public String toString() { return "SUCCEEDED"; }
+    };
+    
+    private final CountDownLatch _latch = new CountDownLatch(1);
+    private final AtomicReference<Throwable> _state = new AtomicReference<>();
+    
+    public BlockingCallback()
+    {}
+
+    @Override
+    public void succeeded()
+    {
+        if (_state.compareAndSet(null,SUCCEEDED))
+            _latch.countDown();
+    }
+
+    @Override
+    public void failed(Throwable cause)
+    {
+        if (_state.compareAndSet(null,cause))
+            _latch.countDown();
+    }
+
+    /** Block until the Callback has succeeded or failed and 
+     * after the return leave in the state to allow reuse.
+     * This is useful for code that wants to repeatable use a FutureCallback to convert
+     * an asynchronous API to a blocking API. 
+     * @throws IOException if exception was caught during blocking, or callback was cancelled 
+     */
+    public void block() throws IOException
+    {
+        if (NonBlockingThread.isNonBlockingThread())
+            LOG.warn("Blocking a NonBlockingThread: ",new Throwable());
+        
+        try
+        {
+            _latch.await();
+            Throwable state=_state.get();
+            if (state==SUCCEEDED)
+                return;
+            if (state instanceof IOException)
+                throw (IOException) state;
+            if (state instanceof CancellationException)
+                throw (CancellationException) state;
+            throw new IOException(state);
+        }
+        catch (final InterruptedException e)
+        {
+            throw new InterruptedIOException(){{initCause(e);}};
+        }
+        finally
+        {
+            _state.set(null);
+        }
+    }
+    
+    
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}",BlockingCallback.class.getSimpleName(),hashCode(),_state.get());
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/BufferUtil.java b/lib/jetty/org/eclipse/jetty/util/BufferUtil.java
new file mode 100644 (file)
index 0000000..bd7cb06
--- /dev/null
@@ -0,0 +1,1045 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.Buffer;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------------------------- */
+/**
+ * Buffer utility methods.
+ * <p>The standard JVM {@link ByteBuffer} can exist in two modes: In fill mode the valid
+ * data is between 0 and pos; In flush mode the valid data is between the pos and the limit.
+ * The various ByteBuffer methods assume a mode and some of them will switch or enforce a mode:
+ * Allocate and clear set fill mode; flip and compact switch modes; read and write assume fill 
+ * and flush modes.    This duality can result in confusing code such as:
+ * <pre>
+ *     buffer.clear();
+ *     channel.write(buffer);
+ * </pre>
+ * Which looks as if it should write no data, but in fact writes the buffer worth of garbage.
+ * </p>
+ * <p>
+ * The BufferUtil class provides a set of utilities that operate on the convention that ByteBuffers
+ * will always be left, passed in an API or returned from a method in the flush mode - ie with
+ * valid data between the pos and limit.    This convention is adopted so as to avoid confusion as to
+ * what state a buffer is in and to avoid excessive copying of data that can result with the usage 
+ * of compress.</p> 
+ * <p>
+ * Thus this class provides alternate implementations of {@link #allocate(int)}, 
+ * {@link #allocateDirect(int)} and {@link #clear(ByteBuffer)} that leave the buffer
+ * in flush mode.   Thus the following tests will pass:<pre>
+ *     ByteBuffer buffer = BufferUtil.allocate(1024);
+ *     assert(buffer.remaining()==0);
+ *     BufferUtil.clear(buffer);
+ *     assert(buffer.remaining()==0);
+ * </pre>
+ * </p>
+ * <p>If the BufferUtil methods {@link #fill(ByteBuffer, byte[], int, int)}, 
+ * {@link #append(ByteBuffer, byte[], int, int)} or {@link #put(ByteBuffer, ByteBuffer)} are used,
+ * then the caller does not need to explicitly switch the buffer to fill mode.    
+ * If the caller wishes to use other ByteBuffer bases libraries to fill a buffer, 
+ * then they can use explicit calls of #flipToFill(ByteBuffer) and #flipToFlush(ByteBuffer, int)
+ * to change modes.  Note because this convention attempts to avoid the copies of compact, the position
+ * is not set to zero on each fill cycle and so its value must be remembered:
+ * <pre>
+ *      int pos = BufferUtil.flipToFill(buffer);
+ *      try
+ *      {
+ *          buffer.put(data);
+ *      }
+ *      finally
+ *      {
+ *          flipToFlush(buffer, pos);
+ *      }
+ * </pre>
+ * The flipToFill method will effectively clear the buffer if it is emtpy and will compact the buffer if there is no space.
+ * 
+ */
+public class BufferUtil
+{
+    static final int TEMP_BUFFER_SIZE = 4096;
+    static final byte SPACE = 0x20;
+    static final byte MINUS = '-';
+    static final byte[] DIGIT =
+            {(byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D',
+                    (byte)'E', (byte)'F'};
+
+    public static final ByteBuffer EMPTY_BUFFER = ByteBuffer.wrap(new byte[0]);
+
+    /* ------------------------------------------------------------ */
+    /** Allocate ByteBuffer in flush mode.
+     * The position and limit will both be zero, indicating that the buffer is
+     * empty and must be flipped before any data is put to it.
+     * @param capacity capacity of the allocated ByteBuffer
+     * @return Buffer
+     */
+    public static ByteBuffer allocate(int capacity)
+    {
+        ByteBuffer buf = ByteBuffer.allocate(capacity);
+        buf.limit(0);
+        return buf;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Allocate ByteBuffer in flush mode.
+     * The position and limit will both be zero, indicating that the buffer is
+     * empty and in flush mode.
+     * @param capacity capacity of the allocated ByteBuffer
+     * @return Buffer
+     */
+    public static ByteBuffer allocateDirect(int capacity)
+    {
+        ByteBuffer buf = ByteBuffer.allocateDirect(capacity);
+        buf.limit(0);
+        return buf;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Clear the buffer to be empty in flush mode.
+     * The position and limit are set to 0;
+     * @param buffer The buffer to clear.
+     */
+    public static void clear(ByteBuffer buffer)
+    {
+        if (buffer != null)
+        {
+            buffer.position(0);
+            buffer.limit(0);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Clear the buffer to be empty in fill mode.
+     * The position is set to 0 and the limit is set to the capacity.
+     * @param buffer The buffer to clear.
+     */
+    public static void clearToFill(ByteBuffer buffer)
+    {
+        if (buffer != null)
+        {
+            buffer.position(0);
+            buffer.limit(buffer.capacity());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Flip the buffer to fill mode.
+     * The position is set to the first unused position in the buffer
+     * (the old limit) and the limit is set to the capacity.
+     * If the buffer is empty, then this call is effectively {@link #clearToFill(ByteBuffer)}.
+     * If there is no unused space to fill, a {@link ByteBuffer#compact()} is done to attempt
+     * to create space.
+     * <p>
+     * This method is used as a replacement to {@link ByteBuffer#compact()}.
+     *
+     * @param buffer The buffer to flip
+     * @return The position of the valid data before the flipped position. This value should be
+     * passed to a subsequent call to {@link #flipToFlush(ByteBuffer, int)}
+     */
+    public static int flipToFill(ByteBuffer buffer)
+    {
+        int position = buffer.position();
+        int limit = buffer.limit();
+        if (position == limit)
+        {
+            buffer.position(0);
+            buffer.limit(buffer.capacity());
+            return 0;
+        }
+
+        int capacity = buffer.capacity();
+        if (limit == capacity)
+        {
+            buffer.compact();
+            return 0;
+        }
+
+        buffer.position(limit);
+        buffer.limit(capacity);
+        return position;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Flip the buffer to Flush mode.
+     * The limit is set to the first unused byte(the old position) and
+     * the position is set to the passed position.
+     * <p>
+     * This method is used as a replacement of {@link Buffer#flip()}.
+     * @param buffer   the buffer to be flipped
+     * @param position The position of valid data to flip to. This should
+     * be the return value of the previous call to {@link #flipToFill(ByteBuffer)}
+     */
+    public static void flipToFlush(ByteBuffer buffer, int position)
+    {
+        buffer.limit(buffer.position());
+        buffer.position(position);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Convert a ByteBuffer to a byte array.
+     * @param buffer The buffer to convert in flush mode. The buffer is not altered.
+     * @return An array of bytes duplicated from the buffer.
+     */
+    public static byte[] toArray(ByteBuffer buffer)
+    {
+        if (buffer.hasArray())
+        {
+            byte[] array = buffer.array();
+            int from=buffer.arrayOffset() + buffer.position();
+            return Arrays.copyOfRange(array,from,from+buffer.remaining());
+        }
+        else
+        {
+            byte[] to = new byte[buffer.remaining()];
+            buffer.slice().get(to);
+            return to;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Check for an empty or null buffer.
+     * @param buf the buffer to check
+     * @return true if the buffer is null or empty.
+     */
+    public static boolean isEmpty(ByteBuffer buf)
+    {
+        return buf == null || buf.remaining() == 0;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Check for a non null and non empty buffer.
+     * @param buf the buffer to check
+     * @return true if the buffer is not null and not empty.
+     */
+    public static boolean hasContent(ByteBuffer buf)
+    {
+        return buf != null && buf.remaining() > 0;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Check for a non null and full buffer.
+     * @param buf the buffer to check
+     * @return true if the buffer is not null and the limit equals the capacity.
+     */
+    public static boolean isFull(ByteBuffer buf)
+    {
+        return buf != null && buf.limit() == buf.capacity();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get remaining from null checked buffer
+     * @param buffer The buffer to get the remaining from, in flush mode.
+     * @return 0 if the buffer is null, else the bytes remaining in the buffer.
+     */
+    public static int length(ByteBuffer buffer)
+    {
+        return buffer == null ? 0 : buffer.remaining();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the space from the limit to the capacity
+     * @param buffer the buffer to get the space from
+     * @return space
+     */
+    public static int space(ByteBuffer buffer)
+    {
+        if (buffer == null)
+            return 0;
+        return buffer.capacity() - buffer.limit();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Compact the buffer
+     * @param buffer the buffer to compact
+     * @return true if the compact made a full buffer have space
+     */
+    public static boolean compact(ByteBuffer buffer)
+    {
+        if (buffer.position()==0)
+            return false;
+        boolean full = buffer.limit() == buffer.capacity();
+        buffer.compact().flip();
+        return full && buffer.limit() < buffer.capacity();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Put data from one buffer into another, avoiding over/under flows
+     * @param from Buffer to take bytes from in flush mode
+     * @param to   Buffer to put bytes to in fill mode.
+     * @return number of bytes moved
+     */
+    public static int put(ByteBuffer from, ByteBuffer to)
+    {
+        int put;
+        int remaining = from.remaining();
+        if (remaining > 0)
+        {
+            if (remaining <= to.remaining())
+            {
+                to.put(from);
+                put = remaining;
+                from.position(0);
+                from.limit(0);
+            }
+            else if (from.hasArray())
+            {
+                put = to.remaining();
+                to.put(from.array(), from.arrayOffset() + from.position(), put);
+                from.position(from.position() + put);
+            }
+            else
+            {
+                put = to.remaining();
+                ByteBuffer slice = from.slice();
+                slice.limit(put);
+                to.put(slice);
+                from.position(from.position() + put);
+            }
+        }
+        else
+            put = 0;
+
+        return put;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Put data from one buffer into another, avoiding over/under flows
+     * @param from Buffer to take bytes from in flush mode
+     * @param to   Buffer to put bytes to in flush mode. The buffer is flipToFill before the put and flipToFlush after.
+     * @return number of bytes moved
+     * @deprecated use {@link #append(ByteBuffer, ByteBuffer)}
+     */
+    public static int flipPutFlip(ByteBuffer from, ByteBuffer to)
+    {
+        return append(to,from);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Append bytes to a buffer.
+     * @param to Buffer is flush mode
+     * @param b bytes to append
+     * @param off offset into byte
+     * @param len length to append
+     * @throws BufferOverflowException
+     */
+    public static void append(ByteBuffer to, byte[] b, int off, int len) throws BufferOverflowException
+    {
+        int pos = flipToFill(to);
+        try
+        {
+            to.put(b, off, len);
+        }
+        finally
+        {
+            flipToFlush(to, pos);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Appends a byte to a buffer
+     * @param to Buffer is flush mode
+     * @param b byte to append
+     */
+    public static void append(ByteBuffer to, byte b)
+    {
+        int pos = flipToFill(to);
+        try
+        {
+            to.put(b);
+        }
+        finally
+        {
+            flipToFlush(to, pos);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Appends a byte to a buffer
+     * @param to Buffer is flush mode
+     * @param b bytes to append
+     */
+    public static int append(ByteBuffer to, ByteBuffer b)
+    {
+        int pos = flipToFill(to);
+        try
+        {
+            return put(b, to);
+        }
+        finally
+        {
+            flipToFlush(to, pos);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Like append, but does not throw {@link BufferOverflowException}
+     * @param to Buffer is flush mode
+     * @param b bytes to fill
+     * @param off offset into byte
+     * @param len length to fill
+     */
+    public static int fill(ByteBuffer to, byte[] b, int off, int len)
+    {
+        int pos = flipToFill(to);
+        try
+        {
+            int remaining = to.remaining();
+            int take = remaining < len ? remaining : len;
+            to.put(b, off, take);
+            return take;
+        }
+        finally
+        {
+            flipToFlush(to, pos);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static void readFrom(File file, ByteBuffer buffer) throws IOException
+    {
+        try(RandomAccessFile raf = new RandomAccessFile(file,"r"))
+        {
+            FileChannel channel = raf.getChannel();
+            long needed=raf.length();
+
+            while (needed>0 && buffer.hasRemaining())
+                needed=needed-channel.read(buffer);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void readFrom(InputStream is, int needed, ByteBuffer buffer) throws IOException
+    {
+        ByteBuffer tmp = allocate(8192);
+
+        while (needed > 0 && buffer.hasRemaining())
+        {
+            int l = is.read(tmp.array(), 0, 8192);
+            if (l < 0)
+                break;
+            tmp.position(0);
+            tmp.limit(l);
+            buffer.put(tmp);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void writeTo(ByteBuffer buffer, OutputStream out) throws IOException
+    {
+        if (buffer.hasArray())
+            out.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
+        else
+        {
+            byte[] bytes = new byte[TEMP_BUFFER_SIZE];
+            while(buffer.hasRemaining()){
+                int byteCountToWrite = Math.min(buffer.remaining(), TEMP_BUFFER_SIZE);
+                buffer.get(bytes, 0, byteCountToWrite);
+                out.write(bytes,0 , byteCountToWrite);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert the buffer to an ISO-8859-1 String
+     * @param buffer The buffer to convert in flush mode. The buffer is unchanged
+     * @return The buffer as a string.
+     */
+    public static String toString(ByteBuffer buffer)
+    {
+        return toString(buffer, StandardCharsets.ISO_8859_1);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert the buffer to an UTF-8 String
+     * @param buffer The buffer to convert in flush mode. The buffer is unchanged
+     * @return The buffer as a string.
+     */
+    public static String toUTF8String(ByteBuffer buffer)
+    {
+        return toString(buffer, StandardCharsets.UTF_8);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert the buffer to an ISO-8859-1 String
+     * @param buffer  The buffer to convert in flush mode. The buffer is unchanged
+     * @param charset The {@link Charset} to use to convert the bytes
+     * @return The buffer as a string.
+     */
+    public static String toString(ByteBuffer buffer, Charset charset)
+    {
+        if (buffer == null)
+            return null;
+        byte[] array = buffer.hasArray() ? buffer.array() : null;
+        if (array == null)
+        {
+            byte[] to = new byte[buffer.remaining()];
+            buffer.slice().get(to);
+            return new String(to, 0, to.length, charset);
+        }
+        return new String(array, buffer.arrayOffset() + buffer.position(), buffer.remaining(), charset);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert a partial buffer to an ISO-8859-1 String
+     * @param buffer  The buffer to convert in flush mode. The buffer is unchanged
+     * @param charset The {@link Charset} to use to convert the bytes
+     * @return The buffer as a string.
+     */
+    public static String toString(ByteBuffer buffer, int position, int length, Charset charset)
+    {
+        if (buffer == null)
+            return null;
+        byte[] array = buffer.hasArray() ? buffer.array() : null;
+        if (array == null)
+        {
+            ByteBuffer ro = buffer.asReadOnlyBuffer();
+            ro.position(position);
+            ro.limit(position + length);
+            byte[] to = new byte[length];
+            ro.get(to);
+            return new String(to, 0, to.length, charset);
+        }
+        return new String(array, buffer.arrayOffset() + position, length, charset);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Convert buffer to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+     *
+     * @param buffer
+     *            A buffer containing an integer in flush mode. The position is not changed.
+     * @return an int
+     */
+    public static int toInt(ByteBuffer buffer)
+    {
+        int val = 0;
+        boolean started = false;
+        boolean minus = false;
+
+        for (int i = buffer.position(); i < buffer.limit(); i++)
+        {
+            byte b = buffer.get(i);
+            if (b <= SPACE)
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val = val * 10 + (b - '0');
+                started = true;
+            }
+            else if (b == MINUS && !started)
+            {
+                minus = true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+            return minus ? (-val) : val;
+        throw new NumberFormatException(toString(buffer));
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Convert buffer to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+     *
+     * @param buffer
+     *            A buffer containing an integer in flush mode. The position is updated.
+     * @return an int
+     */
+    public static int takeInt(ByteBuffer buffer)
+    {
+        int val = 0;
+        boolean started = false;
+        boolean minus = false;
+        int i;
+        for (i = buffer.position(); i < buffer.limit(); i++)
+        {
+            byte b = buffer.get(i);
+            if (b <= SPACE)
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val = val * 10 + (b - '0');
+                started = true;
+            }
+            else if (b == MINUS && !started)
+            {
+                minus = true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+        {
+            buffer.position(i);
+            return minus ? (-val) : val;
+        }
+        throw new NumberFormatException(toString(buffer));
+    }
+
+    /**
+     * Convert buffer to an long. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+     *
+     * @param buffer
+     *            A buffer containing an integer in flush mode. The position is not changed.
+     * @return an int
+     */
+    public static long toLong(ByteBuffer buffer)
+    {
+        long val = 0;
+        boolean started = false;
+        boolean minus = false;
+
+        for (int i = buffer.position(); i < buffer.limit(); i++)
+        {
+            byte b = buffer.get(i);
+            if (b <= SPACE)
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val = val * 10L + (b - '0');
+                started = true;
+            }
+            else if (b == MINUS && !started)
+            {
+                minus = true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+            return minus ? (-val) : val;
+        throw new NumberFormatException(toString(buffer));
+    }
+
+    public static void putHexInt(ByteBuffer buffer, int n)
+    {
+        if (n < 0)
+        {
+            buffer.put((byte)'-');
+
+            if (n == Integer.MIN_VALUE)
+            {
+                buffer.put((byte)(0x7f & '8'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+
+                return;
+            }
+            n = -n;
+        }
+
+        if (n < 0x10)
+        {
+            buffer.put(DIGIT[n]);
+        }
+        else
+        {
+            boolean started = false;
+            // This assumes constant time int arithmatic
+            for (int hexDivisor : hexDivisors)
+            {
+                if (n < hexDivisor)
+                {
+                    if (started)
+                        buffer.put((byte)'0');
+                    continue;
+                }
+
+                started = true;
+                int d = n / hexDivisor;
+                buffer.put(DIGIT[d]);
+                n = n - d * hexDivisor;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void putDecInt(ByteBuffer buffer, int n)
+    {
+        if (n < 0)
+        {
+            buffer.put((byte)'-');
+
+            if (n == Integer.MIN_VALUE)
+            {
+                buffer.put((byte)'2');
+                n = 147483648;
+            }
+            else
+                n = -n;
+        }
+
+        if (n < 10)
+        {
+            buffer.put(DIGIT[n]);
+        }
+        else
+        {
+            boolean started = false;
+            // This assumes constant time int arithmatic
+            for (int decDivisor : decDivisors)
+            {
+                if (n < decDivisor)
+                {
+                    if (started)
+                        buffer.put((byte)'0');
+                    continue;
+                }
+
+                started = true;
+                int d = n / decDivisor;
+                buffer.put(DIGIT[d]);
+                n = n - d * decDivisor;
+            }
+        }
+    }
+
+    public static void putDecLong(ByteBuffer buffer, long n)
+    {
+        if (n < 0)
+        {
+            buffer.put((byte)'-');
+
+            if (n == Long.MIN_VALUE)
+            {
+                buffer.put((byte)'9');
+                n = 223372036854775808L;
+            }
+            else
+                n = -n;
+        }
+
+        if (n < 10)
+        {
+            buffer.put(DIGIT[(int)n]);
+        }
+        else
+        {
+            boolean started = false;
+            // This assumes constant time int arithmatic
+            for (long aDecDivisorsL : decDivisorsL)
+            {
+                if (n < aDecDivisorsL)
+                {
+                    if (started)
+                        buffer.put((byte)'0');
+                    continue;
+                }
+
+                started = true;
+                long d = n / aDecDivisorsL;
+                buffer.put(DIGIT[(int)d]);
+                n = n - d * aDecDivisorsL;
+            }
+        }
+    }
+
+    public static ByteBuffer toBuffer(int value)
+    {
+        ByteBuffer buf = ByteBuffer.allocate(32);
+        putDecInt(buf, value);
+        return buf;
+    }
+
+    public static ByteBuffer toBuffer(long value)
+    {
+        ByteBuffer buf = ByteBuffer.allocate(32);
+        putDecLong(buf, value);
+        return buf;
+    }
+
+    public static ByteBuffer toBuffer(String s)
+    {
+        return ByteBuffer.wrap(s.getBytes(StandardCharsets.ISO_8859_1));
+    }
+
+    public static ByteBuffer toDirectBuffer(String s)
+    {
+        byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1);
+        ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length);
+        buf.put(bytes);
+        buf.flip();
+        return buf;
+    }
+
+    public static ByteBuffer toBuffer(String s, Charset charset)
+    {
+        return ByteBuffer.wrap(s.getBytes(charset));
+    }
+
+    public static ByteBuffer toDirectBuffer(String s, Charset charset)
+    {
+        byte[] bytes = s.getBytes(charset);
+        ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length);
+        buf.put(bytes);
+        buf.flip();
+        return buf;
+    }
+
+    /**
+     * Create a new ByteBuffer using provided byte array.
+     *
+     * @param array
+     *            the byte array to back buffer with.
+     * @return ByteBuffer with provided byte array, in flush mode
+     */
+    public static ByteBuffer toBuffer(byte array[])
+    {
+        return ByteBuffer.wrap(array);
+    }
+
+    /**
+     * Create a new ByteBuffer using the provided byte array.
+     *
+     * @param array
+     *            the byte array to use.
+     * @param offset
+     *            the offset within the byte array to use from
+     * @param length
+     *            the length in bytes of the array to use
+     * @return ByteBuffer with provided byte array, in flush mode
+     */
+    public static ByteBuffer toBuffer(byte array[], int offset, int length)
+    {
+        return ByteBuffer.wrap(array, offset, length);
+    }
+
+    public static ByteBuffer toMappedBuffer(File file) throws IOException
+    {
+        try (RandomAccessFile raf = new RandomAccessFile(file, "r"))
+        {
+            return raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length());
+        }
+    }
+
+    public static ByteBuffer toBuffer(Resource resource,boolean direct) throws IOException
+    {
+        int len=(int)resource.length();
+        if (len<0)
+            throw new IllegalArgumentException("invalid resource: "+String.valueOf(resource)+" len="+len);
+        
+        ByteBuffer buffer = direct?BufferUtil.allocateDirect(len):BufferUtil.allocate(len);
+
+        int pos=BufferUtil.flipToFill(buffer);
+        if (resource.getFile()!=null)
+            BufferUtil.readFrom(resource.getFile(),buffer);
+        else
+        {
+            try (InputStream is = resource.getInputStream();)
+            {
+                BufferUtil.readFrom(is,len,buffer);
+            }
+        }
+        BufferUtil.flipToFlush(buffer,pos);
+        
+        return buffer;
+    }
+
+    public static String toSummaryString(ByteBuffer buffer)
+    {
+        if (buffer == null)
+            return "null";
+        StringBuilder buf = new StringBuilder();
+        buf.append("[p=");
+        buf.append(buffer.position());
+        buf.append(",l=");
+        buf.append(buffer.limit());
+        buf.append(",c=");
+        buf.append(buffer.capacity());
+        buf.append(",r=");
+        buf.append(buffer.remaining());
+        buf.append("]");
+        return buf.toString();
+    }
+
+    public static String toDetailString(ByteBuffer[] buffer)
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append('[');
+        for (int i = 0; i < buffer.length; i++)
+        {
+            if (i > 0) builder.append(',');
+            builder.append(toDetailString(buffer[i]));
+        }
+        builder.append(']');
+        return builder.toString();
+    }
+
+    public static String toDetailString(ByteBuffer buffer)
+    {
+        if (buffer == null)
+            return "null";
+
+        StringBuilder buf = new StringBuilder();
+        buf.append(buffer.getClass().getSimpleName());
+        buf.append("@");
+        if (buffer.hasArray())
+            buf.append(Integer.toHexString(((Object)buffer.array()).hashCode()));
+        else
+            buf.append(Integer.toHexString(buf.hashCode()));
+        buf.append("[p=");
+        buf.append(buffer.position());
+        buf.append(",l=");
+        buf.append(buffer.limit());
+        buf.append(",c=");
+        buf.append(buffer.capacity());
+        buf.append(",r=");
+        buf.append(buffer.remaining());
+        buf.append("]={");
+
+        for (int i = 0; i < buffer.position(); i++)
+        {
+            char c = (char)buffer.get(i);
+            if (c >= ' ' && c <= 127)
+                buf.append(c);
+            else if (c == '\r' || c == '\n')
+                buf.append('|');
+            else
+                buf.append('\ufffd');
+            if (i == 16 && buffer.position() > 32)
+            {
+                buf.append("...");
+                i = buffer.position() - 16;
+            }
+        }
+        buf.append("<<<");
+        for (int i = buffer.position(); i < buffer.limit(); i++)
+        {
+            char c = (char)buffer.get(i);
+            if (c >= ' ' && c <= 127)
+                buf.append(c);
+            else if (c == '\r' || c == '\n')
+                buf.append('|');
+            else
+                buf.append('\ufffd');
+            if (i == buffer.position() + 16 && buffer.limit() > buffer.position() + 32)
+            {
+                buf.append("...");
+                i = buffer.limit() - 16;
+            }
+        }
+        buf.append(">>>");
+        int limit = buffer.limit();
+        buffer.limit(buffer.capacity());
+        for (int i = limit; i < buffer.capacity(); i++)
+        {
+            char c = (char)buffer.get(i);
+            if (c >= ' ' && c <= 127)
+                buf.append(c);
+            else if (c == '\r' || c == '\n')
+                buf.append('|');
+            else
+                buf.append('\ufffd');
+            if (i == limit + 16 && buffer.capacity() > limit + 32)
+            {
+                buf.append("...");
+                i = buffer.capacity() - 16;
+            }
+        }
+        buffer.limit(limit);
+        buf.append("}");
+
+        return buf.toString();
+    }
+
+
+    private final static int[] decDivisors =
+            {1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1};
+
+    private final static int[] hexDivisors =
+            {0x10000000, 0x1000000, 0x100000, 0x10000, 0x1000, 0x100, 0x10, 0x1};
+
+    private final static long[] decDivisorsL =
+            {1000000000000000000L, 100000000000000000L, 10000000000000000L, 1000000000000000L, 100000000000000L, 10000000000000L, 1000000000000L, 100000000000L,
+                    10000000000L, 1000000000L, 100000000L, 10000000L, 1000000L, 100000L, 10000L, 1000L, 100L, 10L, 1L};
+
+    public static void putCRLF(ByteBuffer buffer)
+    {
+        buffer.put((byte)13);
+        buffer.put((byte)10);
+    }
+
+    public static boolean isPrefix(ByteBuffer prefix, ByteBuffer buffer)
+    {
+        if (prefix.remaining() > buffer.remaining())
+            return false;
+        int bi = buffer.position();
+        for (int i = prefix.position(); i < prefix.limit(); i++)
+            if (prefix.get(i) != buffer.get(bi++))
+                return false;
+        return true;
+    }
+
+    public static ByteBuffer ensureCapacity(ByteBuffer buffer, int capacity)
+    {
+        if (buffer==null)
+            return allocate(capacity);
+        
+        if (buffer.capacity()>=capacity)
+            return buffer;
+        
+        if (buffer.hasArray())
+            return ByteBuffer.wrap(Arrays.copyOfRange(buffer.array(), buffer.arrayOffset(), buffer.arrayOffset()+capacity),buffer.position(),buffer.remaining());
+        
+        throw new UnsupportedOperationException();
+    }
+
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ByteArrayISO8859Writer.java b/lib/jetty/org/eclipse/jetty/util/ByteArrayISO8859Writer.java
new file mode 100644 (file)
index 0000000..80df881
--- /dev/null
@@ -0,0 +1,269 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+
+/* ------------------------------------------------------------ */
+/** Byte Array ISO 8859 writer. 
+ * This class combines the features of a OutputStreamWriter for
+ * ISO8859 encoding with that of a ByteArrayOutputStream.  It avoids
+ * many inefficiencies associated with these standard library classes.
+ * It has been optimized for standard ASCII characters.
+ * 
+ * 
+ */
+public class ByteArrayISO8859Writer extends Writer
+{
+    private byte[] _buf;
+    private int _size;
+    private ByteArrayOutputStream2 _bout=null;
+    private OutputStreamWriter _writer=null;
+    private boolean _fixed=false;
+
+    /* ------------------------------------------------------------ */
+    /** Constructor. 
+     */
+    public ByteArrayISO8859Writer()
+    {
+        _buf=new byte[2048];
+    } 
+    
+    /* ------------------------------------------------------------ */
+    /** Constructor. 
+     * @param capacity Buffer capacity
+     */
+    public ByteArrayISO8859Writer(int capacity)
+    {
+        _buf=new byte[capacity];
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ByteArrayISO8859Writer(byte[] buf)
+    {
+        _buf=buf;
+        _fixed=true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Object getLock()
+    {
+        return lock;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int size()
+    {
+        return _size;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int capacity()
+    {
+        return _buf.length;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int spareCapacity()
+    {
+        return _buf.length-_size;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setLength(int l)
+    {
+        _size=l;
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] getBuf()
+    {
+        return _buf;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void writeTo(OutputStream out)
+        throws IOException
+    {
+        out.write(_buf,0,_size);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void write(char c)
+        throws IOException
+    {
+        ensureSpareCapacity(1);
+        if (c>=0&&c<=0x7f)
+            _buf[_size++]=(byte)c;
+        else
+        {
+            char[] ca ={c};
+            writeEncoded(ca,0,1);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(char[] ca)
+        throws IOException
+    {
+        ensureSpareCapacity(ca.length);
+        for (int i=0;i<ca.length;i++)
+        {
+            char c=ca[i];
+            if (c>=0&&c<=0x7f)
+                _buf[_size++]=(byte)c;
+            else
+            {
+                writeEncoded(ca,i,ca.length-i);
+                break;
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(char[] ca,int offset, int length)
+        throws IOException
+    {
+        ensureSpareCapacity(length);
+        for (int i=0;i<length;i++)
+        {
+            char c=ca[offset+i];
+            if (c>=0&&c<=0x7f)
+                _buf[_size++]=(byte)c;
+            else
+            {
+                writeEncoded(ca,offset+i,length-i);
+                break;
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(String s)
+        throws IOException
+    {
+        if (s==null)
+        {
+            write("null",0,4);
+            return;
+        }
+        
+        int length=s.length();
+        ensureSpareCapacity(length);
+        for (int i=0;i<length;i++)
+        {
+            char c=s.charAt(i);
+            if (c>=0x0&&c<=0x7f)
+                _buf[_size++]=(byte)c;
+            else
+            {
+                writeEncoded(s.toCharArray(),i,length-i);
+                break;
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(String s,int offset, int length)
+        throws IOException
+    {
+        ensureSpareCapacity(length);
+        for (int i=0;i<length;i++)
+        {
+            char c=s.charAt(offset+i);
+            if (c>=0&&c<=0x7f)
+                _buf[_size++]=(byte)c;
+            else
+            {
+                writeEncoded(s.toCharArray(),offset+i,length-i);
+                break;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void writeEncoded(char[] ca,int offset, int length)
+        throws IOException
+    {
+        if (_bout==null)
+        {
+            _bout = new ByteArrayOutputStream2(2*length);
+            _writer = new OutputStreamWriter(_bout,StandardCharsets.ISO_8859_1);
+        }
+        else
+            _bout.reset();
+        _writer.write(ca,offset,length);
+        _writer.flush();
+        ensureSpareCapacity(_bout.getCount());
+        System.arraycopy(_bout.getBuf(),0,_buf,_size,_bout.getCount());
+        _size+=_bout.getCount();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void flush()
+    {}
+
+    /* ------------------------------------------------------------ */
+    public void resetWriter()
+    {
+        _size=0;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void close()
+    {}
+
+    /* ------------------------------------------------------------ */
+    public void destroy()
+    {
+        _buf=null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void ensureSpareCapacity(int n)
+        throws IOException
+    {
+        if (_size+n>_buf.length)
+        {
+            if (_fixed)
+                throw new IOException("Buffer overflow: "+_buf.length);
+            _buf=Arrays.copyOf(_buf,(_buf.length+n)*4/3);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] getByteArray()
+    {
+        return Arrays.copyOf(_buf,_size);
+    }
+    
+}
+    
+    
diff --git a/lib/jetty/org/eclipse/jetty/util/ByteArrayOutputStream2.java b/lib/jetty/org/eclipse/jetty/util/ByteArrayOutputStream2.java
new file mode 100644 (file)
index 0000000..bfde018
--- /dev/null
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.Charset;
+
+/* ------------------------------------------------------------ */
+/** ByteArrayOutputStream with public internals
+
+ * 
+ */
+public class ByteArrayOutputStream2 extends ByteArrayOutputStream
+{
+    public ByteArrayOutputStream2(){super();}
+    public ByteArrayOutputStream2(int size){super(size);}
+    public byte[] getBuf(){return buf;}
+    public int getCount(){return count;}
+    public void setCount(int count){this.count = count;}
+
+    public void reset(int minSize)
+    {
+        reset();
+        if (buf.length<minSize)
+        {
+            buf=new byte[minSize];
+        }
+    }
+    
+    public void writeUnchecked(int b)
+    {
+        buf[count++]=(byte)b;
+    }
+    
+    public String toString(Charset charset)
+    {
+        return new String(buf, 0, count, charset);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Callback.java b/lib/jetty/org/eclipse/jetty/util/Callback.java
new file mode 100644 (file)
index 0000000..973d941
--- /dev/null
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/*
+ * Copyright (c) 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.eclipse.jetty.util;
+
+/**
+ * <p>A callback abstraction that handles completed/failed events of asynchronous operations.</p>
+ *
+ * <p>Semantically this is equivalent to an optimise Promise&lt;Void&gt;, but callback is a more meaningful 
+ * name than EmptyPromise</p>
+ */
+public interface Callback
+{
+    /**
+     * <p>Callback invoked when the operation completes.</p>
+     *
+     * @see #failed(Throwable)
+     */
+    public abstract void succeeded();
+
+    /**
+     * <p>Callback invoked when the operation fails.</p>
+     * @param x the reason for the operation failure
+     */
+    public void failed(Throwable x);
+
+    /**
+     * <p>Empty implementation of {@link Callback}</p>
+     */
+    public static class Adapter implements Callback
+    {
+        /**
+         * Instance of Adapter that can be used when the callback methods need an empty
+         * implementation without incurring in the cost of allocating a new Adapter object.
+         */
+        public static final Adapter INSTANCE = new Adapter();
+
+        @Override
+        public void succeeded()
+        {
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java b/lib/jetty/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java
new file mode 100644 (file)
index 0000000..5b734db
--- /dev/null
@@ -0,0 +1,105 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+
+
+/**
+ * ClassLoadingObjectInputStream
+ *
+ * For re-inflating serialized objects, this class uses the thread context classloader
+ * rather than the jvm's default classloader selection.
+ * 
+ */
+public class ClassLoadingObjectInputStream extends ObjectInputStream
+{
+    /* ------------------------------------------------------------ */
+    public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
+    {
+        super(in);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ClassLoadingObjectInputStream () throws IOException
+    {
+        super();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
+    {
+        try
+        {
+            return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
+        }
+        catch (ClassNotFoundException e)
+        {
+            return super.resolveClass(cl);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected Class<?> resolveProxyClass(String[] interfaces)
+            throws IOException, ClassNotFoundException
+    {
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+
+        ClassLoader nonPublicLoader = null;
+        boolean hasNonPublicInterface = false;
+
+        // define proxy in class loader of non-public interface(s), if any
+        Class[] classObjs = new Class[interfaces.length];
+        for (int i = 0; i < interfaces.length; i++) 
+        {
+            Class cl = Class.forName(interfaces[i], false, loader);
+            if ((cl.getModifiers() & Modifier.PUBLIC) == 0) 
+            {
+                if (hasNonPublicInterface) 
+                {
+                    if (nonPublicLoader != cl.getClassLoader()) 
+                    {
+                        throw new IllegalAccessError(
+                                "conflicting non-public interface class loaders");
+                    }
+                } 
+                else 
+                {
+                    nonPublicLoader = cl.getClassLoader();
+                    hasNonPublicInterface = true;
+                }
+            }
+            classObjs[i] = cl;
+        }
+        try 
+        {
+            return Proxy.getProxyClass(hasNonPublicInterface ? nonPublicLoader : loader,classObjs);
+        } 
+        catch (IllegalArgumentException e) 
+        {
+            throw new ClassNotFoundException(null, e);
+        }    
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/CompletableCallback.java b/lib/jetty/org/eclipse/jetty/util/CompletableCallback.java
new file mode 100644 (file)
index 0000000..ec5a30a
--- /dev/null
@@ -0,0 +1,100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A callback to be used by driver code that needs to know whether the callback has been
+ * succeeded or failed (that is, completed) just after the asynchronous operation or not,
+ * typically because further processing depends on the callback being completed.
+ * The driver code competes with the asynchronous operation to complete the callback.
+ * <p />
+ * If the callback is already completed, the driver code continues the processing,
+ * otherwise it suspends it. If it is suspended, the callback will be completed some time
+ * later, and {@link #resume()} or {@link #abort(Throwable)} will be called to allow the
+ * application to resume the processing.
+ * <p />
+ * Typical usage:
+ * <pre>
+ * CompletableCallback callback = new CompletableCallback()
+ * {
+ *     &#64;Override
+ *     public void resume()
+ *     {
+ *         // continue processing
+ *     }
+ *
+ *     &#64;Override
+ *     public void abort(Throwable failure)
+ *     {
+ *         // abort processing
+ *     }
+ * }
+ * asyncOperation(callback);
+ * boolean completed = callback.tryComplete();
+ * if (completed)
+ *     // suspend processing, async operation not done yet
+ * else
+ *     // continue processing, async operation already done
+ * </pre>
+ */
+public abstract class CompletableCallback implements Callback
+{
+    private final AtomicBoolean completed = new AtomicBoolean();
+
+    @Override
+    public void succeeded()
+    {
+        if (!tryComplete())
+            resume();
+    }
+
+    @Override
+    public void failed(Throwable x)
+    {
+        if (!tryComplete())
+            abort(x);
+    }
+
+    /**
+     * Callback method invoked when this callback is succeeded
+     * <em>after</em> a first call to {@link #tryComplete()}.
+     */
+    public abstract void resume();
+
+    /**
+     * Callback method invoked when this callback is failed
+     * <em>after</em> a first call to {@link #tryComplete()}.
+     */
+    public abstract void abort(Throwable failure);
+
+    /**
+     * Tries to complete this callback; driver code should call
+     * this method once <em>after</em> the asynchronous operation
+     * to detect whether the asynchronous operation has already
+     * completed or not.
+     *
+     * @return whether the attempt to complete was successful.
+     */
+    public boolean tryComplete()
+    {
+        return completed.compareAndSet(false, true);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ConcurrentArrayQueue.java b/lib/jetty/org/eclipse/jetty/util/ConcurrentArrayQueue.java
new file mode 100644 (file)
index 0000000..fc1326d
--- /dev/null
@@ -0,0 +1,570 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.AbstractQueue;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+/**
+ * A concurrent, unbounded implementation of {@link Queue} that uses singly-linked array blocks
+ * to store elements.
+ * <p/>
+ * This class is a drop-in replacement for {@link ConcurrentLinkedQueue}, with similar performance
+ * but producing less garbage because arrays are used to store elements rather than nodes.
+ * <p/>
+ * The algorithm used is a variation of the algorithm from Gidenstam, Sundell and Tsigas
+ * (http://www.adm.hb.se/~AGD/Presentations/CacheAwareQueue_OPODIS.pdf).
+ *
+ * @param <T>
+ */
+public class ConcurrentArrayQueue<T> extends AbstractQueue<T>
+{
+    public static final int DEFAULT_BLOCK_SIZE = 512;
+    public static final Object REMOVED_ELEMENT = new Object()
+    {
+        @Override
+        public String toString()
+        {
+            return "X";
+        }
+    };
+
+    private static final int HEAD_OFFSET = MemoryUtils.getIntegersPerCacheLine() - 1;
+    private static final int TAIL_OFFSET = MemoryUtils.getIntegersPerCacheLine()*2 -1;
+
+    private final AtomicReferenceArray<Block<T>> _blocks = new AtomicReferenceArray<>(TAIL_OFFSET + 1);
+    private final int _blockSize;
+
+    public ConcurrentArrayQueue()
+    {
+        this(DEFAULT_BLOCK_SIZE);
+    }
+
+    public ConcurrentArrayQueue(int blockSize)
+    {
+        _blockSize = blockSize;
+        Block<T> block = newBlock();
+        _blocks.set(HEAD_OFFSET,block);
+        _blocks.set(TAIL_OFFSET,block);
+    }
+
+    public int getBlockSize()
+    {
+        return _blockSize;
+    }
+
+    protected Block<T> getHeadBlock()
+    {
+        return _blocks.get(HEAD_OFFSET);
+    }
+
+    protected Block<T> getTailBlock()
+    {
+        return _blocks.get(TAIL_OFFSET);
+    }
+
+    @Override
+    public boolean offer(T item)
+    {
+        item = Objects.requireNonNull(item);
+
+        final Block<T> initialTailBlock = getTailBlock();
+        Block<T> currentTailBlock = initialTailBlock;
+        int tail = currentTailBlock.tail();
+        while (true)
+        {
+            if (tail == getBlockSize())
+            {
+                Block<T> nextTailBlock = currentTailBlock.next();
+                if (nextTailBlock == null)
+                {
+                    nextTailBlock = newBlock();
+                    if (currentTailBlock.link(nextTailBlock))
+                    {
+                        // Linking succeeded, loop
+                        currentTailBlock = nextTailBlock;
+                    }
+                    else
+                    {
+                        // Concurrent linking, use other block and loop
+                        currentTailBlock = currentTailBlock.next();
+                    }
+                }
+                else
+                {
+                    // Not at last block, loop
+                    currentTailBlock = nextTailBlock;
+                }
+                tail = currentTailBlock.tail();
+            }
+            else
+            {
+                if (currentTailBlock.peek(tail) == null)
+                {
+                    if (currentTailBlock.store(tail, item))
+                    {
+                        // Item stored
+                        break;
+                    }
+                    else
+                    {
+                        // Concurrent store, try next index
+                        ++tail;
+                    }
+                }
+                else
+                {
+                    // Not free, try next index
+                    ++tail;
+                }
+            }
+        }
+
+        updateTailBlock(initialTailBlock, currentTailBlock);
+
+        return true;
+    }
+
+    private void updateTailBlock(Block<T> oldTailBlock, Block<T> newTailBlock)
+    {
+        // Update the tail block pointer if needs to
+        if (oldTailBlock != newTailBlock)
+        {
+            // The tail block pointer is allowed to lag behind.
+            // If this update fails, it means that other threads
+            // have filled this block and installed a new one.
+            casTailBlock(oldTailBlock, newTailBlock);
+        }
+    }
+
+    protected boolean casTailBlock(Block<T> current, Block<T> update)
+    {
+        return _blocks.compareAndSet(TAIL_OFFSET,current,update);
+    }
+
+    @Override
+    public T poll()
+    {
+        final Block<T> initialHeadBlock = getHeadBlock();
+        Block<T> currentHeadBlock = initialHeadBlock;
+        int head = currentHeadBlock.head();
+        T result = null;
+        while (true)
+        {
+            if (head == getBlockSize())
+            {
+                Block<T> nextHeadBlock = currentHeadBlock.next();
+                if (nextHeadBlock == null)
+                {
+                    // We could have read that the next head block was null
+                    // but another thread allocated a new block and stored a
+                    // new item. This thread could not detect this, but that
+                    // is ok, otherwise we would not be able to exit this loop.
+
+                    // Queue is empty
+                    break;
+                }
+                else
+                {
+                    // Use next block and loop
+                    currentHeadBlock = nextHeadBlock;
+                    head = currentHeadBlock.head();
+                }
+            }
+            else
+            {
+                Object element = currentHeadBlock.peek(head);
+                if (element == REMOVED_ELEMENT)
+                {
+                    // Already removed, try next index
+                    ++head;
+                }
+                else
+                {
+                    result = (T)element;
+                    if (result != null)
+                    {
+                        if (currentHeadBlock.remove(head, result, true))
+                        {
+                            // Item removed
+                            break;
+                        }
+                        else
+                        {
+                            // Concurrent remove, try next index
+                            ++head;
+                        }
+                    }
+                    else
+                    {
+                        // Queue is empty
+                        break;
+                    }
+                }
+            }
+        }
+
+        updateHeadBlock(initialHeadBlock, currentHeadBlock);
+
+        return result;
+    }
+
+    private void updateHeadBlock(Block<T> oldHeadBlock, Block<T> newHeadBlock)
+    {
+        // Update the head block pointer if needs to
+        if (oldHeadBlock != newHeadBlock)
+        {
+            // The head block pointer lagged behind.
+            // If this update fails, it means that other threads
+            // have emptied this block and pointed to a new one.
+            casHeadBlock(oldHeadBlock, newHeadBlock);
+        }
+    }
+
+    protected boolean casHeadBlock(Block<T> current, Block<T> update)
+    {
+        return _blocks.compareAndSet(HEAD_OFFSET,current,update);
+    }
+
+    @Override
+    public T peek()
+    {
+        Block<T> currentHeadBlock = getHeadBlock();
+        int head = currentHeadBlock.head();
+        while (true)
+        {
+            if (head == getBlockSize())
+            {
+                Block<T> nextHeadBlock = currentHeadBlock.next();
+                if (nextHeadBlock == null)
+                {
+                    // Queue is empty
+                    return null;
+                }
+                else
+                {
+                    // Use next block and loop
+                    currentHeadBlock = nextHeadBlock;
+                    head = currentHeadBlock.head();
+                }
+            }
+            else
+            {
+                Object element = currentHeadBlock.peek(head);
+                if (element == REMOVED_ELEMENT)
+                {
+                    // Already removed, try next index
+                    ++head;
+                }
+                else
+                {
+                    return (T)element;
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean remove(Object o)
+    {
+        Block<T> currentHeadBlock = getHeadBlock();
+        int head = currentHeadBlock.head();
+        boolean result = false;
+        while (true)
+        {
+            if (head == getBlockSize())
+            {
+                Block<T> nextHeadBlock = currentHeadBlock.next();
+                if (nextHeadBlock == null)
+                {
+                    // Not found
+                    break;
+                }
+                else
+                {
+                    // Use next block and loop
+                    currentHeadBlock = nextHeadBlock;
+                    head = currentHeadBlock.head();
+                }
+            }
+            else
+            {
+                Object element = currentHeadBlock.peek(head);
+                if (element == REMOVED_ELEMENT)
+                {
+                    // Removed, try next index
+                    ++head;
+                }
+                else
+                {
+                    if (element == null)
+                    {
+                        // Not found
+                        break;
+                    }
+                    else
+                    {
+                        if (element.equals(o))
+                        {
+                            // Found
+                            if (currentHeadBlock.remove(head, o, false))
+                            {
+                                result = true;
+                                break;
+                            }
+                            else
+                            {
+                                ++head;
+                            }
+                        }
+                        else
+                        {
+                            // Not the one we're looking for
+                            ++head;
+                        }
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c)
+    {
+        // TODO: super invocations are based on iterator.remove(), which throws
+        return super.removeAll(c);
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c)
+    {
+        // TODO: super invocations are based on iterator.remove(), which throws
+        return super.retainAll(c);
+    }
+
+    @Override
+    public Iterator<T> iterator()
+    {
+        final List<Object[]> blocks = new ArrayList<>();
+        Block<T> currentHeadBlock = getHeadBlock();
+        while (currentHeadBlock != null)
+        {
+            Object[] elements = currentHeadBlock.arrayCopy();
+            blocks.add(elements);
+            currentHeadBlock = currentHeadBlock.next();
+        }
+        return new Iterator<T>()
+        {
+            private int blockIndex;
+            private int index;
+
+            @Override
+            public boolean hasNext()
+            {
+                while (true)
+                {
+                    if (blockIndex == blocks.size())
+                        return false;
+
+                    Object element = blocks.get(blockIndex)[index];
+
+                    if (element == null)
+                        return false;
+
+                    if (element != REMOVED_ELEMENT)
+                        return true;
+
+                    advance();
+                }
+            }
+
+            @Override
+            public T next()
+            {
+                while (true)
+                {
+                    if (blockIndex == blocks.size())
+                        throw new NoSuchElementException();
+
+                    Object element = blocks.get(blockIndex)[index];
+
+                    if (element == null)
+                        throw new NoSuchElementException();
+
+                    advance();
+
+                    if (element != REMOVED_ELEMENT)
+                        return (T)element;
+                }
+            }
+
+            private void advance()
+            {
+                if (++index == getBlockSize())
+                {
+                    index = 0;
+                    ++blockIndex;
+                }
+            }
+
+            @Override
+            public void remove()
+            {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    @Override
+    public int size()
+    {
+        Block<T> currentHeadBlock = getHeadBlock();
+        int head = currentHeadBlock.head();
+        int size = 0;
+        while (true)
+        {
+            if (head == getBlockSize())
+            {
+                Block<T> nextHeadBlock = currentHeadBlock.next();
+                if (nextHeadBlock == null)
+                {
+                    break;
+                }
+                else
+                {
+                    // Use next block and loop
+                    currentHeadBlock = nextHeadBlock;
+                    head = currentHeadBlock.head();
+                }
+            }
+            else
+            {
+                Object element = currentHeadBlock.peek(head);
+                if (element == REMOVED_ELEMENT)
+                {
+                    // Already removed, try next index
+                    ++head;
+                }
+                else if (element != null)
+                {
+                    ++size;
+                    ++head;
+                }
+                else
+                {
+                    break;
+                }
+            }
+        }
+        return size;
+    }
+
+    protected Block<T> newBlock()
+    {
+        return new Block<>(getBlockSize());
+    }
+
+    protected int getBlockCount()
+    {
+        int result = 0;
+        Block<T> headBlock = getHeadBlock();
+        while (headBlock != null)
+        {
+            ++result;
+            headBlock = headBlock.next();
+        }
+        return result;
+    }
+
+    protected static final class Block<E>
+    {
+        private static final int headOffset = MemoryUtils.getIntegersPerCacheLine()-1;
+        private static final int tailOffset = MemoryUtils.getIntegersPerCacheLine()*2-1;
+
+        private final AtomicReferenceArray<Object> elements;
+        private final AtomicReference<Block<E>> next = new AtomicReference<>();
+        private final AtomicIntegerArray indexes = new AtomicIntegerArray(TAIL_OFFSET+1);
+
+        protected Block(int blockSize)
+        {
+            elements = new AtomicReferenceArray<>(blockSize);
+        }
+
+        public Object peek(int index)
+        {
+            return elements.get(index);
+        }
+
+        public boolean store(int index, E item)
+        {
+            boolean result = elements.compareAndSet(index, null, item);
+            if (result)
+                indexes.incrementAndGet(tailOffset);
+            return result;
+        }
+
+        public boolean remove(int index, Object item, boolean updateHead)
+        {
+            boolean result = elements.compareAndSet(index, item, REMOVED_ELEMENT);
+            if (result && updateHead)
+                indexes.incrementAndGet(headOffset);
+            return result;
+        }
+
+        public Block<E> next()
+        {
+            return next.get();
+        }
+
+        public boolean link(Block<E> nextBlock)
+        {
+            return next.compareAndSet(null, nextBlock);
+        }
+
+        public int head()
+        {
+            return indexes.get(headOffset);
+        }
+
+        public int tail()
+        {
+            return indexes.get(tailOffset);
+        }
+
+        public Object[] arrayCopy()
+        {
+            Object[] result = new Object[elements.length()];
+            for (int i = 0; i < result.length; ++i)
+                result[i] = elements.get(i);
+            return result;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ConcurrentHashSet.java b/lib/jetty/org/eclipse/jetty/util/ConcurrentHashSet.java
new file mode 100644 (file)
index 0000000..4a4c8e6
--- /dev/null
@@ -0,0 +1,126 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ConcurrentHashSet<E> extends AbstractSet<E> implements Set<E>
+{
+    private final Map<E, Boolean> _map = new ConcurrentHashMap<E, Boolean>();
+    private transient Set<E> _keys = _map.keySet();
+
+    public ConcurrentHashSet()
+    {
+    }
+
+    @Override
+    public boolean add(E e)
+    {
+        return _map.put(e,Boolean.TRUE) == null;
+    }
+
+    @Override
+    public void clear()
+    {
+        _map.clear();
+    }
+
+    @Override
+    public boolean contains(Object o)
+    {
+        return _map.containsKey(o);
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c)
+    {
+        return _keys.containsAll(c);
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        return o == this || _keys.equals(o);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return _keys.hashCode();
+    }
+
+    @Override
+    public boolean isEmpty()
+    {
+        return _map.isEmpty();
+    }
+
+    @Override
+    public Iterator<E> iterator()
+    {
+        return _keys.iterator();
+    }
+
+    @Override
+    public boolean remove(Object o)
+    {
+        return _map.remove(o) != null;
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c)
+    {
+        return _keys.removeAll(c);
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c)
+    {
+        return _keys.retainAll(c);
+    }
+
+    @Override
+    public int size()
+    {
+        return _map.size();
+    }
+
+    @Override
+    public Object[] toArray()
+    {
+        return _keys.toArray();
+    }
+
+    @Override
+    public <T> T[] toArray(T[] a)
+    {
+        return _keys.toArray(a);
+    }
+
+    @Override
+    public String toString()
+    {
+        return _keys.toString();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/DateCache.java b/lib/jetty/org/eclipse/jetty/util/DateCache.java
new file mode 100644 (file)
index 0000000..31097ee
--- /dev/null
@@ -0,0 +1,268 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/* ------------------------------------------------------------ */
+/**  Date Format Cache.
+ * Computes String representations of Dates and caches
+ * the results so that subsequent requests within the same second
+ * will be fast.
+ *
+ * Only format strings that contain either "ss".  Sub second formatting is 
+ * not handled.
+ *
+ * The timezone of the date may be included as an ID with the "zzz"
+ * format string or as an offset with the "ZZZ" format string.
+ *
+ * If consecutive calls are frequently very different, then this
+ * may be a little slower than a normal DateFormat.
+ *
+ */
+
+public class DateCache
+{
+    public static final String DEFAULT_FORMAT="EEE MMM dd HH:mm:ss zzz yyyy";
+    
+    private final String _formatString;
+    private final String _tzFormatString;
+    private final SimpleDateFormat _tzFormat;
+    private final Locale _locale ;
+    
+    private volatile Tick _tick;
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class Tick
+    {
+        final long _seconds;
+        final String _string;
+        public Tick(long seconds, String string)
+        {
+            _seconds = seconds;
+            _string = string;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     * Make a DateCache that will use a default format. The default format
+     * generates the same results as Date.toString().
+     */
+    public DateCache()
+    {
+        this(DEFAULT_FORMAT);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     * Make a DateCache that will use the given format
+     */
+    public DateCache(String format)
+    {
+        this(format,null,TimeZone.getDefault());
+    }
+    
+    /* ------------------------------------------------------------ */
+    public DateCache(String format,Locale l)
+    {
+        this(format,l,TimeZone.getDefault());
+    }
+
+    /* ------------------------------------------------------------ */
+    public DateCache(String format,Locale l,String tz)
+    {
+        this(format,l,TimeZone.getTimeZone(tz));
+    }
+    
+    /* ------------------------------------------------------------ */
+    public DateCache(String format,Locale l,TimeZone tz)
+    {
+        _formatString=format;
+        _locale = l;
+        
+
+        int zIndex = _formatString.indexOf( "ZZZ" );
+        if( zIndex >= 0 )
+        {
+            String ss1 = _formatString.substring( 0, zIndex );
+            String ss2 = _formatString.substring( zIndex+3 );
+            int tzOffset = tz.getRawOffset();
+            
+            StringBuilder sb = new StringBuilder(_formatString.length()+10);
+            sb.append(ss1);
+            sb.append("'");
+            if( tzOffset >= 0 )
+                sb.append( '+' );
+            else
+            {
+                tzOffset = -tzOffset;
+                sb.append( '-' );
+            }
+            
+            int raw = tzOffset / (1000*60);             // Convert to seconds
+            int hr = raw / 60;
+            int min = raw % 60;
+            
+            if( hr < 10 )
+                sb.append( '0' );
+            sb.append( hr );
+            if( min < 10 )
+                sb.append( '0' );
+            sb.append( min );
+            sb.append( '\'' );
+            
+            sb.append(ss2);
+            _tzFormatString=sb.toString();            
+        }
+        else
+            _tzFormatString=_formatString;
+   
+        if( _locale != null ) 
+        {
+            _tzFormat=new SimpleDateFormat(_tzFormatString,_locale);
+        }
+        else 
+        {
+            _tzFormat=new SimpleDateFormat(_tzFormatString);
+        }
+        _tzFormat.setTimeZone(tz);
+        
+        _tick=null;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public TimeZone getTimeZone()
+    {
+        return _tzFormat.getTimeZone();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Format a date according to our stored formatter.
+     * @param inDate 
+     * @return Formatted date
+     */
+    public String format(Date inDate)
+    {
+        long seconds = inDate.getTime() / 1000;
+
+        Tick tick=_tick;
+        
+        // Is this the cached time
+        if (tick==null || seconds!=tick._seconds)
+        {
+            // It's a cache miss
+            synchronized (this)
+            {
+                return _tzFormat.format(inDate);
+            }
+        }
+        
+        return tick._string;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Format a date according to our stored formatter.
+     * If it happens to be in the same second as the last formatNow
+     * call, then the format is reused.
+     * @param inDate 
+     * @return Formatted date
+     */
+    public String format(long inDate)
+    {
+        long seconds = inDate / 1000;
+
+        Tick tick=_tick;
+        
+        // Is this the cached time
+        if (tick==null || seconds!=tick._seconds)
+        {
+            // It's a cache miss
+            Date d = new Date(inDate);
+            synchronized (this)
+            {
+                return _tzFormat.format(d);
+            }
+        }
+        
+        return tick._string;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Format a date according to our stored formatter.
+     * The passed time is expected to be close to the current time, so it is 
+     * compared to the last value passed and if it is within the same second,
+     * the format is reused.  Otherwise a new cached format is created.
+     * @param now 
+     * @return Formatted date
+     */
+    public String formatNow(long now)
+    {
+        long seconds = now / 1000;
+
+        Tick tick=_tick;
+        
+        // Is this the cached time
+        if (tick!=null && tick._seconds==seconds)
+            return tick._string;
+        return formatTick(now)._string;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String now()
+    {
+        return formatNow(System.currentTimeMillis());
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Tick tick()
+    {
+        return formatTick(System.currentTimeMillis());
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected Tick formatTick(long now)
+    {
+        long seconds = now / 1000;
+
+        // Synchronize to protect _tzFormat
+        synchronized (this)
+        {
+            // recheck the tick, to save multiple formats
+            if (_tick==null || _tick._seconds!=seconds)
+            {
+                String s= _tzFormat.format(new Date(now));
+                return _tick=new Tick(seconds,s);
+            }
+            return _tick;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getFormatString()
+    {
+        return _formatString;
+    }    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Fields.java b/lib/jetty/org/eclipse/jetty/util/Fields.java
new file mode 100644 (file)
index 0000000..152934d
--- /dev/null
@@ -0,0 +1,336 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>A container for name/value pairs, known as fields.</p>
+ * <p>A {@link Field} is composed of a name string that can be case-sensitive
+ * or case-insensitive (by specifying the option at the constructor) and
+ * of a case-sensitive set of value strings.</p>
+ * <p>The implementation of this class is not thread safe.</p>
+ */
+public class Fields implements Iterable<Fields.Field>
+{
+    private final boolean caseSensitive;
+    private final Map<String, Field> fields;
+
+    /**
+     * <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
+     * @see #Fields(Fields, boolean)
+     */
+    public Fields()
+    {
+        this(false);
+    }
+
+    /**
+     * <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
+     * @param caseSensitive whether this {@link Fields} instance must be case sensitive
+     * @see #Fields(Fields, boolean)
+     */
+    public Fields(boolean caseSensitive)
+    {
+        this.caseSensitive = caseSensitive;
+        fields = new LinkedHashMap<>();
+    }
+
+    /**
+     * <p>Creates a {@link Fields} instance by copying the fields from the given
+     * {@link Fields} and making it (im)mutable depending on the given {@code immutable} parameter</p>
+     *
+     * @param original the {@link Fields} to copy fields from
+     * @param immutable whether this instance is immutable
+     */
+    public Fields(Fields original, boolean immutable)
+    {
+        this.caseSensitive = original.caseSensitive;
+        Map<String, Field> copy = new LinkedHashMap<>();
+        copy.putAll(original.fields);
+        fields = immutable ? Collections.unmodifiableMap(copy) : copy;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+            return true;
+        if (obj == null || getClass() != obj.getClass())
+            return false;
+        Fields that = (Fields)obj;
+        if (getSize() != that.getSize())
+            return false;
+        if (caseSensitive != that.caseSensitive)
+            return false;
+        for (Map.Entry<String, Field> entry : fields.entrySet())
+        {
+            String name = entry.getKey();
+            Field value = entry.getValue();
+            if (!value.equals(that.get(name), caseSensitive))
+                return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return fields.hashCode();
+    }
+
+    /**
+     * @return a set of field names
+     */
+    public Set<String> getNames()
+    {
+        Set<String> result = new LinkedHashSet<>();
+        for (Field field : fields.values())
+            result.add(field.getName());
+        return result;
+    }
+
+    private String normalizeName(String name)
+    {
+        return caseSensitive ? name : name.toLowerCase(Locale.ENGLISH);
+    }
+
+    /**
+     * @param name the field name
+     * @return the {@link Field} with the given name, or null if no such field exists
+     */
+    public Field get(String name)
+    {
+        return fields.get(normalizeName(name));
+    }
+
+    /**
+     * <p>Inserts or replaces the given name/value pair as a single-valued {@link Field}.</p>
+     *
+     * @param name the field name
+     * @param value the field value
+     */
+    public void put(String name, String value)
+    {
+        // Preserve the case for the field name
+        Field field = new Field(name, value);
+        fields.put(normalizeName(name), field);
+    }
+
+    /**
+     * <p>Inserts or replaces the given {@link Field}, mapped to the {@link Field#getName() field's name}</p>
+     *
+     * @param field the field to put
+     */
+    public void put(Field field)
+    {
+        if (field != null)
+            fields.put(normalizeName(field.getName()), field);
+    }
+
+    /**
+     * <p>Adds the given value to a field with the given name,
+     * creating a {@link Field} is none exists for the given name.</p>
+     *
+     * @param name the field name
+     * @param value the field value to add
+     */
+    public void add(String name, String value)
+    {
+        String key = normalizeName(name);
+        Field field = fields.get(key);
+        if (field == null)
+        {
+            // Preserve the case for the field name
+            field = new Field(name, value);
+            fields.put(key, field);
+        }
+        else
+        {
+            field = new Field(field.getName(), field.getValues(), value);
+            fields.put(key, field);
+        }
+    }
+
+    /**
+     * <p>Removes the {@link Field} with the given name</p>
+     *
+     * @param name the name of the field to remove
+     * @return the removed field, or null if no such field existed
+     */
+    public Field remove(String name)
+    {
+        return fields.remove(normalizeName(name));
+    }
+
+    /**
+     * <p>Empties this {@link Fields} instance from all fields</p>
+     * @see #isEmpty()
+     */
+    public void clear()
+    {
+        fields.clear();
+    }
+
+    /**
+     * @return whether this {@link Fields} instance is empty
+     */
+    public boolean isEmpty()
+    {
+        return fields.isEmpty();
+    }
+
+    /**
+     * @return the number of fields
+     */
+    public int getSize()
+    {
+        return fields.size();
+    }
+
+    /**
+     * @return an iterator over the {@link Field}s present in this instance
+     */
+    @Override
+    public Iterator<Field> iterator()
+    {
+        return fields.values().iterator();
+    }
+
+    @Override
+    public String toString()
+    {
+        return fields.toString();
+    }
+
+    /**
+     * <p>A named list of string values.</p>
+     * <p>The name is case-sensitive and there must be at least one value.</p>
+     */
+    public static class Field
+    {
+        private final String name;
+        private final List<String> values;
+
+        public Field(String name, String value)
+        {
+            this(name, Collections.singletonList(value));
+        }
+
+        private Field(String name, List<String> values, String... moreValues)
+        {
+            this.name = name;
+            List<String> list = new ArrayList<>(values.size() + moreValues.length);
+            list.addAll(values);
+            list.addAll(Arrays.asList(moreValues));
+            this.values = Collections.unmodifiableList(list);
+        }
+
+        public boolean equals(Field that, boolean caseSensitive)
+        {
+            if (this == that)
+                return true;
+            if (that == null)
+                return false;
+            if (caseSensitive)
+                return equals(that);
+            return name.equalsIgnoreCase(that.name) && values.equals(that.values);
+        }
+
+        @Override
+        public boolean equals(Object obj)
+        {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            Field that = (Field)obj;
+            return name.equals(that.name) && values.equals(that.values);
+        }
+
+        @Override
+        public int hashCode()
+        {
+            int result = name.hashCode();
+            result = 31 * result + values.hashCode();
+            return result;
+        }
+
+        /**
+         * @return the field's name
+         */
+        public String getName()
+        {
+            return name;
+        }
+
+        /**
+         * @return the first field's value
+         */
+        public String getValue()
+        {
+            return values.get(0);
+        }
+
+        /**
+         * <p>Attempts to convert the result of {@link #getValue()} to an integer,
+         * returning it if the conversion is successful; returns null if the
+         * result of {@link #getValue()} is null.</p>
+         *
+         * @return the result of {@link #getValue()} converted to an integer, or null
+         * @throws NumberFormatException if the conversion fails
+         */
+        public Integer getValueAsInt()
+        {
+            final String value = getValue();
+            return value == null ? null : Integer.valueOf(value);
+        }
+
+        /**
+         * @return the field's values
+         */
+        public List<String> getValues()
+        {
+            return values;
+        }
+
+        /**
+         * @return whether the field has multiple values
+         */
+        public boolean hasMultipleValues()
+        {
+            return values.size() > 1;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s=%s", name, values);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ForkInvoker.java b/lib/jetty/org/eclipse/jetty/util/ForkInvoker.java
new file mode 100644 (file)
index 0000000..c15b313
--- /dev/null
@@ -0,0 +1,135 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+/**
+ * Utility class that splits calls to {@link #invoke(Object)} into calls to {@link #fork(Object)} or {@link #call(Object)}
+ * depending on the max number of reentrant calls to {@link #invoke(Object)}.
+ * <p/>
+ * This class prevents {@link StackOverflowError}s in case of methods that end up invoking themselves,
+ * such is common for {@link Callback#succeeded()}.
+ * <p/>
+ * Typical use case is:
+ * <pre>
+ * public void reentrantMethod(Object param)
+ * {
+ *     if (condition || tooManyReenters)
+ *         fork(param)
+ *     else
+ *         call(param)
+ * }
+ * </pre>
+ * Calculating {@code tooManyReenters} usually involves using a {@link ThreadLocal} and algebra on the
+ * number of reentrant invocations, which is factored out in this class for convenience.
+ * <p />
+ * The same code using this class becomes:
+ * <pre>
+ * private final ForkInvoker invoker = ...;
+ *
+ * public void reentrantMethod(Object param)
+ * {
+ *     invoker.invoke(param);
+ * }
+ * </pre>
+ *
+ */
+public abstract class ForkInvoker<T>
+{
+    private static final ThreadLocal<Integer> __invocations = new ThreadLocal<Integer>()
+    {
+        @Override
+        protected Integer initialValue()
+        {
+            return 0;
+        }
+    };
+    private final int _maxInvocations;
+
+    /**
+     * Creates an instance with the given max number of reentrant calls to {@link #invoke(Object)}
+     * <p/>
+     * If {@code maxInvocations} is zero or negative, it is interpreted
+     * as if the max number of reentrant calls is infinite.
+     *
+     * @param maxInvocations the max number of reentrant calls to {@link #invoke(Object)}
+     */
+    public ForkInvoker(int maxInvocations)
+    {
+        _maxInvocations = maxInvocations;
+    }
+
+    /**
+     * Invokes either {@link #fork(Object)} or {@link #call(Object)}.
+     * If {@link #condition()} returns true, {@link #fork(Object)} is invoked.
+     * Otherwise, if the max number of reentrant calls is positive and the
+     * actual number of reentrant invocations exceeds it, {@link #fork(Object)} is invoked.
+     * Otherwise, {@link #call(Object)} is invoked.
+     * @param arg TODO
+     *
+     * @return true if {@link #fork(Object)} has been called, false otherwise
+     */
+    public boolean invoke(T arg)
+    {
+        boolean countInvocations = _maxInvocations > 0;
+        int invocations = __invocations.get();
+        if (condition() || countInvocations && invocations > _maxInvocations)
+        {
+            fork(arg);
+            return true;
+        }
+        else
+        {
+            if (countInvocations)
+                __invocations.set(invocations + 1);
+            try
+            {
+                call(arg);
+                return false;
+            }
+            finally
+            {
+                if (countInvocations)
+                    __invocations.set(invocations);
+            }
+        }
+    }
+
+    /**
+     * Subclasses should override this method returning true if they want
+     * {@link #invoke(Object)} to call {@link #fork(Object)}.
+     *
+     * @return true if {@link #invoke(Object)} should call {@link #fork(Object)}, false otherwise
+     */
+    protected boolean condition()
+    {
+        return false;
+    }
+
+    /**
+     * Executes the forked invocation
+     * @param arg TODO
+     */
+    public abstract void fork(T arg);
+
+    /**
+     * Executes the direct, non-forked, invocation
+     * @param arg TODO
+     */
+    public abstract void call(T arg);
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/FutureCallback.java b/lib/jetty/org/eclipse/jetty/util/FutureCallback.java
new file mode 100644 (file)
index 0000000..5bad6b2
--- /dev/null
@@ -0,0 +1,157 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class FutureCallback implements Future<Void>,Callback
+{
+    private static Throwable COMPLETED=new Throwable();
+    private final AtomicBoolean _done=new AtomicBoolean(false);
+    private final CountDownLatch _latch=new CountDownLatch(1);
+    private Throwable _cause;
+    
+    public FutureCallback()
+    {}
+
+    public FutureCallback(boolean completed)
+    {
+        if (completed)
+        {
+            _cause=COMPLETED;
+            _done.set(true);
+            _latch.countDown();
+        }
+    }
+
+    public FutureCallback(Throwable failed)
+    {
+        _cause=failed;
+        _done.set(true);
+        _latch.countDown();
+    }
+
+    @Override
+    public void succeeded()
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _cause=COMPLETED;
+            _latch.countDown();
+        }
+    }
+
+    @Override
+    public void failed(Throwable cause)
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _cause=cause;
+            _latch.countDown();
+        }
+    }
+
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning)
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _cause=new CancellationException();
+            _latch.countDown();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isCancelled()
+    {
+        if (_done.get())
+        {
+            try
+            {
+                _latch.await();
+            }
+            catch (InterruptedException e)
+            {
+                throw new RuntimeException(e);
+            }
+            return _cause instanceof CancellationException;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isDone()
+    {
+        return _done.get() && _latch.getCount()==0;
+    }
+
+    @Override
+    public Void get() throws InterruptedException, ExecutionException
+    {
+        _latch.await();
+        if (_cause==COMPLETED)
+            return null;
+        if (_cause instanceof CancellationException)
+            throw (CancellationException) new CancellationException().initCause(_cause);
+        throw new ExecutionException(_cause);
+    }
+
+    @Override
+    public Void get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
+    {
+        if (!_latch.await(timeout,unit))
+            throw new TimeoutException();
+
+        if (_cause==COMPLETED)
+            return null;
+        if (_cause instanceof TimeoutException)
+            throw (TimeoutException)_cause;
+        if (_cause instanceof CancellationException)
+            throw (CancellationException) new CancellationException().initCause(_cause);
+        throw new ExecutionException(_cause);
+    }
+
+    public static void rethrow(ExecutionException e) throws IOException
+    {
+        Throwable cause=e.getCause();
+        if (cause instanceof IOException)
+            throw (IOException)cause;
+        if (cause instanceof Error)
+            throw (Error)cause;
+        if (cause instanceof RuntimeException)
+            throw (RuntimeException)cause;
+        throw new RuntimeException(cause);
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("FutureCallback@%x{%b,%b}",hashCode(),_done,_cause==COMPLETED);
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/FuturePromise.java b/lib/jetty/org/eclipse/jetty/util/FuturePromise.java
new file mode 100644 (file)
index 0000000..f781c85
--- /dev/null
@@ -0,0 +1,159 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class FuturePromise<C> implements Future<C>,Promise<C>
+{
+    private static Throwable COMPLETED=new Throwable();
+    private final AtomicBoolean _done=new AtomicBoolean(false);
+    private final CountDownLatch _latch=new CountDownLatch(1);
+    private Throwable _cause;
+    private C _result;
+    
+    public FuturePromise()
+    {}
+
+    public FuturePromise(C result)
+    {
+        _cause=COMPLETED;
+        _result=result;
+        _done.set(true);
+        _latch.countDown();
+    }
+
+    public FuturePromise(C ctx, Throwable failed)
+    {
+        _result=ctx;
+        _cause=failed;
+        _done.set(true);
+        _latch.countDown();
+    }
+
+    @Override
+    public void succeeded(C result)
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _result=result;
+            _cause=COMPLETED;
+            _latch.countDown();
+        }
+    }
+
+    @Override
+    public void failed(Throwable cause)
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _cause=cause;
+            _latch.countDown();
+        }
+    }
+
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning)
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _result=null;
+            _cause=new CancellationException();
+            _latch.countDown();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isCancelled()
+    {
+        if (_done.get())
+        {
+            try
+            {
+                _latch.await();
+            }
+            catch (InterruptedException e)
+            {
+                throw new RuntimeException(e);
+            }
+            return _cause instanceof CancellationException;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isDone()
+    {
+        return _done.get() && _latch.getCount()==0;
+    }
+
+    @Override
+    public C get() throws InterruptedException, ExecutionException
+    {
+        _latch.await();
+        if (_cause==COMPLETED)
+            return _result;
+        if (_cause instanceof CancellationException)
+            throw (CancellationException) new CancellationException().initCause(_cause);
+        throw new ExecutionException(_cause);
+    }
+
+    @Override
+    public C get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
+    {
+        if (!_latch.await(timeout,unit))
+            throw new TimeoutException();
+
+        if (_cause==COMPLETED)
+            return _result;
+        if (_cause instanceof TimeoutException)
+            throw (TimeoutException)_cause;
+        if (_cause instanceof CancellationException)
+            throw (CancellationException) new CancellationException().initCause(_cause);
+        throw new ExecutionException(_cause);
+    }
+
+    public static void rethrow(ExecutionException e) throws IOException
+    {
+        Throwable cause=e.getCause();
+        if (cause instanceof IOException)
+            throw (IOException)cause;
+        if (cause instanceof Error)
+            throw (Error)cause;
+        if (cause instanceof RuntimeException)
+            throw (RuntimeException)cause;
+        throw new RuntimeException(cause);
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("FutureCallback@%x{%b,%b,%s}",hashCode(),_done,_cause==COMPLETED,_result);
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/HostMap.java b/lib/jetty/org/eclipse/jetty/util/HostMap.java
new file mode 100644 (file)
index 0000000..23e10bb
--- /dev/null
@@ -0,0 +1,108 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+/* ------------------------------------------------------------ */
+/**
+ */
+@SuppressWarnings("serial")
+public class HostMap<TYPE> extends HashMap<String, TYPE>
+{
+
+    /* --------------------------------------------------------------- */
+    /** Construct empty HostMap.
+     */
+    public HostMap()
+    {
+        super(11);
+    }
+   
+    /* --------------------------------------------------------------- */
+    /** Construct empty HostMap.
+     * 
+     * @param capacity initial capacity
+     */
+    public HostMap(int capacity)
+    {
+        super (capacity);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
+     */
+    @Override
+    public TYPE put(String host, TYPE object)
+        throws IllegalArgumentException
+    {
+        return super.put(host, object);
+    }
+        
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.util.HashMap#get(java.lang.Object)
+     */
+    @Override
+    public TYPE get(Object key)
+    {
+        return super.get(key);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve a lazy list of map entries associated with specified
+     * hostname by taking into account the domain suffix matches.
+     * 
+     * @param host hostname
+     * @return lazy list of map entries
+     */
+    public Object getLazyMatches(String host)
+    {
+        if (host == null)
+            return LazyList.getList(super.entrySet());
+        
+        int idx = 0;
+        String domain = host.trim();
+        HashSet<String> domains = new HashSet<String>();
+        do {
+            domains.add(domain);
+            if ((idx = domain.indexOf('.')) > 0)
+            {
+                domain = domain.substring(idx+1);
+            }
+        } while (idx > 0);
+        
+        Object entries = null;
+        for(Map.Entry<String, TYPE> entry: super.entrySet())
+        {
+            if (domains.contains(entry.getKey()))
+            {
+                entries = LazyList.add(entries,entry);
+            }
+        }
+       
+        return entries;        
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/HttpCookieStore.java b/lib/jetty/org/eclipse/jetty/util/HttpCookieStore.java
new file mode 100644 (file)
index 0000000..99aa742
--- /dev/null
@@ -0,0 +1,114 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.net.CookieManager;
+import java.net.CookieStore;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implementation of {@link CookieStore} that delegates to an instance created by {@link CookieManager}
+ * via {@link CookieManager#getCookieStore()}.
+ */
+public class HttpCookieStore implements CookieStore
+{
+    private final CookieStore delegate;
+
+    public HttpCookieStore()
+    {
+        delegate = new CookieManager().getCookieStore();
+    }
+
+    @Override
+    public void add(URI uri, HttpCookie cookie)
+    {
+        delegate.add(uri, cookie);
+    }
+
+    @Override
+    public List<HttpCookie> get(URI uri)
+    {
+        return delegate.get(uri);
+    }
+
+    @Override
+    public List<HttpCookie> getCookies()
+    {
+        return delegate.getCookies();
+    }
+
+    @Override
+    public List<URI> getURIs()
+    {
+        return delegate.getURIs();
+    }
+
+    @Override
+    public boolean remove(URI uri, HttpCookie cookie)
+    {
+        return delegate.remove(uri, cookie);
+    }
+
+    @Override
+    public boolean removeAll()
+    {
+        return delegate.removeAll();
+    }
+
+    public static class Empty implements CookieStore
+    {
+        @Override
+        public void add(URI uri, HttpCookie cookie)
+        {
+        }
+
+        @Override
+        public List<HttpCookie> get(URI uri)
+        {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<HttpCookie> getCookies()
+        {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<URI> getURIs()
+        {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public boolean remove(URI uri, HttpCookie cookie)
+        {
+            return false;
+        }
+
+        @Override
+        public boolean removeAll()
+        {
+            return false;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/IO.java b/lib/jetty/org/eclipse/jetty/util/IO.java
new file mode 100644 (file)
index 0000000..51fbed9
--- /dev/null
@@ -0,0 +1,499 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ======================================================================== */
+/** IO Utilities.
+ * Provides stream handling utilities in
+ * singleton Threadpool implementation accessed by static members.
+ */
+public class IO 
+{
+    private static final Logger LOG = Log.getLogger(IO.class);
+    
+    /* ------------------------------------------------------------------- */
+    public final static String
+        CRLF      = "\015\012";
+
+    /* ------------------------------------------------------------------- */
+    public final static byte[]
+        CRLF_BYTES    = {(byte)'\015',(byte)'\012'};
+
+    /* ------------------------------------------------------------------- */
+    public static final int bufferSize = 64*1024;
+
+    /* ------------------------------------------------------------------- */
+    static class Job implements Runnable
+    {
+        InputStream in;
+        OutputStream out;
+        Reader read;
+        Writer write;
+
+        Job(InputStream in,OutputStream out)
+        {
+            this.in=in;
+            this.out=out;
+            this.read=null;
+            this.write=null;
+        }
+        Job(Reader read,Writer write)
+        {
+            this.in=null;
+            this.out=null;
+            this.read=read;
+            this.write=write;
+        }
+        
+        /* ------------------------------------------------------------ */
+        /* 
+         * @see java.lang.Runnable#run()
+         */
+        public void run()
+        {
+            try {
+                if (in!=null)
+                    copy(in,out,-1);
+                else
+                    copy(read,write,-1);
+            }
+            catch(IOException e)
+            {
+                LOG.ignore(e);
+                try{
+                    if (out!=null)
+                        out.close();
+                    if (write!=null)
+                        write.close();
+                }
+                catch(IOException e2)
+                {
+                    LOG.ignore(e2);
+                }
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------------- */
+    /** Copy Stream in to Stream out until EOF or exception.
+     */
+    public static void copy(InputStream in, OutputStream out)
+         throws IOException
+    {
+        copy(in,out,-1);
+    }
+    
+    /* ------------------------------------------------------------------- */
+    /** Copy Reader to Writer out until EOF or exception.
+     */
+    public static void copy(Reader in, Writer out)
+         throws IOException
+    {
+        copy(in,out,-1);
+    }
+    
+    /* ------------------------------------------------------------------- */
+    /** Copy Stream in to Stream for byteCount bytes or until EOF or exception.
+     */
+    public static void copy(InputStream in,
+                            OutputStream out,
+                            long byteCount)
+         throws IOException
+    {     
+        byte buffer[] = new byte[bufferSize];
+        int len=bufferSize;
+        
+        if (byteCount>=0)
+        {
+            while (byteCount>0)
+            {
+                int max = byteCount<bufferSize?(int)byteCount:bufferSize;
+                len=in.read(buffer,0,max);
+                
+                if (len==-1)
+                    break;
+                
+                byteCount -= len;
+                out.write(buffer,0,len);
+            }
+        }
+        else
+        {
+            while (true)
+            {
+                len=in.read(buffer,0,bufferSize);
+                if (len<0 )
+                    break;
+                out.write(buffer,0,len);
+            }
+        }
+    }  
+    
+    /* ------------------------------------------------------------------- */
+    /** Copy Reader to Writer for byteCount bytes or until EOF or exception.
+     */
+    public static void copy(Reader in,
+                            Writer out,
+                            long byteCount)
+         throws IOException
+    {  
+        char buffer[] = new char[bufferSize];
+        int len=bufferSize;
+        
+        if (byteCount>=0)
+        {
+            while (byteCount>0)
+            {
+                if (byteCount<bufferSize)
+                    len=in.read(buffer,0,(int)byteCount);
+                else
+                    len=in.read(buffer,0,bufferSize);                   
+                
+                if (len==-1)
+                    break;
+                
+                byteCount -= len;
+                out.write(buffer,0,len);
+            }
+        }
+        else if (out instanceof PrintWriter)
+        {
+            PrintWriter pout=(PrintWriter)out;
+            while (!pout.checkError())
+            {
+                len=in.read(buffer,0,bufferSize);
+                if (len==-1)
+                    break;
+                out.write(buffer,0,len);
+            }
+        }
+        else
+        {
+            while (true)
+            {
+                len=in.read(buffer,0,bufferSize);
+                if (len==-1)
+                    break;
+                out.write(buffer,0,len);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Copy files or directories
+     * @param from
+     * @param to
+     * @throws IOException
+     */
+    public static void copy(File from,File to) throws IOException
+    {
+        if (from.isDirectory())
+            copyDir(from,to);
+        else
+            copyFile(from,to);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void copyDir(File from,File to) throws IOException
+    {
+        if (to.exists())
+        {
+            if (!to.isDirectory())
+                throw new IllegalArgumentException(to.toString());
+        }
+        else
+            to.mkdirs();
+        
+        File[] files = from.listFiles();
+        if (files!=null)
+        {
+            for (int i=0;i<files.length;i++)
+            {
+                String name = files[i].getName();
+                if (".".equals(name) || "..".equals(name))
+                    continue;
+                copy(files[i],new File(to,name));
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static void copyFile(File from,File to) throws IOException
+    {
+        try (InputStream in=new FileInputStream(from);
+                OutputStream out=new FileOutputStream(to))
+        {
+            copy(in,out);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Read input stream to string.
+     */
+    public static String toString(InputStream in)
+        throws IOException
+    {
+        return toString(in,(Charset)null);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Read input stream to string.
+     */
+    public static String toString(InputStream in,String encoding)
+        throws IOException
+    {
+        return toString(in, encoding==null?null:Charset.forName(encoding));
+    }
+
+    /** Read input stream to string.
+     */
+    public static String toString(InputStream in, Charset encoding)
+            throws IOException
+    {
+        StringWriter writer=new StringWriter();
+        InputStreamReader reader = encoding==null?new InputStreamReader(in):new InputStreamReader(in,encoding);
+
+        copy(reader,writer);
+        return writer.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Read input stream to string.
+     */
+    public static String toString(Reader in)
+        throws IOException
+    {
+        StringWriter writer=new StringWriter();
+        copy(in,writer);
+        return writer.toString();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Delete File.
+     * This delete will recursively delete directories - BE CAREFULL
+     * @param file The file to be deleted.
+     */
+    public static boolean delete(File file)
+    {
+        if (!file.exists())
+            return false;
+        if (file.isDirectory())
+        {
+            File[] files = file.listFiles();
+            for (int i=0;files!=null && i<files.length;i++)
+                delete(files[i]);
+        }
+        return file.delete();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * closes an input stream, and logs exceptions
+     *
+     * @param is the input stream to close
+     */
+    public static void close(InputStream is)
+    {
+        try
+        {
+            if (is != null)
+                is.close();
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+    /**
+     * closes a reader, and logs exceptions
+     * 
+     * @param reader the reader to close
+     */
+    public static void close(Reader reader)
+    {
+        try
+        {
+            if (reader != null)
+                reader.close();
+        } catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+    /**
+     * closes a writer, and logs exceptions
+     * 
+     * @param writer the writer to close
+     */
+    public static void close(Writer writer)
+    {
+        try
+        {
+            if (writer != null)
+                writer.close();
+        } catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static byte[] readBytes(InputStream in)
+        throws IOException
+    {
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        copy(in,bout);
+        return bout.toByteArray();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * closes an output stream, and logs exceptions
+     *
+     * @param os the output stream to close
+     */
+    public static void close(OutputStream os)
+    {
+        try
+        {
+            if (os != null)
+                os.close();
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return An outputstream to nowhere
+     */
+    public static OutputStream getNullStream()
+    {
+        return __nullStream;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return An outputstream to nowhere
+     */
+    public static InputStream getClosedStream()
+    {
+        return __closedStream;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class NullOS extends OutputStream                                    
+    {
+        @Override
+        public void close(){}
+        @Override
+        public void flush(){}
+        @Override
+        public void write(byte[]b){}
+        @Override
+        public void write(byte[]b,int i,int l){}
+        @Override
+        public void write(int b){}
+    }
+    private static NullOS __nullStream = new NullOS();
+
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class ClosedIS extends InputStream                                    
+    {
+        @Override
+        public int read() throws IOException
+        {
+            return -1;
+        }
+    }
+    private static ClosedIS __closedStream = new ClosedIS();
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return An writer to nowhere
+     */
+    public static Writer getNullWriter()
+    {
+        return __nullWriter;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return An writer to nowhere
+     */
+    public static PrintWriter getNullPrintWriter()
+    {
+        return __nullPrintWriter;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class NullWrite extends Writer                                    
+    {
+        @Override
+        public void close(){}
+        @Override
+        public void flush(){}
+        @Override
+        public void write(char[]b){}
+        @Override
+        public void write(char[]b,int o,int l){}
+        @Override
+        public void write(int b){}
+        @Override
+        public void write(String s){}
+        @Override
+        public void write(String s,int o,int l){}
+    }
+    private static NullWrite __nullWriter = new NullWrite();
+    private static PrintWriter __nullPrintWriter = new PrintWriter(__nullWriter);
+}
+
+
+
+
+
+
+
+
+
diff --git a/lib/jetty/org/eclipse/jetty/util/IPAddressMap.java b/lib/jetty/org/eclipse/jetty/util/IPAddressMap.java
new file mode 100644 (file)
index 0000000..7cbbcab
--- /dev/null
@@ -0,0 +1,364 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Internet address map to object
+ * <p>
+ * Internet addresses may be specified as absolute address or as a combination of 
+ * four octet wildcard specifications (a.b.c.d) that are defined as follows.
+ * </p>
+ * <pre>
+ * nnn - an absolute value (0-255)
+ * mmm-nnn - an inclusive range of absolute values, 
+ *           with following shorthand notations:
+ *           nnn- => nnn-255
+ *           -nnn => 0-nnn
+ *           -    => 0-255
+ * a,b,... - a list of wildcard specifications
+ * </pre>
+ */
+@SuppressWarnings("serial")
+public class IPAddressMap<TYPE> extends HashMap<String, TYPE>
+{
+    private final HashMap<String,IPAddrPattern> _patterns = new HashMap<String,IPAddrPattern>();
+
+    /* --------------------------------------------------------------- */
+    /** Construct empty IPAddressMap.
+     */
+    public IPAddressMap()
+    {
+        super(11);
+    }
+   
+    /* --------------------------------------------------------------- */
+    /** Construct empty IPAddressMap.
+     * 
+     * @param capacity initial capacity
+     */
+    public IPAddressMap(int capacity)
+    {
+        super (capacity);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Insert a new internet address into map
+     * 
+     * @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
+     */
+    @Override
+    public TYPE put(String addrSpec, TYPE object)
+        throws IllegalArgumentException
+    {
+        if (addrSpec == null || addrSpec.trim().length() == 0)
+            throw new IllegalArgumentException("Invalid IP address pattern: "+addrSpec);
+        
+        String spec = addrSpec.trim();
+        if (_patterns.get(spec) == null)
+            _patterns.put(spec,new IPAddrPattern(spec));
+        
+        return super.put(spec, object);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the object mapped to the specified internet address literal
+     * 
+     * @see java.util.HashMap#get(java.lang.Object)
+     */
+    @Override
+    public TYPE get(Object key)
+    {
+        return super.get(key);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the first object that is associated with the specified 
+     * internet address by taking into account the wildcard specifications.
+     * 
+     * @param addr internet address
+     * @return associated object
+     */
+    public TYPE match(String addr)
+    {
+        Map.Entry<String, TYPE> entry = getMatch(addr);
+        return entry==null ? null : entry.getValue();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the first map entry that is associated with the specified 
+     * internet address by taking into account the wildcard specifications.
+     * 
+     * @param addr internet address
+     * @return map entry associated
+     */
+    public Map.Entry<String, TYPE> getMatch(String addr)
+    {
+        if (addr != null)
+        {
+            for(Map.Entry<String, TYPE> entry: super.entrySet())
+            {
+                if (_patterns.get(entry.getKey()).match(addr))
+                {
+                    return entry;
+                }
+            }
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve a lazy list of map entries associated with specified
+     * internet address by taking into account the wildcard specifications.
+     * 
+     * @param addr  internet address
+     * @return lazy list of map entries
+     */
+    public Object getLazyMatches(String addr)
+    {
+        if (addr == null)
+            return LazyList.getList(super.entrySet());
+        
+        Object entries = null;
+        for(Map.Entry<String, TYPE> entry: super.entrySet())
+        {
+            if (_patterns.get(entry.getKey()).match(addr))
+            {
+                entries = LazyList.add(entries,entry);
+            }
+        }
+        return entries;        
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * IPAddrPattern
+     * 
+     * Represents internet address wildcard. 
+     * Matches the wildcard to provided internet address.
+     */
+    private static class IPAddrPattern
+    {
+        private final OctetPattern[] _octets = new OctetPattern[4];
+        /* ------------------------------------------------------------ */
+        /**
+         * Create new IPAddrPattern
+         * 
+         * @param value internet address wildcard specification
+         * @throws IllegalArgumentException if wildcard specification is invalid
+         */
+        public IPAddrPattern(String value)
+            throws IllegalArgumentException
+        {
+            if (value == null || value.trim().length() == 0)
+                throw new IllegalArgumentException("Invalid IP address pattern: "+value);
+                
+            try
+            {
+                StringTokenizer parts = new StringTokenizer(value, ".");
+                
+                String part;
+                for (int idx=0; idx<4; idx++)
+                {
+                    part = parts.hasMoreTokens() ? parts.nextToken().trim() : "0-255";
+                    
+                    int len = part.length();
+                    if (len == 0 && parts.hasMoreTokens())
+                        throw new IllegalArgumentException("Invalid IP address pattern: "+value);
+                    
+                    _octets[idx] = new OctetPattern(len==0 ? "0-255" : part);
+                }
+            }
+            catch (IllegalArgumentException ex)
+            {
+                throw new IllegalArgumentException("Invalid IP address pattern: "+value, ex);
+            }
+        }
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * Match the specified internet address against the wildcard
+         * 
+         * @param value internet address
+         * @return true if specified internet address matches wildcard specification
+         * 
+         * @throws IllegalArgumentException if specified internet address is invalid
+         */
+        public boolean match(String value)
+            throws IllegalArgumentException
+        {
+            if (value == null || value.trim().length() == 0)
+                throw new IllegalArgumentException("Invalid IP address: "+value);
+            
+            try
+            {
+                StringTokenizer parts = new StringTokenizer(value, ".");
+                
+                boolean result = true;
+                for (int idx=0; idx<4; idx++)
+                {
+                    if (!parts.hasMoreTokens())
+                        throw new IllegalArgumentException("Invalid IP address: "+value);
+                        
+                    if (!(result &= _octets[idx].match(parts.nextToken())))
+                        break;
+                }
+                return result;
+            }
+            catch (IllegalArgumentException ex)
+            {
+                throw new IllegalArgumentException("Invalid IP address: "+value, ex);
+            }
+        }
+    }
+        
+    /* ------------------------------------------------------------ */
+    /**
+     * OctetPattern
+     * 
+     * Represents a single octet wildcard.
+     * Matches the wildcard to the specified octet value.
+     */
+    private static class OctetPattern extends BitSet
+    {
+        private final BitSet _mask = new BitSet(256);
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * Create new OctetPattern
+         * 
+         * @param octetSpec octet wildcard specification
+         * @throws IllegalArgumentException if wildcard specification is invalid
+         */
+        public OctetPattern(String octetSpec)
+            throws IllegalArgumentException
+        {
+            try
+            {
+                if (octetSpec != null)
+                {
+                    String spec = octetSpec.trim();
+                    if(spec.length() == 0)
+                    {
+                        _mask.set(0,255);
+                    }
+                    else
+                    {
+                        StringTokenizer parts = new StringTokenizer(spec,",");
+                        while (parts.hasMoreTokens())
+                        {
+                            String part = parts.nextToken().trim();
+                            if (part.length() > 0)
+                            {
+                                if (part.indexOf('-') < 0)
+                                {
+                                    Integer value = Integer.valueOf(part);
+                                    _mask.set(value);
+                                }
+                                else
+                                {
+                                    int low = 0, high = 255;
+                                    
+                                    String[] bounds = part.split("-",-2);
+                                    if (bounds.length != 2)
+                                    {
+                                        throw new IllegalArgumentException("Invalid octet spec: "+octetSpec);
+                                    }
+                                    
+                                    if (bounds[0].length() > 0)
+                                    {
+                                        low = Integer.parseInt(bounds[0]);
+                                    }
+                                    if (bounds[1].length() > 0)
+                                    {
+                                        high = Integer.parseInt(bounds[1]);
+                                    }
+                                    
+                                    if (low > high)
+                                    {
+                                        throw new IllegalArgumentException("Invalid octet spec: "+octetSpec);
+                                    }
+                                    
+                                    _mask.set(low, high+1);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            catch (NumberFormatException ex)
+            {
+                throw new IllegalArgumentException("Invalid octet spec: "+octetSpec, ex);
+            }
+        }
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * Match specified octet value against the wildcard
+         * 
+         * @param value octet value
+         * @return true if specified octet value matches the wildcard
+         * @throws IllegalArgumentException if specified octet value is invalid
+         */
+        public boolean match(String value)
+            throws IllegalArgumentException
+        {
+            if (value == null || value.trim().length() == 0)
+                throw new IllegalArgumentException("Invalid octet: "+value);
+
+            try
+            {
+                int number = Integer.parseInt(value);
+                return match(number);
+            }
+            catch (NumberFormatException ex)
+            {
+                throw new IllegalArgumentException("Invalid octet: "+value);
+            }
+        }
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * Match specified octet value against the wildcard
+         * 
+         * @param number octet value
+         * @return true if specified octet value matches the wildcard
+         * @throws IllegalArgumentException if specified octet value is invalid
+         */
+        public boolean match(int number)
+            throws IllegalArgumentException
+        {
+            if (number < 0 || number > 255)
+                throw new IllegalArgumentException("Invalid octet: "+number);
+            
+            return _mask.get(number);
+        }
+    }   
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/IntrospectionUtil.java b/lib/jetty/org/eclipse/jetty/util/IntrospectionUtil.java
new file mode 100644 (file)
index 0000000..8588675
--- /dev/null
@@ -0,0 +1,300 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * IntrospectionUtil
+ *
+ *
+ */
+public class IntrospectionUtil
+{
+    
+    public static boolean isJavaBeanCompliantSetter (Method method)
+    {
+        if (method == null)
+            return false;
+        
+        if (method.getReturnType() != Void.TYPE)
+            return false;
+        
+        if (!method.getName().startsWith("set"))
+            return false;
+        
+        if (method.getParameterTypes().length != 1)
+            return false;
+        
+        return true;
+    }
+    
+    public static Method findMethod (Class<?> clazz, String methodName, Class<?>[] args, boolean checkInheritance, boolean strictArgs)
+    throws NoSuchMethodException
+    {
+        if (clazz == null)
+            throw new NoSuchMethodException("No class");
+        if (methodName==null || methodName.trim().equals(""))
+            throw new NoSuchMethodException("No method name");
+        
+        Method method = null;
+        Method[] methods = clazz.getDeclaredMethods();
+        for (int i=0;i<methods.length && method==null;i++)
+        {
+            if (methods[i].getName().equals(methodName) && checkParams(methods[i].getParameterTypes(), (args==null?new Class[] {}:args), strictArgs))
+            {
+                method = methods[i];
+            }
+            
+        }
+        if (method!=null)
+        {
+            return method;
+        }
+        else if (checkInheritance)
+                return findInheritedMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, args, strictArgs);
+        else
+            throw new NoSuchMethodException("No such method "+methodName+" on class "+clazz.getName());
+
+    }
+    
+    
+    
+    
+
+    public static Field findField (Class<?> clazz, String targetName, Class<?> targetType, boolean checkInheritance, boolean strictType)
+    throws NoSuchFieldException
+    {
+        if (clazz == null)
+            throw new NoSuchFieldException("No class");
+        if (targetName==null)
+            throw new NoSuchFieldException("No field name");
+        
+        try
+        {
+            Field field = clazz.getDeclaredField(targetName);
+            if (strictType)
+            {
+                if (field.getType().equals(targetType))
+                    return field;
+            }
+            else
+            {
+                if (field.getType().isAssignableFrom(targetType))
+                    return field;
+            }
+            if (checkInheritance)
+            {
+                    return findInheritedField(clazz.getPackage(), clazz.getSuperclass(), targetName, targetType, strictType);
+            }
+            else
+                throw new NoSuchFieldException("No field with name "+targetName+" in class "+clazz.getName()+" of type "+targetType);
+        }
+        catch (NoSuchFieldException e)
+        {
+            return findInheritedField(clazz.getPackage(),clazz.getSuperclass(), targetName,targetType,strictType);
+        }
+    }
+    
+    
+    
+    
+    
+    public static boolean isInheritable (Package pack, Member member)
+    {
+        if (pack==null)
+            return false;
+        if (member==null)
+            return false;
+        
+        int modifiers = member.getModifiers();
+        if (Modifier.isPublic(modifiers))
+            return true;
+        if (Modifier.isProtected(modifiers))
+            return true;
+        if (!Modifier.isPrivate(modifiers) && pack.equals(member.getDeclaringClass().getPackage()))
+            return true;
+       
+        return false;
+    }
+    
+   
+    
+    
+    public static boolean checkParams (Class<?>[] formalParams, Class<?>[] actualParams, boolean strict)
+    {
+        if (formalParams==null)
+            return actualParams==null;
+        if (actualParams==null)
+            return false;
+
+        if (formalParams.length!=actualParams.length)
+            return false;
+
+        if (formalParams.length==0)
+            return true; 
+        
+        int j=0;
+        if (strict)
+        {
+            while (j<formalParams.length && formalParams[j].equals(actualParams[j]))
+                j++;
+        }
+        else
+        { 
+            while ((j<formalParams.length) && (formalParams[j].isAssignableFrom(actualParams[j])))
+            {
+                j++;
+            }
+        }
+
+        if (j!=formalParams.length)
+        {
+            return false;
+        }
+
+        return true;
+    }
+    
+    
+    public static boolean isSameSignature (Method methodA, Method methodB)
+    {
+        if (methodA==null)
+            return false;
+        if (methodB==null)
+            return false;
+        
+        List<Class<?>> parameterTypesA = Arrays.asList(methodA.getParameterTypes());
+        List<Class<?>> parameterTypesB = Arrays.asList(methodB.getParameterTypes());
+       
+        if (methodA.getName().equals(methodB.getName())
+            &&
+            parameterTypesA.containsAll(parameterTypesB))
+            return true;
+        
+        return false;
+    }
+    
+    public static boolean isTypeCompatible (Class<?> formalType, Class<?> actualType, boolean strict)
+    {
+        if (formalType==null)
+            return actualType==null;
+        if (actualType==null)
+            return false;
+        
+        if (strict)
+            return formalType.equals(actualType);
+        else
+            return formalType.isAssignableFrom(actualType);
+    }
+
+    
+    
+    
+    public static boolean containsSameMethodSignature (Method method, Class<?> c, boolean checkPackage)
+    {
+        if (checkPackage)
+        {
+            if (!c.getPackage().equals(method.getDeclaringClass().getPackage()))
+                return false;
+        }
+        
+        boolean samesig = false;
+        Method[] methods = c.getDeclaredMethods();
+        for (int i=0; i<methods.length && !samesig; i++)
+        {
+            if (IntrospectionUtil.isSameSignature(method, methods[i]))
+                samesig = true;
+        }
+        return samesig;
+    }
+    
+    
+    public static boolean containsSameFieldName(Field field, Class<?> c, boolean checkPackage)
+    {
+        if (checkPackage)
+        {
+            if (!c.getPackage().equals(field.getDeclaringClass().getPackage()))
+                return false;
+        }
+        
+        boolean sameName = false;
+        Field[] fields = c.getDeclaredFields();
+        for (int i=0;i<fields.length && !sameName; i++)
+        {
+            if (fields[i].getName().equals(field.getName()))
+                sameName = true;
+        }
+        return sameName;
+    }
+    
+    
+    
+    protected static Method findInheritedMethod (Package pack, Class<?> clazz, String methodName, Class<?>[] args, boolean strictArgs)
+    throws NoSuchMethodException
+    {
+        if (clazz==null)
+            throw new NoSuchMethodException("No class");
+        if (methodName==null)
+            throw new NoSuchMethodException("No method name");
+        
+        Method method = null;
+        Method[] methods = clazz.getDeclaredMethods();
+        for (int i=0;i<methods.length && method==null;i++)
+        {
+            if (methods[i].getName().equals(methodName) 
+                    && isInheritable(pack,methods[i])
+                    && checkParams(methods[i].getParameterTypes(), args, strictArgs))
+                method = methods[i];
+        }
+        if (method!=null)
+        {
+            return method;
+        }
+        else
+            return findInheritedMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, args, strictArgs);
+    }
+    
+    protected static Field findInheritedField (Package pack, Class<?> clazz, String fieldName, Class<?> fieldType, boolean strictType)
+    throws NoSuchFieldException
+    {
+        if (clazz==null)
+            throw new NoSuchFieldException ("No class");
+        if (fieldName==null)
+            throw new NoSuchFieldException ("No field name");
+        try
+        {
+            Field field = clazz.getDeclaredField(fieldName);
+            if (isInheritable(pack, field) && isTypeCompatible(fieldType, field.getType(), strictType))
+                return field;
+            else
+                return findInheritedField(clazz.getPackage(), clazz.getSuperclass(),fieldName, fieldType, strictType);
+        }
+        catch (NoSuchFieldException e)
+        {
+            return findInheritedField(clazz.getPackage(), clazz.getSuperclass(),fieldName, fieldType, strictType); 
+        }
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/IteratingCallback.java b/lib/jetty/org/eclipse/jetty/util/IteratingCallback.java
new file mode 100644 (file)
index 0000000..1c74d5e
--- /dev/null
@@ -0,0 +1,374 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * This specialized callback implements a pattern that allows
+ * a large job to be broken into smaller tasks using iteration
+ * rather than recursion.
+ * <p/>
+ * A typical example is the write of a large content to a socket,
+ * divided in chunks. Chunk C1 is written by thread T1, which
+ * also invokes the callback, which writes chunk C2, which invokes
+ * the callback again, which writes chunk C3, and so forth.
+ * <p/>
+ * The problem with the example is that if the callback thread
+ * is the same that performs the I/O operation, then the process
+ * is recursive and may result in a stack overflow.
+ * To avoid the stack overflow, a thread dispatch must be performed,
+ * causing context switching and cache misses, affecting performance.
+ * <p/>
+ * To avoid this issue, this callback uses an AtomicReference to
+ * record whether success callback has been called during the processing
+ * of a sub task, and if so then the processing iterates rather than
+ * recurring.
+ * <p/>
+ * Subclasses must implement method {@link #process()} where the sub
+ * task is executed and a suitable {@link IteratingCallback.Action} is
+ * returned to this callback to indicate the overall progress of the job.
+ * This callback is passed to the asynchronous execution of each sub
+ * task and a call the {@link #succeeded()} on this callback represents
+ * the completion of the sub task.
+ */
+public abstract class IteratingCallback implements Callback
+{
+    /**
+     * The indication of the overall progress of the overall job that
+     * implementations of {@link #process()} must return.
+     */
+    protected enum Action
+    {
+        /**
+         * Indicates that {@link #process()} has no more work to do,
+         * but the overall job is not completed yet, probably waiting
+         * for additional events to trigger more work.
+         */
+        IDLE,
+        /**
+         * Indicates that {@link #process()} is executing asynchronously
+         * a sub task, where the execution has started but the callback
+         * may have not yet been invoked.
+         */
+        SCHEDULED,
+        /**
+         * Indicates that {@link #process()} has completed the overall job.
+         */
+        SUCCEEDED,
+        /**
+         * Indicates that {@link #process()} has failed the overall job.
+         */
+        FAILED
+    }
+
+    private final AtomicReference<State> _state = new AtomicReference<>(State.INACTIVE);
+
+    /**
+     * Method called by {@link #iterate()} to process the sub task.
+     * <p/>
+     * Implementations must start the asynchronous execution of the sub task
+     * (if any) and return an appropriate action:
+     * <ul>
+     * <li>{@link Action#IDLE} when no sub tasks are available for execution
+     * but the overall job is not completed yet</li>
+     * <li>{@link Action#SCHEDULED} when the sub task asynchronous execution
+     * has been started</li>
+     * <li>{@link Action#SUCCEEDED} when the overall job is completed</li>
+     * <li>{@link Action#FAILED} when the overall job cannot be completed</li>
+     * </ul>
+     *
+     * @throws Exception if the sub task processing throws
+     */
+    protected abstract Action process() throws Exception;
+
+    /**
+     * Invoked when the overall task has completed successfully.
+     */
+    protected abstract void completed();
+
+    /**
+     * This method must be invoked by applications to start the processing
+     * of sub tasks.
+     * <p/>
+     * If {@link #process()} returns {@link Action#IDLE}, then this method
+     * should be called again to restart processing.
+     * It is safe to call iterate multiple times from multiple threads since only
+     * the first thread to move the state out of INACTIVE will actually do any iteration
+     * and processing.
+     */
+    public void iterate()
+    {
+        try
+        {
+            while (true)
+            {
+                switch (_state.get())
+                {
+                    case INACTIVE:
+                    {
+                        if (processIterations())
+                            return;
+                        break;
+                    }
+                    case ITERATING:
+                    {
+                        if (_state.compareAndSet(State.ITERATING, State.ITERATE_AGAIN))
+                            return;
+                        break;
+                    }
+                    default:
+                    {
+                        return;
+                    }
+                }
+            }
+        }
+        catch (Throwable x)
+        {
+            failed(x);
+        }
+    }
+
+    private boolean processIterations() throws Exception
+    {
+        // Keeps iterating as long as succeeded() is called during process().
+        // If we are in INACTIVE state, either this is the first iteration or
+        // succeeded()/failed() were called already.
+        while (_state.compareAndSet(State.INACTIVE, State.ITERATING))
+        {
+            // Method process() can only be called by one thread at a time because
+            // it is guarded by the CaS above. However, the case blocks below may
+            // be executed concurrently in this case: T1 calls process() which
+            // executes the asynchronous sub task, which calls succeeded(), which
+            // moves the state into INACTIVE, then returns SCHEDULED; T2 calls
+            // iterate(), state is now INACTIVE and process() is called again and
+            // returns another action. Now we have 2 threads that may execute the
+            // action case blocks below concurrently; therefore each case block
+            // has to be prepared to fail the CaS it's doing.
+
+            Action action = process();
+            switch (action)
+            {
+                case IDLE:
+                {
+                    // No more progress can be made.
+                    if (_state.compareAndSet(State.ITERATING, State.INACTIVE))
+                        return true;
+
+                    // Was iterate() called again since we already decided to go INACTIVE ?
+                    // If so, try another iteration as more work may have been added
+                    // while the previous call to process() was returning.
+                    if (_state.compareAndSet(State.ITERATE_AGAIN, State.INACTIVE))
+                        continue;
+
+                    // State may have changed concurrently, try again.
+                    continue;
+                }
+                case SCHEDULED:
+                {
+                    // The sub task is executing, and the callback for it may or
+                    // may not have already been called yet, which we figure out below.
+                    // Can double CaS here because state never changes directly ITERATING_AGAIN --> ITERATE.
+                    if (_state.compareAndSet(State.ITERATING, State.ACTIVE) ||
+                            _state.compareAndSet(State.ITERATE_AGAIN, State.ACTIVE))
+                        // Not called back yet, so wait.
+                        return true;
+                    // Call back must have happened, so iterate.
+                    continue;
+                }
+                case SUCCEEDED:
+                {
+                    // The overall job has completed.
+                    if (completeSuccess())
+                        completed();
+                    return true;
+                }
+                case FAILED:
+                {
+                    completeFailure();
+                    return true;
+                }
+                default:
+                {
+                    throw new IllegalStateException(toString());
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Invoked when the sub task succeeds.
+     * Subclasses that override this method must always remember to call
+     * {@code super.succeeded()}.
+     */
+    @Override
+    public void succeeded()
+    {
+        while (true)
+        {
+            State current = _state.get();
+            switch (current)
+            {
+                case ITERATE_AGAIN:
+                case ITERATING:
+                {
+                    if (_state.compareAndSet(current, State.INACTIVE))
+                        return;
+                    continue;
+                }
+                case ACTIVE:
+                {
+                    // If we can move from ACTIVE to INACTIVE
+                    // then we are responsible to call iterate().
+                    if (_state.compareAndSet(current, State.INACTIVE))
+                        iterate();
+                    // If we can't CaS, then failed() must have been
+                    // called, and we just return.
+                    return;
+                }
+                case INACTIVE:
+                {
+                    // Support the case where the callback is scheduled
+                    // externally without a call to iterate().
+                    iterate();
+                    return;
+                }
+                default:
+                {
+                    throw new IllegalStateException(toString());
+                }
+            }
+        }
+    }
+
+    /**
+     * Invoked when the sub task fails.
+     * Subclasses that override this method must always remember to call
+     * {@code super.failed(Throwable)}.
+     */
+    @Override
+    public void failed(Throwable x)
+    {
+        completeFailure();
+    }
+
+    private boolean completeSuccess()
+    {
+        while (true)
+        {
+            State current = _state.get();
+            if (current == State.FAILED)
+            {
+                // Success arrived too late, sorry.
+                return false;
+            }
+            else
+            {
+                if (_state.compareAndSet(current, State.SUCCEEDED))
+                    return true;
+            }
+        }
+    }
+
+    private void completeFailure()
+    {
+        while (true)
+        {
+            State current = _state.get();
+            if (current == State.SUCCEEDED)
+            {
+                // Failed arrived too late, sorry.
+                return;
+            }
+            else
+            {
+                if (_state.compareAndSet(current, State.FAILED))
+                    break;
+            }
+        }
+    }
+
+    /**
+     * @return whether this callback is idle and {@link #iterate()} needs to be called
+     */
+    public boolean isIdle()
+    {
+        return _state.get() == State.INACTIVE;
+    }
+
+    /**
+     * @return whether this callback has failed
+     */
+    public boolean isFailed()
+    {
+        return _state.get() == State.FAILED;
+    }
+
+    /**
+     * @return whether this callback has succeeded
+     */
+    public boolean isSucceeded()
+    {
+        return _state.get() == State.SUCCEEDED;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s]", super.toString(), _state);
+    }
+
+    /**
+     * The internal states of this callback
+     */
+    private enum State
+    {
+        /**
+         * This callback is inactive, ready to iterate.
+         */
+        INACTIVE,
+        /**
+         * This callback is iterating and {@link #process()} has scheduled an
+         * asynchronous operation by returning {@link Action#SCHEDULED}, but
+         * the operation is still undergoing.
+         */
+        ACTIVE,
+        /**
+         * This callback is iterating and {@link #process()} has been called
+         * but not returned yet.
+         */
+        ITERATING,
+        /**
+         * While this callback was iterating, another request for iteration
+         * has been issued, so the iteration must continue even if a previous
+         * call to {@link #process()} returned {@link Action#IDLE}.
+         */
+        ITERATE_AGAIN,
+        /**
+         * The overall job has succeeded.
+         */
+        SUCCEEDED,
+        /**
+         * The overall job has failed.
+         */
+        FAILED
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/IteratingNestedCallback.java b/lib/jetty/org/eclipse/jetty/util/IteratingNestedCallback.java
new file mode 100644 (file)
index 0000000..e018f43
--- /dev/null
@@ -0,0 +1,68 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+
+/* ------------------------------------------------------------ */
+/** Iterating Nested Callback.
+ * <p>This specialized callback is used when breaking up an
+ * asynchronous task into smaller asynchronous tasks.  A typical pattern
+ * is that a successful callback is used to schedule the next sub task, but 
+ * if that task completes quickly and uses the calling thread to callback
+ * the success notification, this can result in a growing stack depth.
+ * </p>
+ * <p>To avoid this issue, this callback uses an AtomicBoolean to note 
+ * if the success callback has been called during the processing of a 
+ * sub task, and if so then the processing iterates rather than recurses.
+ * </p>
+ * <p>This callback is passed to the asynchronous handling of each sub
+ * task and a call the {@link #succeeded()} on this call back represents
+ * completion of the subtask.  Only once all the subtasks are completed is 
+ * the {@link Callback#succeeded()} method called on the {@link Callback} instance
+ * passed the the {@link #IteratingNestedCallback(Callback)} constructor.</p>
+ *  
+ */
+public abstract class IteratingNestedCallback extends IteratingCallback
+{
+    final Callback _callback;
+    
+    public IteratingNestedCallback(Callback callback)
+    {
+        _callback=callback;
+    }
+    
+    @Override
+    protected void completed()
+    {
+        _callback.succeeded();
+    }
+
+    @Override
+    public void failed(Throwable x)
+    {
+        super.failed(x);
+        _callback.failed(x);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x",getClass().getSimpleName(),hashCode());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Jetty.java b/lib/jetty/org/eclipse/jetty/util/Jetty.java
new file mode 100644 (file)
index 0000000..5a9b257
--- /dev/null
@@ -0,0 +1,39 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+public class Jetty
+{
+    public static final String VERSION;
+
+    static
+    {
+        Package pkg = Jetty.class.getPackage();
+        if (pkg != null &&
+                "Eclipse.org - Jetty".equals(pkg.getImplementationVendor()) &&
+                pkg.getImplementationVersion() != null)
+            VERSION = pkg.getImplementationVersion();
+        else
+            VERSION = System.getProperty("jetty.version", "9.2.z-SNAPSHOT");
+    }
+
+    private Jetty()
+    {
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/LazyList.java b/lib/jetty/org/eclipse/jetty/util/LazyList.java
new file mode 100644 (file)
index 0000000..6ff416e
--- /dev/null
@@ -0,0 +1,451 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/* ------------------------------------------------------------ */
+/** Lazy List creation.
+ * A List helper class that attempts to avoid unnecessary List
+ * creation.   If a method needs to create a List to return, but it is
+ * expected that this will either be empty or frequently contain a
+ * single item, then using LazyList will avoid additional object
+ * creations by using {@link Collections#EMPTY_LIST} or
+ * {@link Collections#singletonList(Object)} where possible.
+ * <p>
+ * LazyList works by passing an opaque representation of the list in
+ * and out of all the LazyList methods.  This opaque object is either
+ * null for an empty list, an Object for a list with a single entry
+ * or an {@link ArrayList} for a list of items.
+ *
+ * <p><h4>Usage</h4>
+ * <pre>
+ *   Object lazylist =null;
+ *   while(loopCondition)
+ *   {
+ *     Object item = getItem();
+ *     if (item.isToBeAdded())
+ *         lazylist = LazyList.add(lazylist,item);
+ *   }
+ *   return LazyList.getList(lazylist);
+ * </pre>
+ *
+ * An ArrayList of default size is used as the initial LazyList.
+ *
+ * @see java.util.List
+ */
+@SuppressWarnings("serial")
+public class LazyList
+    implements Cloneable, Serializable
+{
+    private static final String[] __EMTPY_STRING_ARRAY = new String[0];
+    
+    /* ------------------------------------------------------------ */
+    private LazyList()
+    {}
+    
+    /* ------------------------------------------------------------ */
+    /** Add an item to a LazyList 
+     * @param list The list to add to or null if none yet created.
+     * @param item The item to add.
+     * @return The lazylist created or added to.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object add(Object list, Object item)
+    {
+        if (list==null)
+        {
+            if (item instanceof List || item==null)
+            {
+                List<Object> l = new ArrayList<Object>();
+                l.add(item);
+                return l;
+            }
+
+            return item;
+        }
+
+        if (list instanceof List)
+        {
+            ((List<Object>)list).add(item);
+            return list;
+        }
+
+        List<Object> l=new ArrayList<Object>();
+        l.add(list);
+        l.add(item);
+        return l;    
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add an item to a LazyList 
+     * @param list The list to add to or null if none yet created.
+     * @param index The index to add the item at.
+     * @param item The item to add.
+     * @return The lazylist created or added to.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object add(Object list, int index, Object item)
+    {
+        if (list==null)
+        {
+            if (index>0 || item instanceof List || item==null)
+            {
+                List<Object> l = new ArrayList<Object>();
+                l.add(index,item);
+                return l;
+            }
+            return item;
+        }
+
+        if (list instanceof List)
+        {
+            ((List<Object>)list).add(index,item);
+            return list;
+        }
+
+        List<Object> l=new ArrayList<Object>();
+        l.add(list);
+        l.add(index,item);
+        return l;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add the contents of a Collection to a LazyList
+     * @param list The list to add to or null if none yet created.
+     * @param collection The Collection whose contents should be added.
+     * @return The lazylist created or added to.
+     */
+    public static Object addCollection(Object list, Collection<?> collection)
+    {
+        Iterator<?> i=collection.iterator();
+        while(i.hasNext())
+            list=LazyList.add(list,i.next());
+        return list;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add the contents of an array to a LazyList
+     * @param list The list to add to or null if none yet created.
+     * @param array The array whose contents should be added.
+     * @return The lazylist created or added to.
+     */
+    public static Object addArray(Object list, Object[] array)
+    {
+        for(int i=0;array!=null && i<array.length;i++)
+            list=LazyList.add(list,array[i]);
+        return list;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Ensure the capacity of the underlying list.
+     * 
+     */
+    public static Object ensureSize(Object list, int initialSize)
+    {
+        if (list==null)
+            return new ArrayList<Object>(initialSize);
+        if (list instanceof ArrayList)
+        {
+            ArrayList<?> ol=(ArrayList<?>)list;
+            if (ol.size()>initialSize)
+                return ol;
+            ArrayList<Object> nl = new ArrayList<Object>(initialSize);
+            nl.addAll(ol);
+            return nl;
+        }
+        List<Object> l= new ArrayList<Object>(initialSize);
+        l.add(list);
+        return l;    
+    }
+
+    /* ------------------------------------------------------------ */
+    public static Object remove(Object list, Object o)
+    {
+        if (list==null)
+            return null;
+
+        if (list instanceof List)
+        {
+            List<?> l = (List<?>)list;
+            l.remove(o);
+            if (l.size()==0)
+                return null;
+            return list;
+        }
+
+        if (list.equals(o))
+            return null;
+        return list;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static Object remove(Object list, int i)
+    {
+        if (list==null)
+            return null;
+
+        if (list instanceof List)
+        {
+            List<?> l = (List<?>)list;
+            l.remove(i);
+            if (l.size()==0)
+                return null;
+            return list;
+        }
+
+        if (i==0)
+            return null;
+        return list;
+    }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Get the real List from a LazyList.
+     * 
+     * @param list A LazyList returned from LazyList.add(Object)
+     * @return The List of added items, which may be an EMPTY_LIST
+     * or a SingletonList.
+     */
+    public static<E> List<E> getList(Object list)
+    {
+        return getList(list,false);
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /** Get the real List from a LazyList.
+     * 
+     * @param list A LazyList returned from LazyList.add(Object) or null
+     * @param nullForEmpty If true, null is returned instead of an
+     * empty list.
+     * @return The List of added items, which may be null, an EMPTY_LIST
+     * or a SingletonList.
+     */
+    @SuppressWarnings("unchecked")
+    public static<E> List<E> getList(Object list, boolean nullForEmpty)
+    {
+        if (list==null)
+        {
+            if (nullForEmpty)
+                return null;
+            return Collections.emptyList();
+        }
+        if (list instanceof List)
+            return (List<E>)list;
+        
+        return (List<E>)Collections.singletonList(list);
+    }
+    
+    /**
+     * Simple utility method to test if List has at least 1 entry.
+     * 
+     * @param list
+     *            a LazyList, {@link List} or {@link Object}
+     * @return true if not-null and is not empty
+     */
+    public static boolean hasEntry(Object list)
+    {
+        if (list == null)
+            return false;
+        if (list instanceof List)
+            return !((List<?>)list).isEmpty();
+        return true;
+    }
+    
+    /**
+     * Simple utility method to test if List is empty
+     * 
+     * @param list
+     *            a LazyList, {@link List} or {@link Object}
+     * @return true if null or is empty
+     */
+    public static boolean isEmpty(Object list)
+    {
+        if (list == null)
+            return true;
+        if (list instanceof List)
+            return ((List<?>)list).isEmpty();
+        return false;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    public static String[] toStringArray(Object list)
+    {
+        if (list==null)
+            return __EMTPY_STRING_ARRAY;
+        
+        if (list instanceof List)
+        {
+            List<?> l = (List<?>)list;
+            String[] a = new String[l.size()];
+            for (int i=l.size();i-->0;)
+            {
+                Object o=l.get(i);
+                if (o!=null)
+                    a[i]=o.toString();
+            }
+            return a;
+        }
+        
+        return new String[] {list.toString()};
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert a lazylist to an array
+     * @param list The list to convert
+     * @param clazz The class of the array, which may be a primitive type
+     * @return array of the lazylist entries passed in
+     */
+    public static Object toArray(Object list,Class<?> clazz)
+    {
+        if (list==null)
+            return Array.newInstance(clazz,0);
+        
+        if (list instanceof List)
+        {
+            List<?> l = (List<?>)list;
+            if (clazz.isPrimitive())
+            {
+                Object a = Array.newInstance(clazz,l.size());
+                for (int i=0;i<l.size();i++)
+                    Array.set(a,i,l.get(i));
+                return a;
+            }
+            return l.toArray((Object[])Array.newInstance(clazz,l.size()));
+            
+        }
+        
+        Object a = Array.newInstance(clazz,1);
+        Array.set(a,0,list);
+        return a;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** The size of a lazy List 
+     * @param list  A LazyList returned from LazyList.add(Object) or null
+     * @return the size of the list.
+     */
+    public static int size(Object list)
+    {
+        if (list==null)
+            return 0;
+        if (list instanceof List)
+            return ((List<?>)list).size();
+        return 1;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get item from the list 
+     * @param list  A LazyList returned from LazyList.add(Object) or null
+     * @param i int index
+     * @return the item from the list.
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E get(Object list, int i)
+    {
+        if (list==null)
+            throw new IndexOutOfBoundsException();
+       
+        if (list instanceof List)
+            return (E)((List<?>)list).get(i);
+
+        if (i==0)
+            return (E)list;
+        
+        throw new IndexOutOfBoundsException();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static boolean contains(Object list,Object item)
+    {
+        if (list==null)
+            return false;
+        
+        if (list instanceof List)
+            return ((List<?>)list).contains(item);
+
+        return list.equals(item);
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public static Object clone(Object list)
+    {
+        if (list==null)
+            return null;
+        if (list instanceof List)
+            return new ArrayList<Object>((List<?>)list);
+        return list;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static String toString(Object list)
+    {
+        if (list==null)
+            return "[]";
+        if (list instanceof List)
+            return list.toString();
+        return "["+list+"]";
+    }
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    public static<E> Iterator<E> iterator(Object list)
+    {
+        if (list==null)
+        {
+            List<E> empty=Collections.emptyList();
+            return empty.iterator();
+        }
+        if (list instanceof List)
+        {
+            return ((List<E>)list).iterator();
+        }
+        List<E> l=getList(list);
+        return l.iterator();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    public static<E> ListIterator<E> listIterator(Object list)
+    {
+        if (list==null)
+        {
+            List<E> empty=Collections.emptyList();
+            return empty.listIterator();
+        }
+        if (list instanceof List)
+            return ((List<E>)list).listIterator();
+
+        List<E> l=getList(list);
+        return l.listIterator();
+    }
+    
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/util/LeakDetector.java b/lib/jetty/org/eclipse/jetty/util/LeakDetector.java
new file mode 100644 (file)
index 0000000..b0ac94e
--- /dev/null
@@ -0,0 +1,201 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * A facility to detect improper usage of resource pools.
+ * <p>
+ * Resource pools usually have a method to acquire a pooled resource
+ * and a method to released it back to the pool.
+ * <p>
+ * To detect if client code acquires a resource but never releases it,
+ * the resource pool can be modified to use a {@link LeakDetector}.
+ * The modified resource pool should call {@link #acquired(Object)} every time
+ * the method to acquire a resource is called, and {@link #released(Object)}
+ * every time the method to release the resource is called.
+ * {@link LeakDetector} keeps track of these resources and invokes method
+ * {@link #leaked(org.eclipse.jetty.util.LeakDetector.LeakInfo)} when it detects that a resource
+ * has been leaked (that is, acquired but never released).
+ * <p>
+ * To detect whether client code releases a resource without having
+ * acquired it, the resource pool can be modified to check the return value
+ * of {@link #released(Object)}: if false, it means that the resource was
+ * not acquired.
+ * <p>
+ * IMPLEMENTATION NOTES
+ * <p>
+ * This class relies on {@link System#identityHashCode(Object)} to create
+ * a unique id for each resource passed to {@link #acquired(Object)} and
+ * {@link #released(Object)}. {@link System#identityHashCode(Object)} does
+ * not guarantee that it will not generate the same number for different
+ * objects, but in practice the chance of collision is rare.
+ * <p>
+ * {@link LeakDetector} uses {@link PhantomReference}s to detect leaks.
+ * {@link PhantomReference}s are enqueued in their {@link ReferenceQueue}
+ * <em>after</em> they have been garbage collected (differently from
+ * {@link WeakReference}s that are enqueued <em>before</em>).
+ * Since the resource is now garbage collected, {@link LeakDetector} checks
+ * whether it has been released and if not, it reports a leak.
+ * Using {@link PhantomReference}s is better than overriding {@link #finalize()}
+ * and works also in those cases where {@link #finalize()} is not
+ * overridable.
+ *
+ * @param <T> the resource type.
+ */
+public class LeakDetector<T> extends AbstractLifeCycle implements Runnable
+{
+    private static final Logger LOG = Log.getLogger(LeakDetector.class);
+
+    private final ReferenceQueue<T> queue = new ReferenceQueue<>();
+    private final ConcurrentMap<String, LeakInfo> resources = new ConcurrentHashMap<>();
+    private Thread thread;
+
+    /**
+     * Tracks the resource as been acquired.
+     *
+     * @param resource the resource that has been acquired
+     * @return whether the resource has been tracked
+     * @see #released(Object)
+     */
+    public boolean acquired(T resource)
+    {
+        String id = id(resource);
+        return resources.putIfAbsent(id, new LeakInfo(resource, id)) == null;
+    }
+
+    /**
+     * Tracks the resource as been released.
+     *
+     * @param resource the resource that has been released
+     * @return whether the resource has been acquired
+     * @see #acquired(Object)
+     */
+    public boolean released(T resource)
+    {
+        String id = id(resource);
+        return resources.remove(id) != null;
+    }
+
+    /**
+     * Generates a unique ID for the given resource.
+     *
+     * @param resource the resource to generate the unique ID for
+     * @return the unique ID of the given resource
+     */
+    protected String id(T resource)
+    {
+        return String.valueOf(System.identityHashCode(resource));
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        thread = new Thread(this, getClass().getSimpleName());
+        thread.setDaemon(true);
+        thread.start();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        thread.interrupt();
+        super.doStop();
+    }
+
+    @Override
+    public void run()
+    {
+        try
+        {
+            while (isRunning())
+            {
+                @SuppressWarnings("unchecked")
+                LeakInfo leakInfo = (LeakInfo)queue.remove();
+                LOG.debug("Resource GC'ed: {}", leakInfo);
+                if (resources.remove(leakInfo.id) != null)
+                    leaked(leakInfo);
+            }
+        }
+        catch (InterruptedException x)
+        {
+            // Exit
+        }
+    }
+
+    /**
+     * Callback method invoked by {@link LeakDetector} when it detects that a resource has been leaked.
+     *
+     * @param leakInfo the information about the leak
+     */
+    protected void leaked(LeakInfo leakInfo)
+    {
+        LOG.warn("Resource leaked: " + leakInfo.description, leakInfo.stackFrames);
+    }
+
+    /**
+     * Information about the leak of a resource.
+     */
+    public class LeakInfo extends PhantomReference<T>
+    {
+        private final String id;
+        private final String description;
+        private final Throwable stackFrames;
+
+        private LeakInfo(T referent, String id)
+        {
+            super(referent, queue);
+            this.id = id;
+            this.description = referent.toString();
+            this.stackFrames = new Throwable();
+        }
+
+        /**
+         * @return the resource description as provided by the resource's {@link Object#toString()} method.
+         */
+        public String getResourceDescription()
+        {
+            return description;
+        }
+
+        /**
+         * @return a Throwable instance that contains the stack frames at the time of resource acquisition.
+         */
+        public Throwable getStackFrames()
+        {
+            return stackFrames;
+        }
+
+        @Override
+        public String toString()
+        {
+            return description;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Loader.java b/lib/jetty/org/eclipse/jetty/util/Loader.java
new file mode 100644 (file)
index 0000000..dfb87fd
--- /dev/null
@@ -0,0 +1,183 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/** ClassLoader Helper.
+ * This helper class allows classes to be loaded either from the
+ * Thread's ContextClassLoader, the classloader of the derived class
+ * or the system ClassLoader.
+ *
+ * <B>Usage:</B><PRE>
+ * public class MyClass {
+ *     void myMethod() {
+ *          ...
+ *          Class c=Loader.loadClass(this.getClass(),classname);
+ *          ...
+ *     }
+ * </PRE>          
+ * 
+ */
+public class Loader
+{
+    /* ------------------------------------------------------------ */
+    public static URL getResource(Class<?> loadClass,String name)
+    {
+        URL url =null;
+        ClassLoader context_loader=Thread.currentThread().getContextClassLoader();
+        if (context_loader!=null)
+            url=context_loader.getResource(name); 
+        
+        if (url==null && loadClass!=null)
+        {
+            ClassLoader load_loader=loadClass.getClassLoader();
+            if (load_loader!=null && load_loader!=context_loader)
+                url=load_loader.getResource(name);
+        }
+
+        if (url==null)
+            url=ClassLoader.getSystemResource(name);
+
+        return url;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Load a class.
+     * 
+     * @param loadClass
+     * @param name
+     * @return Class
+     * @throws ClassNotFoundException
+     */
+    @SuppressWarnings("rawtypes")
+    public static Class loadClass(Class loadClass,String name)
+        throws ClassNotFoundException
+    {
+        ClassNotFoundException ex=null;
+        Class<?> c =null;
+        ClassLoader context_loader=Thread.currentThread().getContextClassLoader();
+        if (context_loader!=null )
+        {
+            try { c=context_loader.loadClass(name); }
+            catch (ClassNotFoundException e) {ex=e;}
+        }    
+        
+        if (c==null && loadClass!=null)
+        {
+            ClassLoader load_loader=loadClass.getClassLoader();
+            if (load_loader!=null && load_loader!=context_loader)
+            {
+                try { c=load_loader.loadClass(name); }
+                catch (ClassNotFoundException e) {if(ex==null)ex=e;}
+            }
+        }
+
+        if (c==null)
+        {
+            try { c=Class.forName(name); }
+            catch (ClassNotFoundException e) 
+            {
+                if(ex!=null)
+                    throw ex;
+                throw e;
+            }
+        }   
+
+        return c;
+    }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    public static ResourceBundle getResourceBundle(Class<?> loadClass,String name,boolean checkParents, Locale locale)
+        throws MissingResourceException
+    {
+        MissingResourceException ex=null;
+        ResourceBundle bundle =null;
+        ClassLoader loader=Thread.currentThread().getContextClassLoader();
+        while (bundle==null && loader!=null )
+        {
+            try { bundle=ResourceBundle.getBundle(name, locale, loader); }
+            catch (MissingResourceException e) {if(ex==null)ex=e;}
+            loader=(bundle==null&&checkParents)?loader.getParent():null;
+        }      
+        
+        loader=loadClass==null?null:loadClass.getClassLoader();
+        while (bundle==null && loader!=null )
+        {
+            try { bundle=ResourceBundle.getBundle(name, locale, loader); }
+            catch (MissingResourceException e) {if(ex==null)ex=e;}
+            loader=(bundle==null&&checkParents)?loader.getParent():null;
+        }       
+
+        if (bundle==null)
+        {
+            try { bundle=ResourceBundle.getBundle(name, locale); }
+            catch (MissingResourceException e) {if(ex==null)ex=e;}
+        }   
+
+        if (bundle!=null)
+            return bundle;
+        throw ex;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Generate the classpath (as a string) of all classloaders
+     * above the given classloader.
+     * 
+     * This is primarily used for jasper.
+     * @return the system class path
+     */
+    public static String getClassPath(ClassLoader loader) throws Exception
+    {
+        StringBuilder classpath=new StringBuilder();
+        while (loader != null && (loader instanceof URLClassLoader))
+        {
+            URL[] urls = ((URLClassLoader)loader).getURLs();
+            if (urls != null)
+            {     
+                for (int i=0;i<urls.length;i++)
+                {
+                    Resource resource = Resource.newResource(urls[i]);
+                    File file=resource.getFile();
+                    if (file!=null && file.exists())
+                    {
+                        if (classpath.length()>0)
+                            classpath.append(File.pathSeparatorChar);
+                        classpath.append(file.getAbsolutePath());
+                    }
+                }
+            }
+            loader = loader.getParent();
+        }
+        return classpath.toString();
+    }
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/util/MemoryUtils.java b/lib/jetty/org/eclipse/jetty/util/MemoryUtils.java
new file mode 100644 (file)
index 0000000..764ad60
--- /dev/null
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * {@link MemoryUtils} provides an abstraction over memory properties and operations.
+ * <p />
+ */
+public class MemoryUtils
+{
+    private static final int cacheLineBytes;
+    static
+    {
+        final int defaultValue = 64;
+        int value = defaultValue;
+        try
+        {
+            value = Integer.parseInt(AccessController.doPrivileged(new PrivilegedAction<String>()
+            {
+                @Override
+                public String run()
+                {
+                    return System.getProperty("org.eclipse.jetty.util.cacheLineBytes", String.valueOf(defaultValue));
+                }
+            }));
+        }
+        catch (Exception ignored)
+        {
+        }
+        cacheLineBytes = value;
+    }
+
+    private MemoryUtils()
+    {
+    }
+
+    public static int getCacheLineBytes()
+    {
+        return cacheLineBytes;
+    }
+
+    public static int getIntegersPerCacheLine()
+    {
+        return getCacheLineBytes() >> 2;
+    }
+
+    public static int getLongsPerCacheLine()
+    {
+        return getCacheLineBytes() >> 3;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/MultiException.java b/lib/jetty/org/eclipse/jetty/util/MultiException.java
new file mode 100644 (file)
index 0000000..2e71f3c
--- /dev/null
@@ -0,0 +1,214 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** 
+ * Wraps multiple exceptions.
+ *
+ * Allows multiple exceptions to be thrown as a single exception.
+ */
+@SuppressWarnings("serial")
+public class MultiException extends Exception
+{
+    private List<Throwable> nested;
+
+    /* ------------------------------------------------------------ */
+    public MultiException()
+    {
+        super("Multiple exceptions");
+    }
+
+    /* ------------------------------------------------------------ */
+    public void add(Throwable e)
+    {
+        if(nested == null)
+        {
+            nested = new ArrayList<>();
+        }
+        
+        if (e instanceof MultiException)
+        {
+            MultiException me = (MultiException)e;
+            nested.addAll(me.nested);
+        }
+        else
+            nested.add(e);
+    }
+
+    /* ------------------------------------------------------------ */
+    public int size()
+    {
+        return (nested ==null)?0:nested.size();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public List<Throwable> getThrowables()
+    {
+        if(nested == null) {
+            return Collections.emptyList();
+        }
+        return nested;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Throwable getThrowable(int i)
+    {
+        return nested.get(i);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Throw a multiexception.
+     * If this multi exception is empty then no action is taken. If it
+     * contains a single exception that is thrown, otherwise the this
+     * multi exception is thrown. 
+     * @exception Exception 
+     */
+    public void ifExceptionThrow()
+        throws Exception
+    {
+        if(nested == null)
+            return;
+        
+        switch (nested.size())
+        {
+          case 0:
+              break;
+          case 1:
+              Throwable th=nested.get(0);
+              if (th instanceof Error)
+                  throw (Error)th;
+              if (th instanceof Exception)
+                  throw (Exception)th;
+          default:
+              throw this;
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Throw a Runtime exception.
+     * If this multi exception is empty then no action is taken. If it
+     * contains a single error or runtime exception that is thrown, otherwise the this
+     * multi exception is thrown, wrapped in a runtime exception. 
+     * @exception Error If this exception contains exactly 1 {@link Error} 
+     * @exception RuntimeException If this exception contains 1 {@link Throwable} but it is not an error,
+     *                             or it contains more than 1 {@link Throwable} of any type.
+     */
+    public void ifExceptionThrowRuntime()
+        throws Error
+    {
+        if(nested == null)
+            return;
+        
+        switch (nested.size())
+        {
+          case 0:
+              break;
+          case 1:
+              Throwable th=nested.get(0);
+              if (th instanceof Error)
+                  throw (Error)th;
+              else if (th instanceof RuntimeException)
+                  throw (RuntimeException)th;
+              else
+                  throw new RuntimeException(th);
+          default:
+              throw new RuntimeException(this);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Throw a multiexception.
+     * If this multi exception is empty then no action is taken. If it
+     * contains a any exceptions then this
+     * multi exception is thrown. 
+     */
+    public void ifExceptionThrowMulti()
+        throws MultiException
+    {
+        if(nested == null)
+            return;
+        
+        if (nested.size()>0)
+            throw this;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append(MultiException.class.getSimpleName());
+        if((nested == null) || (nested.size()<=0)) {
+            str.append("[]");
+        } else {
+            str.append(nested);
+        }
+        return str.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void printStackTrace()
+    {
+        super.printStackTrace();
+        if(nested != null) {
+            for(Throwable t: nested) {
+                t.printStackTrace();
+            }
+        }
+    }
+   
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * @see java.lang.Throwable#printStackTrace(java.io.PrintStream)
+     */
+    @Override
+    public void printStackTrace(PrintStream out)
+    {
+        super.printStackTrace(out);
+        if(nested != null) {
+            for(Throwable t: nested) {
+                t.printStackTrace(out);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * @see java.lang.Throwable#printStackTrace(java.io.PrintWriter)
+     */
+    @Override
+    public void printStackTrace(PrintWriter out)
+    {
+        super.printStackTrace(out);
+        if(nested != null) {
+            for(Throwable t: nested) {
+                t.printStackTrace(out);
+            }
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/MultiMap.java b/lib/jetty/org/eclipse/jetty/util/MultiMap.java
new file mode 100644 (file)
index 0000000..a3c905a
--- /dev/null
@@ -0,0 +1,373 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/** 
+ * A multi valued Map.
+ */
+@SuppressWarnings("serial")
+public class MultiMap<V> extends HashMap<String,List<V>>
+{
+    public MultiMap()
+    {
+        super();
+    }
+
+    public MultiMap(Map<String,List<V>> map)
+    {
+        super(map);
+    }
+
+    public MultiMap(MultiMap<V> map)
+    {
+        super(map);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Get multiple values.
+     * Single valued entries are converted to singleton lists.
+     * @param name The entry key. 
+     * @return Unmodifieable List of values.
+     */
+    public List<V> getValues(String name)
+    {
+        List<V> vals = super.get(name);
+        if((vals == null) || vals.isEmpty()) {
+            return null;
+        }
+        return vals;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get a value from a multiple value.
+     * If the value is not a multivalue, then index 0 retrieves the
+     * value or null.
+     * @param name The entry key.
+     * @param i Index of element to get.
+     * @return Unmodifieable List of values.
+     */
+    public V getValue(String name,int i)
+    {
+        List<V> vals = getValues(name);
+        if(vals == null) {
+            return null;
+        }
+        if (i==0 && vals.isEmpty()) {
+            return null;
+        }
+        return vals.get(i);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Get value as String.
+     * Single valued items are converted to a String with the toString()
+     * Object method. Multi valued entries are converted to a comma separated
+     * List.  No quoting of commas within values is performed.
+     * @param name The entry key. 
+     * @return String value.
+     */
+    public String getString(String name)
+    {
+        List<V> vals =get(name);
+        if ((vals == null) || (vals.isEmpty()))
+        {
+            return null;
+        }
+        
+        if (vals.size() == 1)
+        {
+            // simple form.
+            return vals.get(0).toString();
+        }
+        
+        // delimited form
+        StringBuilder values=new StringBuilder(128);
+        for (V e : vals)
+        {
+            if (e != null)
+            {
+                if (values.length() > 0)
+                    values.append(',');
+                values.append(e.toString());
+            }
+        }   
+        return values.toString();
+    }
+    
+    /** 
+     * Put multi valued entry.
+     * @param name The entry key. 
+     * @param value The simple value
+     * @return The previous value or null.
+     */
+    public List<V> put(String name, V value) 
+    {
+        if(value == null) {
+            return super.put(name, null);
+        }
+        List<V> vals = new ArrayList<>();
+        vals.add(value);
+        return put(name,vals);
+    }
+
+    /**
+     * Shorthand version of putAll
+     * @param input the input map
+     */
+    public void putAllValues(Map<String, V> input)
+    {
+        for(Map.Entry<String,V> entry: input.entrySet())
+        {
+            put(entry.getKey(), entry.getValue());
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Put multi valued entry.
+     * @param name The entry key. 
+     * @param values The List of multiple values.
+     * @return The previous value or null.
+     */
+    public List<V> putValues(String name, List<V> values) 
+    {
+        return super.put(name,values);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Put multi valued entry.
+     * @param name The entry key. 
+     * @param values The array of multiple values.
+     * @return The previous value or null.
+     */
+    @SafeVarargs
+    public final List<V> putValues(String name, V... values) 
+    {
+        List<V> list = new ArrayList<>();
+        list.addAll(Arrays.asList(values));
+        return super.put(name,list);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Add value to multi valued entry.
+     * If the entry is single valued, it is converted to the first
+     * value of a multi valued entry.
+     * @param name The entry key. 
+     * @param value The entry value.
+     */
+    public void add(String name, V value) 
+    {
+        List<V> lo = get(name);
+        if(lo == null) {
+            lo = new ArrayList<>();
+        }
+        lo.add(value);
+        super.put(name,lo);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add values to multi valued entry.
+     * If the entry is single valued, it is converted to the first
+     * value of a multi valued entry.
+     * @param name The entry key. 
+     * @param values The List of multiple values.
+     */
+    public void addValues(String name, List<V> values) 
+    {
+        List<V> lo = get(name);
+        if(lo == null) {
+            lo = new ArrayList<>();
+        }
+        lo.addAll(values);
+        put(name,lo);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add values to multi valued entry.
+     * If the entry is single valued, it is converted to the first
+     * value of a multi valued entry.
+     * @param name The entry key. 
+     * @param values The String array of multiple values.
+     */
+    public void addValues(String name, V[] values) 
+    {
+        List<V> lo = get(name);
+        if(lo == null) {
+            lo = new ArrayList<>();
+        }
+        lo.addAll(Arrays.asList(values));
+        put(name,lo);
+    }
+    
+    /**
+     * Merge values.
+     * 
+     * @param map
+     *            the map to overlay on top of this one, merging together values if needed.
+     * @return true if an existing key was merged with potentially new values, false if either no change was made, or there were only new keys.
+     */
+    public boolean addAllValues(MultiMap<V> map)
+    {
+        boolean merged = false;
+
+        if ((map == null) || (map.isEmpty()))
+        {
+            // done
+            return merged;
+        }
+
+        for (Map.Entry<String, List<V>> entry : map.entrySet())
+        {
+            String name = entry.getKey();
+            List<V> values = entry.getValue();
+
+            if (this.containsKey(name))
+            {
+                merged = true;
+            }
+
+            this.addValues(name,values);
+        }
+
+        return merged;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Remove value.
+     * @param name The entry key. 
+     * @param value The entry value. 
+     * @return true if it was removed.
+     */
+    public boolean removeValue(String name,V value)
+    {
+        List<V> lo = get(name);
+        if((lo == null)||(lo.isEmpty())) {
+            return false;
+        }
+        boolean ret = lo.remove(value);
+        if(lo.isEmpty()) {
+            remove(name);
+        } else {
+            put(name,lo);
+        }
+        return ret;
+    }
+    
+    /**
+     * Test for a specific single value in the map.
+     * <p>
+     * NOTE: This is a SLOW operation, and is actively discouraged.
+     * @param value
+     * @return true if contains simple value
+     */
+    public boolean containsSimpleValue(V value)
+    {
+        for (List<V> vals : values())
+        {
+            if ((vals.size() == 1) && vals.contains(value))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    @Override
+    public String toString()
+    {
+        Iterator<Entry<String, List<V>>> iter = entrySet().iterator();
+        StringBuilder sb = new StringBuilder();
+        sb.append('{');
+        boolean delim = false;
+        while (iter.hasNext())
+        {
+            Entry<String, List<V>> e = iter.next();
+            if (delim)
+            {
+                sb.append(", ");
+            }
+            String key = e.getKey();
+            List<V> vals = e.getValue();
+            sb.append(key);
+            sb.append('=');
+            if (vals.size() == 1)
+            {
+                sb.append(vals.get(0));
+            }
+            else
+            {
+                sb.append(vals);
+            }
+            delim = true;
+        }
+        sb.append('}');
+        return sb.toString();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Map of String arrays
+     */
+    public Map<String,String[]> toStringArrayMap()
+    {
+        HashMap<String,String[]> map = new HashMap<String,String[]>(size()*3/2)
+        {
+            @Override
+            public String toString()
+            {
+                StringBuilder b=new StringBuilder();
+                b.append('{');
+                for (String k:super.keySet())
+                {
+                    if(b.length()>1)
+                        b.append(',');
+                    b.append(k);
+                    b.append('=');
+                    b.append(Arrays.asList(super.get(k)));
+                }
+
+                b.append('}');
+                return b.toString();
+            }
+        };
+        
+        for(Map.Entry<String,List<V>> entry: entrySet())
+        {
+            String[] a = null;
+            if (entry.getValue() != null)
+            {
+                a = new String[entry.getValue().size()];
+                a = entry.getValue().toArray(a);
+            }
+            map.put(entry.getKey(),a);
+        }
+        return map;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/MultiPartInputStreamParser.java b/lib/jetty/org/eclipse/jetty/util/MultiPartInputStreamParser.java
new file mode 100644 (file)
index 0000000..441d648
--- /dev/null
@@ -0,0 +1,835 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.ServletException;
+import javax.servlet.http.Part;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+/**
+ * MultiPartInputStream
+ *
+ * Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings.
+ */
+public class MultiPartInputStreamParser
+{
+    private static final Logger LOG = Log.getLogger(MultiPartInputStreamParser.class);
+    public static final MultipartConfigElement  __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir"));
+    protected InputStream _in;
+    protected MultipartConfigElement _config;
+    protected String _contentType;
+    protected MultiMap _parts;
+    protected File _tmpDir;
+    protected File _contextTmpDir;
+    protected boolean _deleteOnExit;
+
+
+
+    public class MultiPart implements Part
+    {
+        protected String _name;
+        protected String _filename;
+        protected File _file;
+        protected OutputStream _out;
+        protected ByteArrayOutputStream2 _bout;
+        protected String _contentType;
+        protected MultiMap _headers;
+        protected long _size = 0;
+        protected boolean _temporary = true;
+
+        public MultiPart (String name, String filename)
+        throws IOException
+        {
+            _name = name;
+            _filename = filename;
+        }
+
+        protected void setContentType (String contentType)
+        {
+            _contentType = contentType;
+        }
+
+
+        protected void open()
+        throws IOException
+        {
+            //We will either be writing to a file, if it has a filename on the content-disposition
+            //and otherwise a byte-array-input-stream, OR if we exceed the getFileSizeThreshold, we
+            //will need to change to write to a file.
+            if (_filename != null && _filename.trim().length() > 0)
+            {
+                createFile();
+            }
+            else
+            {
+                //Write to a buffer in memory until we discover we've exceed the
+                //MultipartConfig fileSizeThreshold
+                _out = _bout= new ByteArrayOutputStream2();
+            }
+        }
+
+        protected void close()
+        throws IOException
+        {
+            _out.close();
+        }
+
+
+        protected void write (int b)
+        throws IOException
+        {
+            if (MultiPartInputStreamParser.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartInputStreamParser.this._config.getMaxFileSize())
+                throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize");
+
+            if (MultiPartInputStreamParser.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartInputStreamParser.this._config.getFileSizeThreshold() && _file==null)
+                createFile();
+            _out.write(b);
+            _size ++;
+        }
+
+        protected void write (byte[] bytes, int offset, int length)
+        throws IOException
+        {
+            if (MultiPartInputStreamParser.this._config.getMaxFileSize() > 0 && _size + length > MultiPartInputStreamParser.this._config.getMaxFileSize())
+                throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize");
+
+            if (MultiPartInputStreamParser.this._config.getFileSizeThreshold() > 0 && _size + length > MultiPartInputStreamParser.this._config.getFileSizeThreshold() && _file==null)
+                createFile();
+
+            _out.write(bytes, offset, length);
+            _size += length;
+        }
+
+        protected void createFile ()
+        throws IOException
+        {
+            _file = File.createTempFile("MultiPart", "", MultiPartInputStreamParser.this._tmpDir);
+            if (_deleteOnExit)
+                _file.deleteOnExit();
+            FileOutputStream fos = new FileOutputStream(_file);
+            BufferedOutputStream bos = new BufferedOutputStream(fos);
+
+            if (_size > 0 && _out != null)
+            {
+                //already written some bytes, so need to copy them into the file
+                _out.flush();
+                _bout.writeTo(bos);
+                _out.close();
+                _bout = null;
+            }
+            _out = bos;
+        }
+
+
+
+        protected void setHeaders(MultiMap headers)
+        {
+            _headers = headers;
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getContentType()
+         */
+        public String getContentType()
+        {
+            return _contentType;
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getHeader(java.lang.String)
+         */
+        public String getHeader(String name)
+        {
+            if (name == null)
+                return null;
+            return (String)_headers.getValue(name.toLowerCase(Locale.ENGLISH), 0);
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getHeaderNames()
+         */
+        public Collection<String> getHeaderNames()
+        {
+            return _headers.keySet();
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getHeaders(java.lang.String)
+         */
+        public Collection<String> getHeaders(String name)
+        {
+           return _headers.getValues(name);
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getInputStream()
+         */
+        public InputStream getInputStream() throws IOException
+        {
+           if (_file != null)
+           {
+               //written to a file, whether temporary or not
+               return new BufferedInputStream (new FileInputStream(_file));
+           }
+           else
+           {
+               //part content is in memory
+               return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size());
+           }
+        }
+
+        
+        /** 
+         * @see javax.servlet.http.Part#getSubmittedFileName()
+         */
+        @Override
+        public String getSubmittedFileName()
+        {
+            return getContentDispositionFilename();
+        }
+
+        public byte[] getBytes()
+        {
+            if (_bout!=null)
+                return _bout.toByteArray();
+            return null;
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getName()
+         */
+        public String getName()
+        {
+           return _name;
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getSize()
+         */
+        public long getSize()
+        {
+            return _size;         
+        }
+
+        /**
+         * @see javax.servlet.http.Part#write(java.lang.String)
+         */
+        public void write(String fileName) throws IOException
+        {
+            if (_file == null)
+            {
+                _temporary = false;
+                
+                //part data is only in the ByteArrayOutputStream and never been written to disk
+                _file = new File (_tmpDir, fileName);
+
+                BufferedOutputStream bos = null;
+                try
+                {
+                    bos = new BufferedOutputStream(new FileOutputStream(_file));
+                    _bout.writeTo(bos);
+                    bos.flush();
+                }
+                finally
+                {
+                    if (bos != null)
+                        bos.close();
+                    _bout = null;
+                }
+            }
+            else
+            {
+                //the part data is already written to a temporary file, just rename it
+                _temporary = false;
+                
+                File f = new File(_tmpDir, fileName);
+                if (_file.renameTo(f))
+                    _file = f;
+            }
+        }
+
+        /**
+         * Remove the file, whether or not Part.write() was called on it
+         * (ie no longer temporary)
+         * @see javax.servlet.http.Part#delete()
+         */
+        public void delete() throws IOException
+        {
+            if (_file != null && _file.exists())
+                _file.delete();     
+        }
+        
+        /**
+         * Only remove tmp files.
+         * 
+         * @throws IOException
+         */
+        public void cleanUp() throws IOException
+        {
+            if (_temporary && _file != null && _file.exists())
+                _file.delete();
+        }
+
+
+        /**
+         * Get the file, if any, the data has been written to.
+         */
+        public File getFile ()
+        {
+            return _file;
+        }
+
+
+        /**
+         * Get the filename from the content-disposition.
+         * @return null or the filename
+         */
+        public String getContentDispositionFilename ()
+        {
+            return _filename;
+        }
+    }
+
+
+
+
+    /**
+     * @param in Request input stream
+     * @param contentType Content-Type header
+     * @param config MultipartConfigElement
+     * @param contextTmpDir javax.servlet.context.tempdir
+     */
+    public MultiPartInputStreamParser (InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir)
+    {
+        _in = new ReadLineInputStream(in);
+       _contentType = contentType;
+       _config = config;
+       _contextTmpDir = contextTmpDir;
+       if (_contextTmpDir == null)
+           _contextTmpDir = new File (System.getProperty("java.io.tmpdir"));
+       
+       if (_config == null)
+           _config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath());
+    }
+
+    /**
+     * Get the already parsed parts.
+     */
+    public Collection<Part> getParsedParts()
+    {
+        if (_parts == null)
+            return Collections.emptyList();
+
+        Collection<Object> values = _parts.values();
+        List<Part> parts = new ArrayList<Part>();
+        for (Object o: values)
+        {
+            List<Part> asList = LazyList.getList(o, false);
+            parts.addAll(asList);
+        }
+        return parts;
+    }
+
+    /**
+     * Delete any tmp storage for parts, and clear out the parts list.
+     * 
+     * @throws MultiException
+     */
+    public void deleteParts ()
+    throws MultiException
+    {
+        Collection<Part> parts = getParsedParts();
+        MultiException err = new MultiException();
+        for (Part p:parts)
+        {
+            try
+            {
+                ((MultiPartInputStreamParser.MultiPart)p).cleanUp();
+            } 
+            catch(Exception e)
+            {     
+                err.add(e); 
+            }
+        }
+        _parts.clear();
+        
+        err.ifExceptionThrowMulti();
+    }
+
+   
+    /**
+     * Parse, if necessary, the multipart data and return the list of Parts.
+     * 
+     * @throws IOException
+     * @throws ServletException
+     */
+    public Collection<Part> getParts()
+    throws IOException, ServletException
+    {
+        parse();
+        Collection<Object> values = _parts.values();
+        List<Part> parts = new ArrayList<Part>();
+        for (Object o: values)
+        {
+            List<Part> asList = LazyList.getList(o, false);
+            parts.addAll(asList);
+        }
+        return parts;
+    }
+
+
+    /**
+     * Get the named Part.
+     * 
+     * @param name
+     * @throws IOException
+     * @throws ServletException
+     */
+    public Part getPart(String name)
+    throws IOException, ServletException
+    {
+        parse();
+        return (Part)_parts.getValue(name, 0);
+    }
+
+
+    /**
+     * Parse, if necessary, the multipart stream.
+     * 
+     * @throws IOException
+     * @throws ServletException
+     */
+    protected void parse ()
+    throws IOException, ServletException
+    {
+        //have we already parsed the input?
+        if (_parts != null)
+            return;
+
+        //initialize
+        long total = 0; //keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize
+        _parts = new MultiMap();
+
+        //if its not a multipart request, don't parse it
+        if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
+            return;
+
+        //sort out the location to which to write the files
+
+        if (_config.getLocation() == null)
+            _tmpDir = _contextTmpDir;
+        else if ("".equals(_config.getLocation()))
+            _tmpDir = _contextTmpDir;
+        else
+        {
+            File f = new File (_config.getLocation());
+            if (f.isAbsolute())
+                _tmpDir = f;
+            else
+                _tmpDir = new File (_contextTmpDir, _config.getLocation());
+        }
+
+        if (!_tmpDir.exists())
+            _tmpDir.mkdirs();
+
+        String contentTypeBoundary = "";
+        int bstart = _contentType.indexOf("boundary=");
+        if (bstart >= 0)
+        {
+            int bend = _contentType.indexOf(";", bstart);
+            bend = (bend < 0? _contentType.length(): bend);
+            contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend)).trim());
+        }
+        
+        String boundary="--"+contentTypeBoundary;
+        byte[] byteBoundary=(boundary+"--").getBytes(StandardCharsets.ISO_8859_1);
+
+        // Get first boundary
+        String line = null;
+        try
+        {
+            line=((ReadLineInputStream)_in).readLine();  
+        }
+        catch (IOException e)
+        {
+            LOG.warn("Badly formatted multipart request");
+            throw e;
+        }
+        
+        if (line == null)
+            throw new IOException("Missing content for multipart request");
+        
+        boolean badFormatLogged = false;
+        line=line.trim();
+        while (line != null && !line.equals(boundary))
+        {
+            if (!badFormatLogged)
+            {
+                LOG.warn("Badly formatted multipart request");
+                badFormatLogged = true;
+            }
+            line=((ReadLineInputStream)_in).readLine();
+            line=(line==null?line:line.trim());
+        }
+
+        if (line == null)
+            throw new IOException("Missing initial multi part boundary");
+
+        // Read each part
+        boolean lastPart=false;
+
+        outer:while(!lastPart)
+        {
+            String contentDisposition=null;
+            String contentType=null;
+            String contentTransferEncoding=null;
+            
+            MultiMap headers = new MultiMap();
+            while(true)
+            {
+                line=((ReadLineInputStream)_in).readLine();
+                
+                //No more input
+                if(line==null)
+                    break outer;
+                
+                //end of headers:
+                if("".equals(line))
+                    break;
+           
+                total += line.length();
+                if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
+                    throw new IllegalStateException ("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
+
+                //get content-disposition and content-type
+                int c=line.indexOf(':',0);
+                if(c>0)
+                {
+                    String key=line.substring(0,c).trim().toLowerCase(Locale.ENGLISH);
+                    String value=line.substring(c+1,line.length()).trim();
+                    headers.put(key, value);
+                    if (key.equalsIgnoreCase("content-disposition"))
+                        contentDisposition=value;
+                    if (key.equalsIgnoreCase("content-type"))
+                        contentType = value;
+                    if(key.equals("content-transfer-encoding"))
+                        contentTransferEncoding=value;
+                }
+            }
+
+            // Extract content-disposition
+            boolean form_data=false;
+            if(contentDisposition==null)
+            {
+                throw new IOException("Missing content-disposition");
+            }
+
+            QuotedStringTokenizer tok=new QuotedStringTokenizer(contentDisposition,";", false, true);
+            String name=null;
+            String filename=null;
+            while(tok.hasMoreTokens())
+            {
+                String t=tok.nextToken().trim();
+                String tl=t.toLowerCase(Locale.ENGLISH);
+                if(t.startsWith("form-data"))
+                    form_data=true;
+                else if(tl.startsWith("name="))
+                    name=value(t);
+                else if(tl.startsWith("filename="))
+                    filename=filenameValue(t);
+            }
+
+            // Check disposition
+            if(!form_data)
+            {
+                continue;
+            }
+            //It is valid for reset and submit buttons to have an empty name.
+            //If no name is supplied, the browser skips sending the info for that field.
+            //However, if you supply the empty string as the name, the browser sends the
+            //field, with name as the empty string. So, only continue this loop if we
+            //have not yet seen a name field.
+            if(name==null)
+            {
+                continue;
+            }
+
+            //Have a new Part
+            MultiPart part = new MultiPart(name, filename);
+            part.setHeaders(headers);
+            part.setContentType(contentType);
+            _parts.add(name, part);
+            part.open();
+            
+            InputStream partInput = null;
+            if ("base64".equalsIgnoreCase(contentTransferEncoding))
+            {
+                partInput = new Base64InputStream((ReadLineInputStream)_in);
+            }
+            else if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding))
+            {
+                partInput = new FilterInputStream(_in)
+                {
+                    @Override
+                    public int read() throws IOException
+                    {
+                        int c = in.read();
+                        if (c >= 0 && c == '=')
+                        {
+                            int hi = in.read();
+                            int lo = in.read();
+                            if (hi < 0 || lo < 0)
+                            {
+                                throw new IOException("Unexpected end to quoted-printable byte");
+                            }
+                            char[] chars = new char[] { (char)hi, (char)lo };
+                            c = Integer.parseInt(new String(chars),16);
+                        }
+                        return c;
+                    }
+                };
+            }
+            else
+                partInput = _in;
+
+            
+            try
+            {
+                int state=-2;
+                int c;
+                boolean cr=false;
+                boolean lf=false;
+
+                // loop for all lines
+                while(true)
+                {
+                    int b=0;
+                    while((c=(state!=-2)?state:partInput.read())!=-1)
+                    {
+                        total ++;
+                        if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
+                            throw new IllegalStateException("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
+
+                        state=-2;
+                        
+                        // look for CR and/or LF
+                        if(c==13||c==10)
+                        {
+                            if(c==13)
+                            {
+                                partInput.mark(1);
+                                int tmp=partInput.read();
+                                if (tmp!=10)
+                                    partInput.reset();
+                                else
+                                    state=tmp;
+                            }
+                            break;
+                        }
+                        
+                        // Look for boundary
+                        if(b>=0&&b<byteBoundary.length&&c==byteBoundary[b])
+                        {
+                            b++;
+                        }
+                        else
+                        {
+                            // Got a character not part of the boundary, so we don't have the boundary marker.
+                            // Write out as many chars as we matched, then the char we're looking at.
+                            if(cr)
+                                part.write(13);
+
+                            if(lf)
+                                part.write(10);
+
+                            cr=lf=false;
+                            if(b>0)
+                                part.write(byteBoundary,0,b);
+
+                            b=-1;
+                            part.write(c);
+                        }
+                    }
+                    
+                    // Check for incomplete boundary match, writing out the chars we matched along the way
+                    if((b>0&&b<byteBoundary.length-2)||(b==byteBoundary.length-1))
+                    {
+                        if(cr)
+                            part.write(13);
+
+                        if(lf)
+                            part.write(10);
+
+                        cr=lf=false;
+                        part.write(byteBoundary,0,b);
+                        b=-1;
+                    }
+                    
+                    // Boundary match. If we've run out of input or we matched the entire final boundary marker, then this is the last part.
+                    if(b>0||c==-1)
+                    {
+                       
+                        if(b==byteBoundary.length)
+                            lastPart=true;
+                        if(state==10)
+                            state=-2;
+                        break;
+                    }
+                    
+                    // handle CR LF
+                    if(cr)
+                        part.write(13);
+
+                    if(lf)
+                        part.write(10);
+
+                    cr=(c==13);
+                    lf=(c==10||state==10);
+                    if(state==10)
+                        state=-2;
+                }
+            }
+            finally
+            {
+
+                part.close();
+            }
+        }
+        if (!lastPart)
+            throw new IOException("Incomplete parts");
+    }
+    
+    public void setDeleteOnExit(boolean deleteOnExit)
+    {
+        _deleteOnExit = deleteOnExit;
+    }
+
+
+    public boolean isDeleteOnExit()
+    {
+        return _deleteOnExit;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    private String value(String nameEqualsValue)
+    {
+        int idx = nameEqualsValue.indexOf('=');
+        String value = nameEqualsValue.substring(idx+1).trim();
+        return QuotedStringTokenizer.unquoteOnly(value);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private String filenameValue(String nameEqualsValue)
+    {
+        int idx = nameEqualsValue.indexOf('=');
+        String value = nameEqualsValue.substring(idx+1).trim();
+
+        if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*"))
+        {
+            //incorrectly escaped IE filenames that have the whole path
+            //we just strip any leading & trailing quotes and leave it as is
+            char first=value.charAt(0);
+            if (first=='"' || first=='\'')
+                value=value.substring(1);
+            char last=value.charAt(value.length()-1);
+            if (last=='"' || last=='\'')
+                value = value.substring(0,value.length()-1);
+
+            return value;
+        }
+        else
+            //unquote the string, but allow any backslashes that don't
+            //form a valid escape sequence to remain as many browsers
+            //even on *nix systems will not escape a filename containing
+            //backslashes
+            return QuotedStringTokenizer.unquoteOnly(value, true);
+    }
+
+    
+
+    private static class Base64InputStream extends InputStream
+    {
+        ReadLineInputStream _in;
+        String _line;
+        byte[] _buffer;
+        int _pos;
+
+    
+        public Base64InputStream(ReadLineInputStream rlis)
+        {
+            _in = rlis;
+        }
+
+        @Override
+        public int read() throws IOException
+        {
+            if (_buffer==null || _pos>= _buffer.length)
+            {
+                //Any CR and LF will be consumed by the readLine() call.
+                //We need to put them back into the bytes returned from this
+                //method because the parsing of the multipart content uses them
+                //as markers to determine when we've reached the end of a part.
+                _line = _in.readLine(); 
+                if (_line==null)
+                    return -1;  //nothing left
+                if (_line.startsWith("--"))
+                    _buffer=(_line+"\r\n").getBytes(); //boundary marking end of part
+                else if (_line.length()==0)
+                    _buffer="\r\n".getBytes(); //blank line
+                else
+                {
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream((4*_line.length()/3)+2);
+                    B64Code.decode(_line, baos);
+                    baos.write(13);
+                    baos.write(10);
+                    _buffer = baos.toByteArray();
+                }
+
+                _pos=0;
+            }
+            
+            return _buffer[_pos++];
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/MultiPartOutputStream.java b/lib/jetty/org/eclipse/jetty/util/MultiPartOutputStream.java
new file mode 100644 (file)
index 0000000..f554e76
--- /dev/null
@@ -0,0 +1,152 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+
+/* ================================================================ */
+/** Handle a multipart MIME response.
+ *
+ * 
+ * 
+*/
+public class MultiPartOutputStream extends FilterOutputStream
+{
+    /* ------------------------------------------------------------ */
+    private static final byte[] __CRLF={'\r','\n'};
+    private static final byte[] __DASHDASH={'-','-'};
+    
+    public static final String MULTIPART_MIXED="multipart/mixed";
+    public static final String MULTIPART_X_MIXED_REPLACE="multipart/x-mixed-replace";
+    
+    /* ------------------------------------------------------------ */
+    private final String boundary;
+    private final byte[] boundaryBytes;
+
+    /* ------------------------------------------------------------ */
+    private boolean inPart=false;    
+    
+    /* ------------------------------------------------------------ */
+    public MultiPartOutputStream(OutputStream out)
+    throws IOException
+    {
+        super(out);
+
+        boundary = "jetty"+System.identityHashCode(this)+
+        Long.toString(System.currentTimeMillis(),36);
+        boundaryBytes=boundary.getBytes(StandardCharsets.ISO_8859_1);
+    }
+
+    public MultiPartOutputStream(OutputStream out, String boundary)
+         throws IOException
+    {
+        super(out);
+
+        this.boundary = boundary;
+        boundaryBytes=boundary.getBytes(StandardCharsets.ISO_8859_1);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** End the current part.
+     * @exception IOException IOException
+     */
+    @Override
+    public void close()
+         throws IOException
+    {
+        try
+        {
+            if (inPart)
+                out.write(__CRLF);
+            out.write(__DASHDASH);
+            out.write(boundaryBytes);
+            out.write(__DASHDASH);
+            out.write(__CRLF);
+            inPart=false;
+        }
+        finally
+        {
+            super.close();
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String getBoundary()
+    {
+        return boundary;
+    }
+
+    public OutputStream getOut() {return out;}
+    
+    /* ------------------------------------------------------------ */
+    /** Start creation of the next Content.
+     */
+    public void startPart(String contentType)
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        inPart=true;
+        out.write(__DASHDASH);
+        out.write(boundaryBytes);
+        out.write(__CRLF);
+        if (contentType != null)
+            out.write(("Content-Type: "+contentType).getBytes(StandardCharsets.ISO_8859_1));
+        out.write(__CRLF);
+        out.write(__CRLF);
+    }
+        
+    /* ------------------------------------------------------------ */
+    /** Start creation of the next Content.
+     */
+    public void startPart(String contentType, String[] headers)
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        inPart=true;
+        out.write(__DASHDASH);
+        out.write(boundaryBytes);
+        out.write(__CRLF);
+        if (contentType != null)
+            out.write(("Content-Type: "+contentType).getBytes(StandardCharsets.ISO_8859_1));
+        out.write(__CRLF);
+        for (int i=0;headers!=null && i<headers.length;i++)
+        {
+            out.write(headers[i].getBytes(StandardCharsets.ISO_8859_1));
+            out.write(__CRLF);
+        }
+        out.write(__CRLF);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException
+    {
+        out.write(b,off,len);
+    }
+}
+
+
+
+
diff --git a/lib/jetty/org/eclipse/jetty/util/MultiPartWriter.java b/lib/jetty/org/eclipse/jetty/util/MultiPartWriter.java
new file mode 100644 (file)
index 0000000..0021712
--- /dev/null
@@ -0,0 +1,144 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+
+/* ================================================================ */
+/** Handle a multipart MIME response.
+ *
+ * 
+ * 
+*/
+public class MultiPartWriter extends FilterWriter
+{
+    /* ------------------------------------------------------------ */
+    private final static String __CRLF="\015\012";
+    private final static String __DASHDASH="--";
+    
+    public static final String MULTIPART_MIXED=MultiPartOutputStream.MULTIPART_MIXED;
+    public static final String MULTIPART_X_MIXED_REPLACE=MultiPartOutputStream.MULTIPART_X_MIXED_REPLACE;
+    
+    /* ------------------------------------------------------------ */
+    private String boundary;
+
+    /* ------------------------------------------------------------ */
+    private boolean inPart=false;    
+    
+    /* ------------------------------------------------------------ */
+    public MultiPartWriter(Writer out)
+         throws IOException
+    {
+        super(out);
+        boundary = "jetty"+System.identityHashCode(this)+
+        Long.toString(System.currentTimeMillis(),36);
+        
+        inPart=false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** End the current part.
+     * @exception IOException IOException
+     */
+    @Override
+    public void close()
+         throws IOException
+    {
+        try
+        {
+            if (inPart)
+                out.write(__CRLF);
+            out.write(__DASHDASH);
+            out.write(boundary);
+            out.write(__DASHDASH);
+            out.write(__CRLF);
+            inPart=false;
+        }
+        finally
+        {
+            super.close();
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String getBoundary()
+    {
+        return boundary;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Start creation of the next Content.
+     */
+    public void startPart(String contentType)
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        out.write(__DASHDASH);
+        out.write(boundary);
+        out.write(__CRLF);
+        out.write("Content-Type: ");
+        out.write(contentType);
+        out.write(__CRLF);
+        out.write(__CRLF);
+        inPart=true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** end creation of the next Content.
+     */
+    public void endPart()
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        inPart=false;
+    }
+        
+    /* ------------------------------------------------------------ */
+    /** Start creation of the next Content.
+     */
+    public void startPart(String contentType, String[] headers)
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        out.write(__DASHDASH);
+        out.write(boundary);
+        out.write(__CRLF);
+        out.write("Content-Type: ");
+        out.write(contentType);
+        out.write(__CRLF);
+        for (int i=0;headers!=null && i<headers.length;i++)
+        {
+            out.write(headers[i]);
+            out.write(__CRLF);
+        }
+        out.write(__CRLF);
+        inPart=true;
+    }
+    
+}
+
+
+
+
diff --git a/lib/jetty/org/eclipse/jetty/util/PatternMatcher.java b/lib/jetty/org/eclipse/jetty/util/PatternMatcher.java
new file mode 100644 (file)
index 0000000..0afda65
--- /dev/null
@@ -0,0 +1,104 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public abstract class PatternMatcher
+{
+    public abstract void matched (URI uri) throws Exception;
+    
+    
+    /**
+     * Find jar names from the provided list matching a pattern.
+     * 
+     * If the pattern is null and isNullInclusive is true, then
+     * all jar names will match.
+     * 
+     * A pattern is a set of acceptable jar names. Each acceptable
+     * jar name is a regex. Each regex can be separated by either a
+     * "," or a "|". If you use a "|" this or's together the jar
+     * name patterns. This means that ordering of the matches is
+     * unimportant to you. If instead, you want to match particular
+     * jar names, and you want to match them in order, you should
+     * separate the regexs with "," instead. 
+     * 
+     * Eg "aaa-.*\\.jar|bbb-.*\\.jar"
+     * Will iterate over the jar names and match
+     * in any order.
+     * 
+     * Eg "aaa-*\\.jar,bbb-.*\\.jar"
+     * Will iterate over the jar names, matching
+     * all those starting with "aaa-" first, then "bbb-".
+     *
+     * @param pattern the pattern
+     * @param uris the uris to test the pattern against
+     * @param isNullInclusive if true, an empty pattern means all names match, if false, none match
+     * @throws Exception
+     */
+    public void match (Pattern pattern, URI[] uris, boolean isNullInclusive)
+    throws Exception
+    {
+        if (uris!=null)
+        {
+            String[] patterns = (pattern==null?null:pattern.pattern().split(","));
+
+            List<Pattern> subPatterns = new ArrayList<Pattern>();
+            for (int i=0; patterns!=null && i<patterns.length;i++)
+            {
+                subPatterns.add(Pattern.compile(patterns[i]));
+            }
+            if (subPatterns.isEmpty())
+                subPatterns.add(pattern);
+
+            if (subPatterns.isEmpty())
+            {
+                matchPatterns(null, uris, isNullInclusive);
+            }
+            else
+            {
+                //for each subpattern, iterate over all the urls, processing those that match
+                for (Pattern p : subPatterns)
+                {
+                    matchPatterns(p, uris, isNullInclusive);
+                }
+            }
+        }
+    }
+
+
+    public void matchPatterns (Pattern pattern, URI[] uris, boolean isNullInclusive)
+    throws Exception
+    {
+        for (int i=0; i<uris.length;i++)
+        {
+            URI uri = uris[i];
+            String s = uri.toString();
+            if ((pattern == null && isNullInclusive)
+                    ||
+                    (pattern!=null && pattern.matcher(s).matches()))
+            {
+                matched(uris[i]);
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Promise.java b/lib/jetty/org/eclipse/jetty/util/Promise.java
new file mode 100644 (file)
index 0000000..6727fc4
--- /dev/null
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import org.eclipse.jetty.util.log.Log;
+
+/**
+ * <p>A callback abstraction that handles completed/failed events of asynchronous operations.</p>
+ *
+ * @param <C> the type of the context object
+ */
+public interface Promise<C>
+{
+    /**
+     * <p>Callback invoked when the operation completes.</p>
+     *
+     * @param result the context
+     * @see #failed(Throwable)
+     */
+    public abstract void succeeded(C result);
+
+    /**
+     * <p>Callback invoked when the operation fails.</p>
+     *
+     * @param x the reason for the operation failure
+     */
+    public void failed(Throwable x);
+    
+
+    /**
+     * <p>Empty implementation of {@link Promise}</p>
+     *
+     * @param <C> the type of the context object
+     */
+    public static class Adapter<C> implements Promise<C>
+    {
+        @Override
+        public void succeeded(C result)
+        {
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            Log.getLogger(this.getClass()).warn(x);
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/QuotedStringTokenizer.java b/lib/jetty/org/eclipse/jetty/util/QuotedStringTokenizer.java
new file mode 100644 (file)
index 0000000..3a4d518
--- /dev/null
@@ -0,0 +1,609 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+/* ------------------------------------------------------------ */
+/** StringTokenizer with Quoting support.
+ *
+ * This class is a copy of the java.util.StringTokenizer API and
+ * the behaviour is the same, except that single and double quoted
+ * string values are recognised.
+ * Delimiters within quotes are not considered delimiters.
+ * Quotes can be escaped with '\'.
+ *
+ * @see java.util.StringTokenizer
+ *
+ */
+public class QuotedStringTokenizer
+    extends StringTokenizer
+{
+    private final static String __delim="\t\n\r";
+    private String _string;
+    private String _delim = __delim;
+    private boolean _returnQuotes=false;
+    private boolean _returnDelimiters=false;
+    private StringBuffer _token;
+    private boolean _hasToken=false;
+    private int _i=0;
+    private int _lastStart=0;
+    private boolean _double=true;
+    private boolean _single=true;
+
+    /* ------------------------------------------------------------ */
+    public QuotedStringTokenizer(String str,
+                                 String delim,
+                                 boolean returnDelimiters,
+                                 boolean returnQuotes)
+    {
+        super("");
+        _string=str;
+        if (delim!=null)
+            _delim=delim;
+        _returnDelimiters=returnDelimiters;
+        _returnQuotes=returnQuotes;
+
+        if (_delim.indexOf('\'')>=0 ||
+            _delim.indexOf('"')>=0)
+            throw new Error("Can't use quotes as delimiters: "+_delim);
+
+        _token=new StringBuffer(_string.length()>1024?512:_string.length()/2);
+    }
+
+    /* ------------------------------------------------------------ */
+    public QuotedStringTokenizer(String str,
+                                 String delim,
+                                 boolean returnDelimiters)
+    {
+        this(str,delim,returnDelimiters,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    public QuotedStringTokenizer(String str,
+                                 String delim)
+    {
+        this(str,delim,false,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    public QuotedStringTokenizer(String str)
+    {
+        this(str,null,false,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean hasMoreTokens()
+    {
+        // Already found a token
+        if (_hasToken)
+            return true;
+
+        _lastStart=_i;
+
+        int state=0;
+        boolean escape=false;
+        while (_i<_string.length())
+        {
+            char c=_string.charAt(_i++);
+
+            switch (state)
+            {
+              case 0: // Start
+                  if(_delim.indexOf(c)>=0)
+                  {
+                      if (_returnDelimiters)
+                      {
+                          _token.append(c);
+                          return _hasToken=true;
+                      }
+                  }
+                  else if (c=='\'' && _single)
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=2;
+                  }
+                  else if (c=='\"' && _double)
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=3;
+                  }
+                  else
+                  {
+                      _token.append(c);
+                      _hasToken=true;
+                      state=1;
+                  }
+                  break;
+
+              case 1: // Token
+                  _hasToken=true;
+                  if(_delim.indexOf(c)>=0)
+                  {
+                      if (_returnDelimiters)
+                          _i--;
+                      return _hasToken;
+                  }
+                  else if (c=='\'' && _single)
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=2;
+                  }
+                  else if (c=='\"' && _double)
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=3;
+                  }
+                  else
+                  {
+                      _token.append(c);
+                  }
+                  break;
+
+              case 2: // Single Quote
+                  _hasToken=true;
+                  if (escape)
+                  {
+                      escape=false;
+                      _token.append(c);
+                  }
+                  else if (c=='\'')
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=1;
+                  }
+                  else if (c=='\\')
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      escape=true;
+                  }
+                  else
+                  {
+                      _token.append(c);
+                  }
+                  break;
+
+              case 3: // Double Quote
+                  _hasToken=true;
+                  if (escape)
+                  {
+                      escape=false;
+                      _token.append(c);
+                  }
+                  else if (c=='\"')
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=1;
+                  }
+                  else if (c=='\\')
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      escape=true;
+                  }
+                  else
+                  {
+                      _token.append(c);
+                  }
+                  break;
+            }
+        }
+
+        return _hasToken;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String nextToken()
+        throws NoSuchElementException
+    {
+        if (!hasMoreTokens() || _token==null)
+            throw new NoSuchElementException();
+        String t=_token.toString();
+        _token.setLength(0);
+        _hasToken=false;
+        return t;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String nextToken(String delim)
+        throws NoSuchElementException
+    {
+        _delim=delim;
+        _i=_lastStart;
+        _token.setLength(0);
+        _hasToken=false;
+        return nextToken();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean hasMoreElements()
+    {
+        return hasMoreTokens();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object nextElement()
+        throws NoSuchElementException
+    {
+        return nextToken();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Not implemented.
+     */
+    @Override
+    public int countTokens()
+    {
+        return -1;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Quote a string.
+     * The string is quoted only if quoting is required due to
+     * embedded delimiters, quote characters or the
+     * empty string.
+     * @param s The string to quote.
+     * @param delim the delimiter to use to quote the string
+     * @return quoted string
+     */
+    public static String quoteIfNeeded(String s, String delim)
+    {
+        if (s==null)
+            return null;
+        if (s.length()==0)
+            return "\"\"";
+
+
+        for (int i=0;i<s.length();i++)
+        {
+            char c = s.charAt(i);
+            if (c=='\\' || c=='"' || c=='\'' || Character.isWhitespace(c) || delim.indexOf(c)>=0)
+            {
+                StringBuffer b=new StringBuffer(s.length()+8);
+                quote(b,s);
+                return b.toString();
+            }
+        }
+
+        return s;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Quote a string.
+     * The string is quoted only if quoting is required due to
+     * embeded delimiters, quote characters or the
+     * empty string.
+     * @param s The string to quote.
+     * @return quoted string
+     */
+    public static String quote(String s)
+    {
+        if (s==null)
+            return null;
+        if (s.length()==0)
+            return "\"\"";
+
+        StringBuffer b=new StringBuffer(s.length()+8);
+        quote(b,s);
+        return b.toString();
+
+    }
+
+    private static final char[] escapes = new char[32];
+    static
+    {
+        Arrays.fill(escapes, (char)0xFFFF);
+        escapes['\b'] = 'b';
+        escapes['\t'] = 't';
+        escapes['\n'] = 'n';
+        escapes['\f'] = 'f';
+        escapes['\r'] = 'r';
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Quote a string into an Appendable.
+     * Only quotes and backslash are escaped.
+     * @param buffer The Appendable
+     * @param input The String to quote.
+     */
+    public static void quoteOnly(Appendable buffer, String input)
+    {
+        if(input==null)
+            return;
+
+        try
+        {
+            buffer.append('"');
+            for (int i = 0; i < input.length(); ++i)
+            {
+                char c = input.charAt(i);
+                if (c == '"' || c == '\\')
+                    buffer.append('\\');
+                buffer.append(c);
+            }
+            buffer.append('"');
+        }
+        catch (IOException x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Quote a string into an Appendable.
+     * The characters ", \, \n, \r, \t, \f and \b are escaped
+     * @param buffer The Appendable
+     * @param input The String to quote.
+     */
+    public static void quote(Appendable buffer, String input)
+    {
+        if(input==null)
+            return;
+
+        try
+        {
+            buffer.append('"');
+            for (int i = 0; i < input.length(); ++i)
+            {
+                char c = input.charAt(i);
+                if (c >= 32)
+                {
+                    if (c == '"' || c == '\\')
+                        buffer.append('\\');
+                    buffer.append(c);
+                }
+                else
+                {
+                    char escape = escapes[c];
+                    if (escape == 0xFFFF)
+                    {
+                        // Unicode escape
+                        buffer.append('\\').append('u').append('0').append('0');
+                        if (c < 0x10)
+                            buffer.append('0');
+                        buffer.append(Integer.toString(c, 16));
+                    }
+                    else
+                    {
+                        buffer.append('\\').append(escape);
+                    }
+                }
+            }
+            buffer.append('"');
+        }
+        catch (IOException x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static String unquoteOnly(String s)
+    {
+        return unquoteOnly(s, false);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Unquote a string, NOT converting unicode sequences
+     * @param s The string to unquote.
+     * @param lenient if true, will leave in backslashes that aren't valid escapes
+     * @return quoted string
+     */
+    public static String unquoteOnly(String s, boolean lenient)
+    {
+        if (s==null)
+            return null;
+        if (s.length()<2)
+            return s;
+
+        char first=s.charAt(0);
+        char last=s.charAt(s.length()-1);
+        if (first!=last || (first!='"' && first!='\''))
+            return s;
+
+        StringBuilder b = new StringBuilder(s.length() - 2);
+        boolean escape=false;
+        for (int i=1;i<s.length()-1;i++)
+        {
+            char c = s.charAt(i);
+
+            if (escape)
+            {
+                escape=false;
+                if (lenient && !isValidEscaping(c))
+                {
+                    b.append('\\');
+                }
+                b.append(c);
+            }
+            else if (c=='\\')
+            {
+                escape=true;
+            }
+            else
+            {
+                b.append(c);
+            }
+        }
+
+        return b.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String unquote(String s)
+    {
+        return unquote(s,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Unquote a string.
+     * @param s The string to unquote.
+     * @return quoted string
+     */
+    public static String unquote(String s, boolean lenient)
+    {
+        if (s==null)
+            return null;
+        if (s.length()<2)
+            return s;
+
+        char first=s.charAt(0);
+        char last=s.charAt(s.length()-1);
+        if (first!=last || (first!='"' && first!='\''))
+            return s;
+
+        StringBuilder b = new StringBuilder(s.length() - 2);
+        boolean escape=false;
+        for (int i=1;i<s.length()-1;i++)
+        {
+            char c = s.charAt(i);
+
+            if (escape)
+            {
+                escape=false;
+                switch (c)
+                {
+                    case 'n':
+                        b.append('\n');
+                        break;
+                    case 'r':
+                        b.append('\r');
+                        break;
+                    case 't':
+                        b.append('\t');
+                        break;
+                    case 'f':
+                        b.append('\f');
+                        break;
+                    case 'b':
+                        b.append('\b');
+                        break;
+                    case '\\':
+                        b.append('\\');
+                        break;
+                    case '/':
+                        b.append('/');
+                        break;
+                    case '"':
+                        b.append('"');
+                        break;
+                    case 'u':
+                        b.append((char)(
+                                (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
+                                (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
+                                (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
+                                (TypeUtil.convertHexDigit((byte)s.charAt(i++)))
+                                )
+                        );
+                        break;
+                    default:
+                        if (lenient && !isValidEscaping(c))
+                        {
+                            b.append('\\');
+                        }
+                        b.append(c);
+                }
+            }
+            else if (c=='\\')
+            {
+                escape=true;
+            }
+            else
+            {
+                b.append(c);
+            }
+        }
+
+        return b.toString();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Check that char c (which is preceded by a backslash) is a valid
+     * escape sequence.
+     * @param c
+     * @return
+     */
+    private static boolean isValidEscaping(char c)
+    {
+        return ((c == 'n') || (c == 'r') || (c == 't') ||
+                 (c == 'f') || (c == 'b') || (c == '\\') ||
+                 (c == '/') || (c == '"') || (c == 'u'));
+    }
+
+    /* ------------------------------------------------------------ */
+    public static boolean isQuoted(String s)
+    {
+        return s!=null && s.length()>0 && s.charAt(0)=='"' && s.charAt(s.length()-1)=='"';
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return handle double quotes if true
+     */
+    public boolean getDouble()
+    {
+        return _double;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param d handle double quotes if true
+     */
+    public void setDouble(boolean d)
+    {
+        _double=d;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return handle single quotes if true
+     */
+    public boolean getSingle()
+    {
+        return _single;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param single handle single quotes if true
+     */
+    public void setSingle(boolean single)
+    {
+        _single=single;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ReadLineInputStream.java b/lib/jetty/org/eclipse/jetty/util/ReadLineInputStream.java
new file mode 100644 (file)
index 0000000..067b2c1
--- /dev/null
@@ -0,0 +1,137 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * ReadLineInputStream
+ *
+ * Read from an input stream, accepting CR/LF, LF or just CR.
+ */
+public class ReadLineInputStream extends BufferedInputStream
+{
+    boolean _seenCRLF;
+    boolean _skipLF;
+    
+    public ReadLineInputStream(InputStream in)
+    {
+        super(in);
+    }
+
+    public ReadLineInputStream(InputStream in, int size)
+    {
+        super(in,size);
+    }
+    
+    public String readLine() throws IOException
+    {
+        mark(buf.length);
+                
+        while (true)
+        {
+            int b=super.read();
+            
+            if (markpos < 0)
+                throw new IOException("Buffer size exceeded: no line terminator");
+            
+            if (b==-1)
+            {
+                int m=markpos;
+                markpos=-1;
+                if (pos>m)
+                    return new String(buf,m,pos-m, StandardCharsets.UTF_8);
+
+                return null;
+            }
+            
+            if (b=='\r')
+            {
+                int p=pos;
+                
+                // if we have seen CRLF before, hungrily consume LF
+                if (_seenCRLF && pos<count)
+                {
+                    if (buf[pos]=='\n')
+                        pos+=1;
+                }
+                else
+                    _skipLF=true;
+                int m=markpos;
+                markpos=-1;
+                return new String(buf,m,p-m-1,StandardCharsets.UTF_8);
+            }
+            
+            if (b=='\n')
+            {
+                if (_skipLF)
+                {
+                    _skipLF=false;
+                    _seenCRLF=true;
+                    markpos++;
+                    continue;
+                }
+                int m=markpos;
+                markpos=-1;
+                return new String(buf,m,pos-m-1,StandardCharsets.UTF_8);
+            }
+        }
+    }
+
+    @Override
+    public synchronized int read() throws IOException
+    {
+        int b = super.read();
+        if (_skipLF)
+        {
+            _skipLF=false;
+            if (_seenCRLF && b=='\n')
+                b=super.read();
+        }
+        return b;
+    }
+
+    @Override
+    public synchronized int read(byte[] buf, int off, int len) throws IOException
+    {
+        if (_skipLF && len>0)
+        {
+            _skipLF=false;
+            if (_seenCRLF)
+            {
+                int b = super.read();
+                if (b==-1)
+                    return -1;
+                
+                if (b!='\n')
+                {
+                    buf[off]=(byte)(0xff&b);
+                    return 1+super.read(buf,off+1,len-1);
+                }
+            }
+        }
+        
+        return super.read(buf,off,len);
+    }
+    
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/RolloverFileOutputStream.java b/lib/jetty/org/eclipse/jetty/util/RolloverFileOutputStream.java
new file mode 100644 (file)
index 0000000..60f5da4
--- /dev/null
@@ -0,0 +1,340 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util; 
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/** 
+ * RolloverFileOutputStream
+ * 
+ * This output stream puts content in a file that is rolled over every 24 hours.
+ * The filename must include the string "yyyy_mm_dd", which is replaced with the 
+ * actual date when creating and rolling over the file.
+ * 
+ * Old files are retained for a number of days before being deleted.
+ * 
+ * 
+ */
+public class RolloverFileOutputStream extends FilterOutputStream
+{
+    private static Timer __rollover;
+    
+    final static String YYYY_MM_DD="yyyy_mm_dd";
+    final static String ROLLOVER_FILE_DATE_FORMAT = "yyyy_MM_dd";
+    final static String ROLLOVER_FILE_BACKUP_FORMAT = "HHmmssSSS";
+    final static int ROLLOVER_FILE_RETAIN_DAYS = 31;
+
+    private RollTask _rollTask;
+    private SimpleDateFormat _fileBackupFormat;
+    private SimpleDateFormat _fileDateFormat;
+
+    private String _filename;
+    private File _file;
+    private boolean _append;
+    private int _retainDays;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filename The filename must include the string "yyyy_mm_dd", 
+     * which is replaced with the actual date when creating and rolling over the file.
+     * @throws IOException
+     */
+    public RolloverFileOutputStream(String filename)
+        throws IOException
+    {
+        this(filename,true,ROLLOVER_FILE_RETAIN_DAYS);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filename The filename must include the string "yyyy_mm_dd", 
+     * which is replaced with the actual date when creating and rolling over the file.
+     * @param append If true, existing files will be appended to.
+     * @throws IOException
+     */
+    public RolloverFileOutputStream(String filename, boolean append)
+        throws IOException
+    {
+        this(filename,append,ROLLOVER_FILE_RETAIN_DAYS);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filename The filename must include the string "yyyy_mm_dd", 
+     * which is replaced with the actual date when creating and rolling over the file.
+     * @param append If true, existing files will be appended to.
+     * @param retainDays The number of days to retain files before deleting them.  0 to retain forever.
+     * @throws IOException
+     */
+    public RolloverFileOutputStream(String filename,
+                                    boolean append,
+                                    int retainDays)
+        throws IOException
+    {
+        this(filename,append,retainDays,TimeZone.getDefault());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filename The filename must include the string "yyyy_mm_dd", 
+     * which is replaced with the actual date when creating and rolling over the file.
+     * @param append If true, existing files will be appended to.
+     * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
+     * @throws IOException
+     */
+    public RolloverFileOutputStream(String filename,
+                                    boolean append,
+                                    int retainDays,
+                                    TimeZone zone)
+        throws IOException
+    {
+
+         this(filename,append,retainDays,zone,null,null);
+    }
+     
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filename The filename must include the string "yyyy_mm_dd", 
+     * which is replaced with the actual date when creating and rolling over the file.
+     * @param append If true, existing files will be appended to.
+     * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
+     * @param dateFormat The format for the date file substitution. The default is "yyyy_MM_dd". 
+     * @param backupFormat The format for the file extension of backup files. The default is "HHmmssSSS". 
+     * @throws IOException
+     */
+    public RolloverFileOutputStream(String filename,
+                                    boolean append,
+                                    int retainDays,
+                                    TimeZone zone,
+                                    String dateFormat,
+                                    String backupFormat)
+        throws IOException
+    {
+        super(null);
+
+        if (dateFormat==null)
+            dateFormat=ROLLOVER_FILE_DATE_FORMAT;
+        _fileDateFormat = new SimpleDateFormat(dateFormat);
+        
+        if (backupFormat==null)
+            backupFormat=ROLLOVER_FILE_BACKUP_FORMAT;
+        _fileBackupFormat = new SimpleDateFormat(backupFormat);
+        
+        _fileBackupFormat.setTimeZone(zone);
+        _fileDateFormat.setTimeZone(zone);
+        
+        if (filename!=null)
+        {
+            filename=filename.trim();
+            if (filename.length()==0)
+                filename=null;
+        }
+        if (filename==null)
+            throw new IllegalArgumentException("Invalid filename");
+
+        _filename=filename;
+        _append=append;
+        _retainDays=retainDays;
+        setFile();
+        
+        synchronized(RolloverFileOutputStream.class)
+        {
+            if (__rollover==null)
+                __rollover=new Timer(RolloverFileOutputStream.class.getName(),true);
+            
+            _rollTask=new RollTask();
+
+             Calendar now = Calendar.getInstance();
+             now.setTimeZone(zone);
+
+             GregorianCalendar midnight =
+                 new GregorianCalendar(now.get(Calendar.YEAR),
+                         now.get(Calendar.MONTH),
+                         now.get(Calendar.DAY_OF_MONTH),
+                         23,0);
+             midnight.setTimeZone(zone);
+             midnight.add(Calendar.HOUR,1);
+             __rollover.scheduleAtFixedRate(_rollTask,midnight.getTime(),1000L*60*60*24);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getFilename()
+    {
+        return _filename;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String getDatedFilename()
+    {
+        if (_file==null)
+            return null;
+        return _file.toString();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int getRetainDays()
+    {
+        return _retainDays;
+    }
+
+    /* ------------------------------------------------------------ */
+    private synchronized void setFile()
+        throws IOException
+    {
+        // Check directory
+        File file = new File(_filename);
+        _filename=file.getCanonicalPath();
+        file=new File(_filename);
+        File dir= new File(file.getParent());
+        if (!dir.isDirectory() || !dir.canWrite())
+            throw new IOException("Cannot write log directory "+dir);
+            
+        Date now=new Date();
+        
+        // Is this a rollover file?
+        String filename=file.getName();
+        int i=filename.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD);
+        if (i>=0)
+        {
+            file=new File(dir,
+                          filename.substring(0,i)+
+                          _fileDateFormat.format(now)+
+                          filename.substring(i+YYYY_MM_DD.length()));
+        }
+            
+        if (file.exists()&&!file.canWrite())
+            throw new IOException("Cannot write log file "+file);
+
+        // Do we need to change the output stream?
+        if (out==null || !file.equals(_file))
+        {
+            // Yep
+            _file=file;
+            if (!_append && file.exists())
+                file.renameTo(new File(file.toString()+"."+_fileBackupFormat.format(now)));
+            OutputStream oldOut=out;
+            out=new FileOutputStream(file.toString(),_append);
+            if (oldOut!=null)
+                oldOut.close();
+            //if(log.isDebugEnabled())log.debug("Opened "+_file);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void removeOldFiles()
+    {
+        if (_retainDays>0)
+        {
+            long now = System.currentTimeMillis();
+            
+            File file= new File(_filename);
+            File dir = new File(file.getParent());
+            String fn=file.getName();
+            int s=fn.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD);
+            if (s<0)
+                return;
+            String prefix=fn.substring(0,s);
+            String suffix=fn.substring(s+YYYY_MM_DD.length());
+
+            String[] logList=dir.list();
+            for (int i=0;i<logList.length;i++)
+            {
+                fn = logList[i];
+                if(fn.startsWith(prefix)&&fn.indexOf(suffix,prefix.length())>=0)
+                {        
+                    File f = new File(dir,fn);
+                    long date = f.lastModified();
+                    if ( ((now-date)/(1000*60*60*24))>_retainDays)
+                        f.delete();   
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (byte[] buf)
+            throws IOException
+     {
+            out.write (buf);
+     }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (byte[] buf, int off, int len)
+            throws IOException
+     {
+            out.write (buf, off, len);
+     }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     */
+    @Override
+    public void close()
+        throws IOException
+    {
+        synchronized(RolloverFileOutputStream.class)
+        {
+            try{super.close();}
+            finally
+            {
+                out=null;
+                _file=null;
+            }
+
+            _rollTask.cancel(); 
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class RollTask extends TimerTask
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                RolloverFileOutputStream.this.setFile();
+                RolloverFileOutputStream.this.removeOldFiles();
+
+            }
+            catch(IOException e)
+            {
+                // Cannot log this exception to a LOG, as RolloverFOS can be used by logging
+                e.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Scanner.java b/lib/jetty/org/eclipse/jetty/util/Scanner.java
new file mode 100644 (file)
index 0000000..f29db3d
--- /dev/null
@@ -0,0 +1,736 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * Scanner
+ * 
+ * Utility for scanning a directory for added, removed and changed
+ * files and reporting these events via registered Listeners.
+ *
+ */
+public class Scanner extends AbstractLifeCycle
+{
+    private static final Logger LOG = Log.getLogger(Scanner.class);
+    private static int __scannerId=0;
+    private int _scanInterval;
+    private int _scanCount = 0;
+    private final List<Listener> _listeners = new ArrayList<Listener>();
+    private final Map<String,TimeNSize> _prevScan = new HashMap<String,TimeNSize> ();
+    private final Map<String,TimeNSize> _currentScan = new HashMap<String,TimeNSize> ();
+    private FilenameFilter _filter;
+    private final List<File> _scanDirs = new ArrayList<File>();
+    private volatile boolean _running = false;
+    private boolean _reportExisting = true;
+    private boolean _reportDirs = true;
+    private Timer _timer;
+    private TimerTask _task;
+    private int _scanDepth=0;
+    
+    public enum Notification { ADDED, CHANGED, REMOVED };
+    private final Map<String,Notification> _notifications = new HashMap<String,Notification>();
+
+    static class TimeNSize
+    {
+        final long _lastModified;
+        final long _size;
+        
+        public TimeNSize(long lastModified, long size)
+        {
+            _lastModified = lastModified;
+            _size = size;
+        }
+        
+        @Override
+        public int hashCode()
+        {
+            return (int)_lastModified^(int)_size;
+        }
+        
+        @Override
+        public boolean equals(Object o)
+        {
+            if (o instanceof TimeNSize)
+            {
+                TimeNSize tns = (TimeNSize)o;
+                return tns._lastModified==_lastModified && tns._size==_size;
+            }
+            return false;
+        }
+        
+        @Override
+        public String toString()
+        {
+            return "[lm="+_lastModified+",s="+_size+"]";
+        }
+    }
+    
+    /**
+     * Listener
+     * 
+     * Marker for notifications re file changes.
+     */
+    public interface Listener
+    {
+    }
+
+    public interface ScanListener extends Listener
+    {
+        public void scan();
+    }
+    
+    public interface DiscreteListener extends Listener
+    {
+        public void fileChanged (String filename) throws Exception;
+        public void fileAdded (String filename) throws Exception;
+        public void fileRemoved (String filename) throws Exception;
+    }
+    
+    
+    public interface BulkListener extends Listener
+    {
+        public void filesChanged (List<String> filenames) throws Exception;
+    }
+
+    /**
+     * Listener that notifies when a scan has started and when it has ended.
+     */
+    public interface ScanCycleListener extends Listener
+    {
+        public void scanStarted(int cycle) throws Exception;
+        public void scanEnded(int cycle) throws Exception;
+    }
+
+    /**
+     * 
+     */
+    public Scanner ()
+    {       
+    }
+
+    /**
+     * Get the scan interval
+     * @return interval between scans in seconds
+     */
+    public synchronized int getScanInterval()
+    {
+        return _scanInterval;
+    }
+
+    /**
+     * Set the scan interval
+     * @param scanInterval pause between scans in seconds, or 0 for no scan after the initial scan.
+     */
+    public synchronized void setScanInterval(int scanInterval)
+    {
+        _scanInterval = scanInterval;
+        schedule();
+    }
+
+    public void setScanDirs (List<File> dirs)
+    {
+        _scanDirs.clear(); 
+        _scanDirs.addAll(dirs);
+    }
+    
+    public synchronized void addScanDir( File dir )
+    {
+        _scanDirs.add( dir );
+    }
+    
+    public List<File> getScanDirs ()
+    {
+        return Collections.unmodifiableList(_scanDirs);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param recursive True if scanning is recursive
+     * @see  #setScanDepth(int)
+     */
+    public void setRecursive (boolean recursive)
+    {
+        _scanDepth=recursive?-1:0;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if scanning is fully recursive (scandepth==-1)
+     * @see #getScanDepth()
+     */
+    public boolean getRecursive ()
+    {
+        return _scanDepth==-1;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the scanDepth.
+     * @return the scanDepth
+     */
+    public int getScanDepth()
+    {
+        return _scanDepth;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the scanDepth.
+     * @param scanDepth the scanDepth to set
+     */
+    public void setScanDepth(int scanDepth)
+    {
+        _scanDepth = scanDepth;
+    }
+
+    /**
+     * Apply a filter to files found in the scan directory.
+     * Only files matching the filter will be reported as added/changed/removed.
+     * @param filter
+     */
+    public void setFilenameFilter (FilenameFilter filter)
+    {
+        _filter = filter;
+    }
+
+    /**
+     * Get any filter applied to files in the scan dir.
+     * @return the filename filter
+     */
+    public FilenameFilter getFilenameFilter ()
+    {
+        return _filter;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Whether or not an initial scan will report all files as being
+     * added.
+     * @param reportExisting if true, all files found on initial scan will be 
+     * reported as being added, otherwise not
+     */
+    public void setReportExistingFilesOnStartup (boolean reportExisting)
+    {
+        _reportExisting = reportExisting;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean getReportExistingFilesOnStartup()
+    {
+        return _reportExisting;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Set if found directories should be reported.
+     * @param dirs
+     */
+    public void setReportDirs(boolean dirs)
+    {
+        _reportDirs=dirs;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean getReportDirs()
+    {
+        return _reportDirs;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Add an added/removed/changed listener
+     * @param listener
+     */
+    public synchronized void addListener (Listener listener)
+    {
+        if (listener == null)
+            return;
+        _listeners.add(listener);   
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Remove a registered listener
+     * @param listener the Listener to be removed
+     */
+    public synchronized void removeListener (Listener listener)
+    {
+        if (listener == null)
+            return;
+        _listeners.remove(listener);    
+    }
+
+
+    /**
+     * Start the scanning action.
+     */
+    @Override
+    public synchronized void doStart()
+    {
+        if (_running)
+            return;
+
+        _running = true;
+
+        if (_reportExisting)
+        {
+            // if files exist at startup, report them
+            scan();
+            scan(); // scan twice so files reported as stable
+        }
+        else
+        {
+            //just register the list of existing files and only report changes
+            scanFiles();
+            _prevScan.putAll(_currentScan);
+        }
+        schedule();
+    }
+
+    public TimerTask newTimerTask ()
+    {
+        return new TimerTask()
+        {
+            @Override
+            public void run() { scan(); }
+        };
+    }
+
+    public Timer newTimer ()
+    {
+        return new Timer("Scanner-"+__scannerId++, true);
+    }
+    
+    public void schedule ()
+    {  
+        if (_running)
+        {
+            if (_timer!=null)
+                _timer.cancel();
+            if (_task!=null)
+                _task.cancel();
+            if (getScanInterval() > 0)
+            {
+                _timer = newTimer();
+                _task = newTimerTask();
+                _timer.schedule(_task, 1010L*getScanInterval(),1010L*getScanInterval());
+            }
+        }
+    }
+    /**
+     * Stop the scanning.
+     */
+    @Override
+    public synchronized void doStop()
+    {
+        if (_running)
+        {
+            _running = false; 
+            if (_timer!=null)
+                _timer.cancel();
+            if (_task!=null)
+                _task.cancel();
+            _task=null;
+            _timer=null;
+        }
+    }
+
+    /**
+     * @return true if the path exists in one of the scandirs
+     */
+    public boolean exists(String path)
+    {
+        for (File dir : _scanDirs)
+            if (new File(dir,path).exists())
+                return true;
+        return false;
+    }
+    
+    
+    /**
+     * Perform a pass of the scanner and report changes
+     */
+    public synchronized void scan ()
+    {
+        reportScanStart(++_scanCount);
+        scanFiles();
+        reportDifferences(_currentScan, _prevScan);
+        _prevScan.clear();
+        _prevScan.putAll(_currentScan);
+        reportScanEnd(_scanCount);
+        
+        for (Listener l : _listeners)
+        {
+            try
+            {
+                if (l instanceof ScanListener)
+                    ((ScanListener)l).scan();
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+            catch (Error e)
+            {
+                LOG.warn(e);
+            }
+        }
+    }
+
+    /**
+     * Recursively scan all files in the designated directories.
+     */
+    public synchronized void scanFiles ()
+    {
+        if (_scanDirs==null)
+            return;
+        
+        _currentScan.clear();
+        Iterator<File> itor = _scanDirs.iterator();
+        while (itor.hasNext())
+        {
+            File dir = itor.next();
+            
+            if ((dir != null) && (dir.exists()))
+                try
+                {
+                    scanFile(dir.getCanonicalFile(), _currentScan,0);
+                }
+                catch (IOException e)
+                {
+                    LOG.warn("Error scanning files.", e);
+                }
+        }
+    }
+
+
+    /**
+     * Report the adds/changes/removes to the registered listeners
+     * 
+     * @param currentScan the info from the most recent pass
+     * @param oldScan info from the previous pass
+     */
+    public synchronized void reportDifferences (Map<String,TimeNSize> currentScan, Map<String,TimeNSize> oldScan) 
+    {
+        // scan the differences and add what was found to the map of notifications:
+
+        Set<String> oldScanKeys = new HashSet<String>(oldScan.keySet());
+        
+        // Look for new and changed files
+        for (Map.Entry<String, TimeNSize> entry: currentScan.entrySet())
+        {
+            String file = entry.getKey(); 
+            if (!oldScanKeys.contains(file))
+            {
+                Notification old=_notifications.put(file,Notification.ADDED);
+                if (old!=null)
+                { 
+                    switch(old)
+                    {
+                        case REMOVED: 
+                        case CHANGED:
+                            _notifications.put(file,Notification.CHANGED);
+                    }
+                }
+            }
+            else if (!oldScan.get(file).equals(currentScan.get(file)))
+            {
+                Notification old=_notifications.put(file,Notification.CHANGED);
+                if (old!=null)
+                {
+                    switch(old)
+                    {
+                        case ADDED:
+                            _notifications.put(file,Notification.ADDED);
+                    }
+                }
+            }
+        }
+        
+        // Look for deleted files
+        for (String file : oldScan.keySet())
+        {
+            if (!currentScan.containsKey(file))
+            {
+                Notification old=_notifications.put(file,Notification.REMOVED);
+                if (old!=null)
+                {
+                    switch(old)
+                    {
+                        case ADDED:
+                            _notifications.remove(file);
+                    }
+                }
+            }
+        }
+        
+        if (LOG.isDebugEnabled())
+            LOG.debug("scanned "+_scanDirs+": "+_notifications);
+                
+        // Process notifications
+        // Only process notifications that are for stable files (ie same in old and current scan).
+        List<String> bulkChanges = new ArrayList<String>();
+        for (Iterator<Entry<String,Notification>> iter = _notifications.entrySet().iterator();iter.hasNext();)
+        {
+            Entry<String,Notification> entry=iter.next();
+            String file=entry.getKey();
+            
+            // Is the file stable?
+            if (oldScan.containsKey(file))
+            {
+                if (!oldScan.get(file).equals(currentScan.get(file)))
+                    continue;
+            }
+            else if (currentScan.containsKey(file))
+                continue;
+                            
+            // File is stable so notify
+            Notification notification=entry.getValue();
+            iter.remove();
+            bulkChanges.add(file);
+            switch(notification)
+            {
+                case ADDED:
+                    reportAddition(file);
+                    break;
+                case CHANGED:
+                    reportChange(file);
+                    break;
+                case REMOVED:
+                    reportRemoval(file);
+                    break;
+            }
+        }
+        if (!bulkChanges.isEmpty())
+            reportBulkChanges(bulkChanges);
+    }
+
+
+    /**
+     * Get last modified time on a single file or recurse if
+     * the file is a directory. 
+     * @param f file or directory
+     * @param scanInfoMap map of filenames to last modified times
+     */
+    private void scanFile (File f, Map<String,TimeNSize> scanInfoMap, int depth)
+    {
+        try
+        {
+            if (!f.exists())
+                return;
+
+            if (f.isFile() || depth>0&& _reportDirs && f.isDirectory())
+            {
+                if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName())))
+                {
+                    LOG.debug("scan accepted {}",f);
+                    String name = f.getCanonicalPath();
+                    scanInfoMap.put(name, new TimeNSize(f.lastModified(),f.length()));
+                }
+                else
+                    LOG.debug("scan rejected {}",f);
+            }
+            
+            // If it is a directory, scan if it is a known directory or the depth is OK.
+            if (f.isDirectory() && (depth<_scanDepth || _scanDepth==-1 || _scanDirs.contains(f)))
+            {
+                File[] files = f.listFiles();
+                if (files != null)
+                {
+                    for (int i=0;i<files.length;i++)
+                        scanFile(files[i], scanInfoMap,depth+1);
+                }
+                else
+                    LOG.warn("Error listing files in directory {}", f);
+            }
+        }
+        catch (IOException e)
+        {
+            LOG.warn("Error scanning watched files", e);
+        }
+    }
+
+    private void warn(Object listener,String filename,Throwable th)
+    {
+        LOG.warn(listener+" failed on '"+filename, th);
+    }
+
+    /**
+     * Report a file addition to the registered FileAddedListeners
+     * @param filename
+     */
+    private void reportAddition (String filename)
+    {
+        Iterator<Listener> itor = _listeners.iterator();
+        while (itor.hasNext())
+        {
+            Listener l = itor.next();
+            try
+            {
+                if (l instanceof DiscreteListener)
+                    ((DiscreteListener)l).fileAdded(filename);
+            }
+            catch (Exception e)
+            {
+                warn(l,filename,e);
+            }
+            catch (Error e)
+            {
+                warn(l,filename,e);
+            }
+        }
+    }
+
+
+    /**
+     * Report a file removal to the FileRemovedListeners
+     * @param filename
+     */
+    private void reportRemoval (String filename)
+    {
+        Iterator<Listener> itor = _listeners.iterator();
+        while (itor.hasNext())
+        {
+            Object l = itor.next();
+            try
+            {
+                if (l instanceof DiscreteListener)
+                    ((DiscreteListener)l).fileRemoved(filename);
+            }
+            catch (Exception e)
+            {
+                warn(l,filename,e);
+            }
+            catch (Error e)
+            {
+                warn(l,filename,e);
+            }
+        }
+    }
+
+
+    /**
+     * Report a file change to the FileChangedListeners
+     * @param filename
+     */
+    private void reportChange (String filename)
+    {
+        Iterator<Listener> itor = _listeners.iterator();
+        while (itor.hasNext())
+        {
+            Listener l = itor.next();
+            try
+            {
+                if (l instanceof DiscreteListener)
+                    ((DiscreteListener)l).fileChanged(filename);
+            }
+            catch (Exception e)
+            {
+                warn(l,filename,e);
+            }
+            catch (Error e)
+            {
+                warn(l,filename,e);
+            }
+        }
+    }
+    
+    private void reportBulkChanges (List<String> filenames)
+    {
+        Iterator<Listener> itor = _listeners.iterator();
+        while (itor.hasNext())
+        {
+            Listener l = itor.next();
+            try
+            {
+                if (l instanceof BulkListener)
+                    ((BulkListener)l).filesChanged(filenames);
+            }
+            catch (Exception e)
+            {
+                warn(l,filenames.toString(),e);
+            }
+            catch (Error e)
+            {
+                warn(l,filenames.toString(),e);
+            }
+        }
+    }
+    
+    /**
+     * signal any scan cycle listeners that a scan has started
+     */
+    private void reportScanStart(int cycle)
+    {
+        for (Listener listener : _listeners)
+        {
+            try
+            {
+                if (listener instanceof ScanCycleListener)
+                {
+                    ((ScanCycleListener)listener).scanStarted(cycle);
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.warn(listener + " failed on scan start for cycle " + cycle, e);
+            }
+        }
+    }
+
+    /**
+     * sign
+     */
+    private void reportScanEnd(int cycle)
+    {
+        for (Listener listener : _listeners)
+        {
+            try
+            {
+                if (listener instanceof ScanCycleListener)
+                {
+                    ((ScanCycleListener)listener).scanEnded(cycle);
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.warn(listener + " failed on scan end for cycle " + cycle, e);
+            }
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/SharedBlockingCallback.java b/lib/jetty/org/eclipse/jetty/util/SharedBlockingCallback.java
new file mode 100644 (file)
index 0000000..3085a68
--- /dev/null
@@ -0,0 +1,272 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+
+
+/* ------------------------------------------------------------ */
+/** Provides a reusable BlockingCallback.
+ * A typical usage pattern is:
+ * <pre>
+ * void someBlockingCall(Object... args) throws IOException
+ * {
+ *   try(Blocker blocker=sharedBlockingCallback.acquire())
+ *   {
+ *     someAsyncCall(args,blocker);
+ *     blocker.block();
+ *   }
+ * }
+ * </pre>
+ */
+public class SharedBlockingCallback
+{
+    private static final Logger LOG = Log.getLogger(SharedBlockingCallback.class);
+
+    
+    private static Throwable IDLE = new Throwable()
+    {
+        @Override
+        public String toString()
+        {
+            return "IDLE";
+        }
+    };
+
+    private static Throwable SUCCEEDED = new Throwable()
+    {
+        @Override
+        public String toString()
+        {
+            return "SUCCEEDED";
+        }
+    };
+    
+    private static Throwable FAILED = new Throwable()
+    {
+        @Override
+        public String toString()
+        {
+            return "FAILED";
+        }
+    };
+
+    final Blocker _blocker;
+    
+    public SharedBlockingCallback()
+    {
+        this(new Blocker());
+    }
+    
+    protected SharedBlockingCallback(Blocker blocker)
+    {
+        _blocker=blocker;
+    }
+    
+    public Blocker acquire() throws IOException
+    {
+        _blocker._lock.lock();
+        try
+        {
+            while (_blocker._state != IDLE)
+                _blocker._idle.await();
+            _blocker._state = null;
+        }
+        catch (final InterruptedException e)
+        {
+            throw new InterruptedIOException()
+            {
+                {
+                    initCause(e);
+                }
+            };
+        }
+        finally
+        {
+            _blocker._lock.unlock();
+        }
+        return _blocker;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /** A Closeable Callback.
+     * Uses the auto close mechanism to check block has been called OK.
+     */
+    public static class Blocker implements Callback, Closeable
+    {
+        final ReentrantLock _lock = new ReentrantLock();
+        final Condition _idle = _lock.newCondition();
+        final Condition _complete = _lock.newCondition();
+        Throwable _state = IDLE;
+
+        public Blocker()
+        {
+        }
+
+        @Override
+        public void succeeded()
+        {
+            _lock.lock();
+            try
+            {
+                if (_state == null)
+                {
+                    _state = SUCCEEDED;
+                    _complete.signalAll();
+                }
+                else if (_state == IDLE)
+                    throw new IllegalStateException("IDLE");
+            }
+            finally
+            {
+                _lock.unlock();
+            }
+        }
+
+        @Override
+        public void failed(Throwable cause)
+        {
+            _lock.lock();
+            try
+            {
+                if (_state == null)
+                {
+                    // TODO remove when feedback received on 435322
+                    if (cause==null)
+                        LOG.warn("null failed cause (please report stack trace) ",new Throwable());
+                    _state = cause==null?FAILED:cause;
+                    _complete.signalAll();
+                }
+                else if (_state == IDLE)
+                    throw new IllegalStateException("IDLE");
+            }
+            finally
+            {
+                _lock.unlock();
+            }
+        }
+
+        /**
+         * Block until the Callback has succeeded or failed and after the return leave in the state to allow reuse. This is useful for code that wants to
+         * repeatable use a FutureCallback to convert an asynchronous API to a blocking API.
+         * 
+         * @throws IOException
+         *             if exception was caught during blocking, or callback was cancelled
+         */
+        public void block() throws IOException
+        {
+            if (NonBlockingThread.isNonBlockingThread())
+                LOG.warn("Blocking a NonBlockingThread: ",new Throwable());
+            
+            _lock.lock();
+            try
+            {
+                while (_state == null)
+                {
+                    // TODO remove this debug timout!
+                    // This is here to help debug 435322,
+                    if (!_complete.await(10,TimeUnit.MINUTES))
+                    {
+                        IOException x = new IOException("DEBUG timeout");
+                        LOG.warn("Blocked too long (please report!!!) "+this, x);
+                        _state=x;
+                    }
+                }
+
+                if (_state == SUCCEEDED)
+                    return;
+                if (_state == IDLE)
+                    throw new IllegalStateException("IDLE");
+                if (_state instanceof IOException)
+                    throw (IOException)_state;
+                if (_state instanceof CancellationException)
+                    throw (CancellationException)_state;
+                if (_state instanceof RuntimeException)
+                    throw (RuntimeException)_state;
+                if (_state instanceof Error)
+                    throw (Error)_state;
+                throw new IOException(_state);
+            }
+            catch (final InterruptedException e)
+            {
+                throw new InterruptedIOException()
+                {
+                    {
+                        initCause(e);
+                    }
+                };
+            }
+            finally
+            {
+                _lock.unlock();
+            }
+        }
+        
+        /**
+         * Check the Callback has succeeded or failed and after the return leave in the state to allow reuse.
+         * 
+         * @throws IOException
+         *             if exception was caught during blocking, or callback was cancelled
+         */
+        @Override
+        public void close() throws IOException
+        {
+            _lock.lock();
+            try
+            {
+                if (_state == IDLE)
+                    throw new IllegalStateException("IDLE");
+                if (_state == null)
+                    LOG.debug("Blocker not complete",new Throwable());
+            }
+            finally
+            {
+                _state = IDLE;
+                _idle.signalAll();
+                _lock.unlock();
+            }
+        }
+
+        @Override
+        public String toString()
+        {
+            _lock.lock();
+            try
+            {
+                return String.format("%s@%x{%s}",SharedBlockingCallback.class.getSimpleName(),hashCode(),_state);
+            }
+            finally
+            {
+                _lock.unlock();
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/SocketAddressResolver.java b/lib/jetty/org/eclipse/jetty/util/SocketAddressResolver.java
new file mode 100644 (file)
index 0000000..e75abfc
--- /dev/null
@@ -0,0 +1,175 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.channels.UnresolvedAddressException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * Creates asynchronously {@link SocketAddress} instances, returning them through a {@link Promise},
+ * in order to avoid blocking on DNS lookup.
+ * <p />
+ * {@link InetSocketAddress#InetSocketAddress(String, int)} attempts to perform a DNS resolution of
+ * the host name, and this may block for several seconds.
+ * This class creates the {@link InetSocketAddress} in a separate thread and provides the result
+ * through a {@link Promise}, with the possibility to specify a timeout for the operation.
+ * <p />
+ * Example usage:
+ * <pre>
+ * SocketAddressResolver resolver = new SocketAddressResolver(executor, scheduler);
+ * resolver.resolve("www.google.com", 80, new Promise&lt;SocketAddress&gt;()
+ * {
+ *     public void succeeded(SocketAddress result)
+ *     {
+ *         // The address was resolved
+ *     }
+ *
+ *     public void failed(Throwable failure)
+ *     {
+ *         // The address resolution failed
+ *     }
+ * });
+ * </pre>
+ */
+public class SocketAddressResolver
+{
+    private static final Logger LOG = Log.getLogger(SocketAddressResolver.class);
+
+    private final Executor executor;
+    private final Scheduler scheduler;
+    private final long timeout;
+
+    /**
+     * Creates a new instance with the given executor (to perform DNS resolution in a separate thread),
+     * the given scheduler (to cancel the operation if it takes too long) and the given timeout, in milliseconds.
+     *
+     * @param executor the thread pool to use to perform DNS resolution in pooled threads
+     * @param scheduler the scheduler to schedule tasks to cancel DNS resolution if it takes too long
+     * @param timeout the timeout, in milliseconds, for the DNS resolution to complete
+     */
+    public SocketAddressResolver(Executor executor, Scheduler scheduler, long timeout)
+    {
+        this.executor = executor;
+        this.scheduler = scheduler;
+        this.timeout = timeout;
+    }
+
+    public Executor getExecutor()
+    {
+        return executor;
+    }
+
+    public Scheduler getScheduler()
+    {
+        return scheduler;
+    }
+
+    public long getTimeout()
+    {
+        return timeout;
+    }
+
+    /**
+     * Resolves the given host and port, returning a {@link SocketAddress} through the given {@link Promise}
+     * with the default timeout.
+     *
+     * @param host the host to resolve
+     * @param port the port of the resulting socket address
+     * @param promise the callback invoked when the resolution succeeds or fails
+     * @see #resolve(String, int, long, Promise)
+     */
+    public void resolve(String host, int port, Promise<SocketAddress> promise)
+    {
+        resolve(host, port, timeout, promise);
+    }
+
+    /**
+     * Resolves the given host and port, returning a {@link SocketAddress} through the given {@link Promise}
+     * with the given timeout.
+     *
+     * @param host the host to resolve
+     * @param port the port of the resulting socket address
+     * @param timeout the timeout, in milliseconds, for the DNS resolution to complete
+     * @param promise the callback invoked when the resolution succeeds or fails
+     */
+    protected void resolve(final String host, final int port, final long timeout, final Promise<SocketAddress> promise)
+    {
+        executor.execute(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                Scheduler.Task task = null;
+                final AtomicBoolean complete = new AtomicBoolean();
+                if (timeout > 0)
+                {
+                    final Thread thread = Thread.currentThread();
+                    task = scheduler.schedule(new Runnable()
+                    {
+                        @Override
+                        public void run()
+                        {
+                            if (complete.compareAndSet(false, true))
+                            {
+                                promise.failed(new TimeoutException());
+                                thread.interrupt();
+                            }
+                        }
+                    }, timeout, TimeUnit.MILLISECONDS);
+                }
+
+                try
+                {
+                    long start = System.nanoTime();
+                    InetSocketAddress result = new InetSocketAddress(host, port);
+                    long elapsed = System.nanoTime() - start;
+                    LOG.debug("Resolved {} in {} ms", host, TimeUnit.NANOSECONDS.toMillis(elapsed));
+                    if (complete.compareAndSet(false, true))
+                    {
+                        if (result.isUnresolved())
+                            promise.failed(new UnresolvedAddressException());
+                        else
+                            promise.succeeded(result);
+                    }
+                }
+                catch (Throwable x)
+                {
+                    if (complete.compareAndSet(false, true))
+                        promise.failed(x);
+                }
+                finally
+                {
+                    if (task != null)
+                        task.cancel();
+                    // Reset the interrupted status before releasing the thread to the pool
+                    Thread.interrupted();
+                }
+            }
+        });
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/StringUtil.java b/lib/jetty/org/eclipse/jetty/util/StringUtil.java
new file mode 100644 (file)
index 0000000..55868ad
--- /dev/null
@@ -0,0 +1,735 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/** Fast String Utilities.
+ *
+ * These string utilities provide both convenience methods and
+ * performance improvements over most standard library versions. The
+ * main aim of the optimizations is to avoid object creation unless
+ * absolutely required.
+ *
+ * 
+ */
+public class StringUtil
+{
+    private static final Logger LOG = Log.getLogger(StringUtil.class);
+    
+    
+    private final static Trie<String> CHARSETS= new ArrayTrie<>(256);
+    
+    public static final String ALL_INTERFACES="0.0.0.0";
+    public static final String CRLF="\015\012";
+    public static final String __LINE_SEPARATOR=
+        System.getProperty("line.separator","\n");
+       
+    public static final String __ISO_8859_1="ISO-8859-1";
+    public final static String __UTF8="UTF-8";
+    public final static String __UTF16="UTF-16";
+
+    /**
+     * @deprecated Use {@link StandardCharsets#UTF_8}
+     */
+    @Deprecated
+    public final static Charset __UTF8_CHARSET=StandardCharsets.UTF_8;
+    /**
+     * @deprecated Use {@link StandardCharsets#ISO_8859_1}
+     */
+    @Deprecated
+    public final static Charset __ISO_8859_1_CHARSET=StandardCharsets.ISO_8859_1;
+    /**
+     * @deprecated Use {@link StandardCharsets#UTF_16}
+     */
+    @Deprecated
+    public final static Charset __UTF16_CHARSET=StandardCharsets.UTF_16;
+    /**
+     * @deprecated Use {@link StandardCharsets#US_ASCII}
+     */
+    @Deprecated
+    public final static Charset __US_ASCII_CHARSET=StandardCharsets.US_ASCII;
+    
+    static
+    {
+        CHARSETS.put("UTF-8",__UTF8);
+        CHARSETS.put("UTF8",__UTF8);
+        CHARSETS.put("UTF-16",__UTF16);
+        CHARSETS.put("UTF16",__UTF16);
+        CHARSETS.put("ISO-8859-1",__ISO_8859_1);
+        CHARSETS.put("ISO_8859_1",__ISO_8859_1);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convert alternate charset names (eg utf8) to normalized
+     * name (eg UTF-8).
+     */
+    public static String normalizeCharset(String s)
+    {
+        String n=CHARSETS.get(s);
+        return (n==null)?s:n;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convert alternate charset names (eg utf8) to normalized
+     * name (eg UTF-8).
+     */
+    public static String normalizeCharset(String s,int offset,int length)
+    {
+        String n=CHARSETS.get(s,offset,length);       
+        return (n==null)?s.substring(offset,offset+length):n;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public static final char[] lowercases = {
+          '\000','\001','\002','\003','\004','\005','\006','\007',
+          '\010','\011','\012','\013','\014','\015','\016','\017',
+          '\020','\021','\022','\023','\024','\025','\026','\027',
+          '\030','\031','\032','\033','\034','\035','\036','\037',
+          '\040','\041','\042','\043','\044','\045','\046','\047',
+          '\050','\051','\052','\053','\054','\055','\056','\057',
+          '\060','\061','\062','\063','\064','\065','\066','\067',
+          '\070','\071','\072','\073','\074','\075','\076','\077',
+          '\100','\141','\142','\143','\144','\145','\146','\147',
+          '\150','\151','\152','\153','\154','\155','\156','\157',
+          '\160','\161','\162','\163','\164','\165','\166','\167',
+          '\170','\171','\172','\133','\134','\135','\136','\137',
+          '\140','\141','\142','\143','\144','\145','\146','\147',
+          '\150','\151','\152','\153','\154','\155','\156','\157',
+          '\160','\161','\162','\163','\164','\165','\166','\167',
+          '\170','\171','\172','\173','\174','\175','\176','\177' };
+
+    /* ------------------------------------------------------------ */
+    /**
+     * fast lower case conversion. Only works on ascii (not unicode)
+     * @param s the string to convert
+     * @return a lower case version of s
+     */
+    public static String asciiToLowerCase(String s)
+    {
+        char[] c = null;
+        int i=s.length();
+
+        // look for first conversion
+        while (i-->0)
+        {
+            char c1=s.charAt(i);
+            if (c1<=127)
+            {
+                char c2=lowercases[c1];
+                if (c1!=c2)
+                {
+                    c=s.toCharArray();
+                    c[i]=c2;
+                    break;
+                }
+            }
+        }
+
+        while (i-->0)
+        {
+            if(c[i]<=127)
+                c[i] = lowercases[c[i]];
+        }
+        
+        return c==null?s:new String(c);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static boolean startsWithIgnoreCase(String s,String w)
+    {
+        if (w==null)
+            return true;
+        
+        if (s==null || s.length()<w.length())
+            return false;
+        
+        for (int i=0;i<w.length();i++)
+        {
+            char c1=s.charAt(i);
+            char c2=w.charAt(i);
+            if (c1!=c2)
+            {
+                if (c1<=127)
+                    c1=lowercases[c1];
+                if (c2<=127)
+                    c2=lowercases[c2];
+                if (c1!=c2)
+                    return false;
+            }
+        }
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static boolean endsWithIgnoreCase(String s,String w)
+    {
+        if (w==null)
+            return true;
+
+        if (s==null)
+            return false;
+            
+        int sl=s.length();
+        int wl=w.length();
+        
+        if (sl<wl)
+            return false;
+        
+        for (int i=wl;i-->0;)
+        {
+            char c1=s.charAt(--sl);
+            char c2=w.charAt(i);
+            if (c1!=c2)
+            {
+                if (c1<=127)
+                    c1=lowercases[c1];
+                if (c2<=127)
+                    c2=lowercases[c2];
+                if (c1!=c2)
+                    return false;
+            }
+        }
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * returns the next index of a character from the chars string
+     */
+    public static int indexFrom(String s,String chars)
+    {
+        for (int i=0;i<s.length();i++)
+           if (chars.indexOf(s.charAt(i))>=0)
+              return i;
+        return -1;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * replace substrings within string.
+     */
+    public static String replace(String s, String sub, String with)
+    {
+        int c=0;
+        int i=s.indexOf(sub,c);
+        if (i == -1)
+            return s;
+    
+        StringBuilder buf = new StringBuilder(s.length()+with.length());
+
+        do
+        {
+            buf.append(s.substring(c,i));
+            buf.append(with);
+            c=i+sub.length();
+        } while ((i=s.indexOf(sub,c))!=-1);
+
+        if (c<s.length())
+            buf.append(s.substring(c,s.length()));
+
+        return buf.toString();
+        
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Remove single or double quotes.
+     */
+    public static String unquote(String s)
+    {
+        return QuotedStringTokenizer.unquote(s);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Append substring to StringBuilder 
+     * @param buf StringBuilder to append to
+     * @param s String to append from
+     * @param offset The offset of the substring
+     * @param length The length of the substring
+     */
+    public static void append(StringBuilder buf,
+                              String s,
+                              int offset,
+                              int length)
+    {
+        synchronized(buf)
+        {
+            int end=offset+length;
+            for (int i=offset; i<end;i++)
+            {
+                if (i>=s.length())
+                    break;
+                buf.append(s.charAt(i));
+            }
+        }
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * append hex digit
+     * 
+     */
+    public static void append(StringBuilder buf,byte b,int base)
+    {
+        int bi=0xff&b;
+        int c='0'+(bi/base)%base;
+        if (c>'9')
+            c= 'a'+(c-'0'-10);
+        buf.append((char)c);
+        c='0'+bi%base;
+        if (c>'9')
+            c= 'a'+(c-'0'-10);
+        buf.append((char)c);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void append2digits(StringBuffer buf,int i)
+    {
+        if (i<100)
+        {
+            buf.append((char)(i/10+'0'));
+            buf.append((char)(i%10+'0'));
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static void append2digits(StringBuilder buf,int i)
+    {
+        if (i<100)
+        {
+            buf.append((char)(i/10+'0'));
+            buf.append((char)(i%10+'0'));
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Return a non null string.
+     * @param s String
+     * @return The string passed in or empty string if it is null. 
+     */
+    public static String nonNull(String s)
+    {
+        if (s==null)
+            return "";
+        return s;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static boolean equals(String s,char[] buf, int offset, int length)
+    {
+        if (s.length()!=length)
+            return false;
+        for (int i=0;i<length;i++)
+            if (buf[offset+i]!=s.charAt(i))
+                return false;
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toUTF8String(byte[] b,int offset,int length)
+    {
+        return new String(b,offset,length,StandardCharsets.UTF_8);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toString(byte[] b,int offset,int length,String charset)
+    {
+        try
+        {
+            return new String(b,offset,length,charset);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Test if a string is null or only has whitespace characters in it.
+     * <p>
+     * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better.
+     * 
+     * <pre>
+     *   isBlank(null)   == true
+     *   isBlank("")     == true
+     *   isBlank("\r\n") == true
+     *   isBlank("\t")   == true
+     *   isBlank("   ")  == true
+     *   isBlank("a")    == false
+     *   isBlank(".")    == false
+     *   isBlank(";\n")  == false
+     * </pre>
+     * 
+     * @param str
+     *            the string to test.
+     * @return true if string is null or only whitespace characters, false if non-whitespace characters encountered.
+     */
+    public static boolean isBlank(String str)
+    {
+        if (str == null)
+        {
+            return true;
+        }
+        int len = str.length();
+        for (int i = 0; i < len; i++)
+        {
+            if (!Character.isWhitespace(str.codePointAt(i)))
+            {
+                // found a non-whitespace, we can stop searching  now
+                return false;
+            }
+        }
+        // only whitespace
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Test if a string is not null and contains at least 1 non-whitespace characters in it.
+     * <p>
+     * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better.
+     * 
+     * <pre>
+     *   isNotBlank(null)   == false
+     *   isNotBlank("")     == false
+     *   isNotBlank("\r\n") == false
+     *   isNotBlank("\t")   == false
+     *   isNotBlank("   ")  == false
+     *   isNotBlank("a")    == true
+     *   isNotBlank(".")    == true
+     *   isNotBlank(";\n")  == true
+     * </pre>
+     * 
+     * @param str
+     *            the string to test.
+     * @return true if string is not null and has at least 1 non-whitespace character, false if null or all-whitespace characters.
+     */
+    public static boolean isNotBlank(String str)
+    {
+        if (str == null)
+        {
+            return false;
+        }
+        int len = str.length();
+        for (int i = 0; i < len; i++)
+        {
+            if (!Character.isWhitespace(str.codePointAt(i)))
+            {
+                // found a non-whitespace, we can stop searching  now
+                return true;
+            }
+        }
+        // only whitespace
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static boolean isUTF8(String charset)
+    {
+        return __UTF8.equalsIgnoreCase(charset)||__UTF8.equalsIgnoreCase(normalizeCharset(charset));
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static String printable(String name)
+    {
+        if (name==null)
+            return null;
+        StringBuilder buf = new StringBuilder(name.length());
+        for (int i=0;i<name.length();i++)
+        {
+            char c=name.charAt(i);
+            if (!Character.isISOControl(c))
+                buf.append(c);
+        }
+        return buf.toString();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static String printable(byte[] b)
+    {
+        StringBuilder buf = new StringBuilder();
+        for (int i=0;i<b.length;i++)
+        {
+            char c=(char)b[i];
+            if (Character.isWhitespace(c)|| c>' ' && c<0x7f)
+                buf.append(c);
+            else 
+            {
+                buf.append("0x");
+                TypeUtil.toHex(b[i],buf);
+            }
+        }
+        return buf.toString();
+    }
+    
+    public static byte[] getBytes(String s)
+    {
+        return s.getBytes(StandardCharsets.ISO_8859_1);
+    }
+    
+    public static byte[] getUtf8Bytes(String s)
+    {
+        return s.getBytes(StandardCharsets.UTF_8);
+    }
+    
+    public static byte[] getBytes(String s,String charset)
+    {
+        try
+        {
+            return s.getBytes(charset);
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+            return s.getBytes();
+        }
+    }
+    
+    
+    
+    /**
+     * Converts a binary SID to a string SID
+     * 
+     * http://en.wikipedia.org/wiki/Security_Identifier
+     * 
+     * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn
+     */
+    public static String sidBytesToString(byte[] sidBytes)
+    {
+        StringBuilder sidString = new StringBuilder();
+        
+        // Identify this as a SID
+        sidString.append("S-");
+        
+        // Add SID revision level (expect 1 but may change someday)
+        sidString.append(Byte.toString(sidBytes[0])).append('-');
+        
+        StringBuilder tmpBuilder = new StringBuilder();
+        
+        // crunch the six bytes of issuing authority value
+        for (int i = 2; i <= 7; ++i)
+        {
+            tmpBuilder.append(Integer.toHexString(sidBytes[i] & 0xFF));
+        }
+        
+        sidString.append(Long.parseLong(tmpBuilder.toString(), 16)); // '-' is in the subauth loop
+   
+        // the number of subAuthorities we need to attach
+        int subAuthorityCount = sidBytes[1];
+
+        // attach each of the subAuthorities
+        for (int i = 0; i < subAuthorityCount; ++i)
+        {
+            int offset = i * 4;
+            tmpBuilder.setLength(0);
+            // these need to be zero padded hex and little endian
+            tmpBuilder.append(String.format("%02X%02X%02X%02X", 
+                    (sidBytes[11 + offset] & 0xFF),
+                    (sidBytes[10 + offset] & 0xFF),
+                    (sidBytes[9 + offset] & 0xFF),
+                    (sidBytes[8 + offset] & 0xFF)));  
+            sidString.append('-').append(Long.parseLong(tmpBuilder.toString(), 16));
+        }
+        
+        return sidString.toString();
+    }
+    
+    /**
+     * Converts a string SID to a binary SID
+     * 
+     * http://en.wikipedia.org/wiki/Security_Identifier
+     * 
+     * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn
+     */
+    public static byte[] sidStringToBytes( String sidString )
+    {
+        String[] sidTokens = sidString.split("-");
+        
+        int subAuthorityCount = sidTokens.length - 3; // S-Rev-IdAuth-
+        
+        int byteCount = 0;
+        byte[] sidBytes = new byte[1 + 1 + 6 + (4 * subAuthorityCount)];
+        
+        // the revision byte
+        sidBytes[byteCount++] = (byte)Integer.parseInt(sidTokens[1]);
+
+        // the # of sub authorities byte
+        sidBytes[byteCount++] = (byte)subAuthorityCount;
+
+        // the certAuthority
+        String hexStr = Long.toHexString(Long.parseLong(sidTokens[2]));
+        
+        while( hexStr.length() < 12) // pad to 12 characters
+        {
+            hexStr = "0" + hexStr;
+        }
+
+        // place the certAuthority 6 bytes
+        for ( int i = 0 ; i < hexStr.length(); i = i + 2)
+        {
+            sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(i, i + 2),16);
+        }
+                
+        
+        for ( int i = 3; i < sidTokens.length ; ++i)
+        {
+            hexStr = Long.toHexString(Long.parseLong(sidTokens[i]));
+            
+            while( hexStr.length() < 8) // pad to 8 characters
+            {
+                hexStr = "0" + hexStr;
+            }     
+            
+            // place the inverted sub authorities, 4 bytes each
+            for ( int j = hexStr.length(); j > 0; j = j - 2)
+            {          
+                sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(j-2, j),16);
+            }
+        }
+      
+        return sidBytes;
+    }
+    
+
+    /**
+     * Convert String to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+     * 
+     * @param string
+     *            A String containing an integer.
+     * @return an int
+     */
+    public static int toInt(String string)
+    {
+        int val = 0;
+        boolean started = false;
+        boolean minus = false;
+
+        for (int i = 0; i < string.length(); i++)
+        {
+            char b = string.charAt(i);
+            if (b <= ' ')
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val = val * 10 + (b - '0');
+                started = true;
+            }
+            else if (b == '-' && !started)
+            {
+                minus = true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+            return minus?(-val):val;
+        throw new NumberFormatException(string);
+    }
+
+    /**
+     * Convert String to an long. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+     * 
+     * @param string
+     *            A String containing an integer.
+     * @return an int
+     */
+    public static long toLong(String string)
+    {
+        long val = 0;
+        boolean started = false;
+        boolean minus = false;
+
+        for (int i = 0; i < string.length(); i++)
+        {
+            char b = string.charAt(i);
+            if (b <= ' ')
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val = val * 10L + (b - '0');
+                started = true;
+            }
+            else if (b == '-' && !started)
+            {
+                minus = true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+            return minus?(-val):val;
+        throw new NumberFormatException(string);
+    }
+    
+    /**
+     * Truncate a string to a max size.
+     * 
+     * @param str the string to possibly truncate
+     * @param maxSize the maximum size of the string
+     * @return the truncated string.  if <code>str</code> param is null, then the returned string will also be null.
+     */
+    public static String truncate(String str, int maxSize)
+    {
+        if (str == null)
+        {
+            return null;
+        }
+
+        if (str.length() <= maxSize)
+        {
+            return str;
+        }
+
+        return str.substring(0,maxSize);
+    }
+
+    public static String[] arrayFromString(String s) 
+    {
+        if (s==null)
+            return new String[]{};
+
+        if (!s.startsWith("[") || !s.endsWith("]"))
+            throw new IllegalArgumentException();
+        if (s.length()==2)
+            return new String[]{};
+
+        return s.substring(1,s.length()-1).split(" *, *");
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/TreeTrie.java b/lib/jetty/org/eclipse/jetty/util/TreeTrie.java
new file mode 100644 (file)
index 0000000..42f3bdc
--- /dev/null
@@ -0,0 +1,349 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/* ------------------------------------------------------------ */
+/** A Trie String lookup data structure using a tree
+ * <p>This implementation is always case insensitive and is optimal for
+ * a variable number of fixed strings with few special characters.
+ * </p>
+ * @param <V>
+ */
+public class TreeTrie<V> extends AbstractTrie<V>
+{
+    private static final int[] __lookup = 
+    { // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+   /*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+   /*1*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 30, 
+   /*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, -1, -1,
+   /*3*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28, 29, -1, -1, -1, -1,
+   /*4*/-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+   /*5*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+   /*6*/-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+   /*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+    };
+    private static final int INDEX = 32;
+    private final TreeTrie<V>[]  _nextIndex;
+    private final List<TreeTrie<V>> _nextOther=new ArrayList<>();
+    private final char _c;
+    private String _key;
+    private V _value;
+
+    public TreeTrie()
+    {
+        super(true);
+        _nextIndex = new TreeTrie[INDEX];
+        _c=0;
+    }
+    
+    private TreeTrie(char c)
+    {
+        super(true);
+        _nextIndex = new TreeTrie[INDEX];
+        this._c=c;
+    }
+
+    @Override
+    public boolean put(String s, V v)
+    {
+        TreeTrie<V> t = this;
+        int limit = s.length();
+        for(int k=0; k < limit; k++)
+        {
+            char c=s.charAt(k);
+            
+            int index=c>=0&&c<0x7f?__lookup[c]:-1;
+            if (index>=0)
+            {
+                if (t._nextIndex[index] == null)
+                    t._nextIndex[index] = new TreeTrie<V>(c);
+                t = t._nextIndex[index];
+            }
+            else
+            {
+                TreeTrie<V> n=null;
+                for (int i=t._nextOther.size();i-->0;)
+                {
+                    n=t._nextOther.get(i);
+                    if (n._c==c)
+                        break;
+                    n=null;
+                }
+                if (n==null)
+                {
+                    n=new TreeTrie<V>(c);
+                    t._nextOther.add(n);
+                }
+                t=n;
+            }
+        }
+        t._key=v==null?null:s;
+        t._value = v;
+        return true;
+    }
+
+    @Override
+    public V get(String s,int offset, int len)
+    {
+        TreeTrie<V> t = this;
+        for(int i=0; i < len; i++)
+        {
+            char c=s.charAt(offset+i);
+            int index=c>=0&&c<0x7f?__lookup[c]:-1;
+            if (index>=0)
+            {
+                if (t._nextIndex[index] == null) 
+                    return null;
+                t = t._nextIndex[index];
+            }
+            else
+            {
+                TreeTrie<V> n=null;
+                for (int j=t._nextOther.size();j-->0;)
+                {
+                    n=t._nextOther.get(j);
+                    if (n._c==c)
+                        break;
+                    n=null;
+                }
+                if (n==null)
+                    return null;
+                t=n;
+            }
+        }
+        return t._value;
+    }
+
+    @Override
+    public V get(ByteBuffer b,int offset,int len)
+    {
+        TreeTrie<V> t = this;
+        for(int i=0; i < len; i++)
+        {
+            byte c=b.get(offset+i);
+            int index=c>=0&&c<0x7f?__lookup[c]:-1;
+            if (index>=0)
+            {
+                if (t._nextIndex[index] == null) 
+                    return null;
+                t = t._nextIndex[index];
+            }
+            else
+            {
+                TreeTrie<V> n=null;
+                for (int j=t._nextOther.size();j-->0;)
+                {
+                    n=t._nextOther.get(j);
+                    if (n._c==c)
+                        break;
+                    n=null;
+                }
+                if (n==null)
+                    return null;
+                t=n;
+            }
+        }
+        return t._value;
+    }
+
+    @Override
+    public V getBest(byte[] b,int offset,int len)
+    {
+        TreeTrie<V> t = this;
+        for(int i=0; i < len; i++)
+        {
+            byte c=b[offset+i];
+            int index=c>=0&&c<0x7f?__lookup[c]:-1;
+            if (index>=0)
+            {
+                if (t._nextIndex[index] == null) 
+                    break;
+                t = t._nextIndex[index];
+            }
+            else
+            {
+                TreeTrie<V> n=null;
+                for (int j=t._nextOther.size();j-->0;)
+                {
+                    n=t._nextOther.get(j);
+                    if (n._c==c)
+                        break;
+                    n=null;
+                }
+                if (n==null)
+                    break;
+                t=n;
+            }
+            
+            // Is the next Trie is a match
+            if (t._key!=null)
+            {
+                // Recurse so we can remember this possibility
+                V best=t.getBest(b,offset+i+1,len-i-1);
+                if (best!=null)
+                    return best;
+                break;
+            }
+        }
+        return t._value;
+    }
+
+    @Override
+    public V getBest(String s, int offset, int len)
+    {
+        // TODO inefficient
+        byte[] b=s.substring(offset,offset+len).getBytes(StandardCharsets.ISO_8859_1);
+        return getBest(b,0,b.length);
+    }
+    
+    @Override
+    public V getBest(ByteBuffer b,int offset,int len)
+    {
+        if (b.hasArray())
+            return getBest(b.array(),b.arrayOffset()+b.position()+offset,len);
+        return getBestByteBuffer(b,offset,len);
+    }
+    
+    private V getBestByteBuffer(ByteBuffer b,int offset,int len)
+    {
+        TreeTrie<V> t = this;
+        int pos=b.position()+offset;
+        for(int i=0; i < len; i++)
+        {
+            byte c=b.get(pos++);
+            int index=c>=0&&c<0x7f?__lookup[c]:-1;
+            if (index>=0)
+            {
+                if (t._nextIndex[index] == null) 
+                    break;
+                t = t._nextIndex[index];
+            }
+            else
+            {
+                TreeTrie<V> n=null;
+                for (int j=t._nextOther.size();j-->0;)
+                {
+                    n=t._nextOther.get(j);
+                    if (n._c==c)
+                        break;
+                    n=null;
+                }
+                if (n==null)
+                    break;
+                t=n;
+            }
+            
+            // Is the next Trie is a match
+            if (t._key!=null)
+            {
+                // Recurse so we can remember this possibility
+                V best=t.getBest(b,offset+i+1,len-i-1);
+                if (best!=null)
+                    return best;
+                break;
+            }
+        }
+        return t._value;
+    }
+    
+
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        toString(buf,this);
+        
+        if (buf.length()==0)
+            return "{}";
+        
+        buf.setCharAt(0,'{');
+        buf.append('}');
+        return buf.toString();
+    }
+
+    private static <V> void toString(Appendable out, TreeTrie<V> t)
+    {
+        if (t != null)
+        {
+            if (t._value!=null)
+            {
+                try
+                {
+                    out.append(',');
+                    out.append(t._key);
+                    out.append('=');
+                    out.append(t._value.toString());
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException(e);
+                }
+            }
+           
+            for(int i=0; i < INDEX; i++)
+            {
+                if (t._nextIndex[i] != null)
+                    toString(out,t._nextIndex[i]);
+            }
+            for (int i=t._nextOther.size();i-->0;)
+                toString(out,t._nextOther.get(i));
+        }           
+    }
+
+    @Override
+    public Set<String> keySet()
+    {
+        Set<String> keys = new HashSet<>();
+        keySet(keys,this);
+        return keys;
+    }
+    
+    private static <V> void keySet(Set<String> set, TreeTrie<V> t)
+    {
+        if (t != null)
+        {
+            if (t._key!=null)
+                set.add(t._key);
+           
+            for(int i=0; i < INDEX; i++)
+            {
+                if (t._nextIndex[i] != null)
+                    keySet(set,t._nextIndex[i]);
+            }
+            for (int i=t._nextOther.size();i-->0;)
+                keySet(set,t._nextOther.get(i));
+        }           
+    }
+    
+    @Override
+    public boolean isFull()
+    {
+        return false;
+    }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Trie.java b/lib/jetty/org/eclipse/jetty/util/Trie.java
new file mode 100644 (file)
index 0000000..9c74924
--- /dev/null
@@ -0,0 +1,124 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.ByteBuffer;
+import java.util.Set;
+
+
+/* ------------------------------------------------------------ */
+/** A Trie String lookup data structure.
+ * @param <V>
+ */
+public interface Trie<V>
+{
+    /* ------------------------------------------------------------ */
+    /** Put and entry into the Trie
+     * @param s The key for the entry
+     * @param v The value of the entry
+     * @return True if the Trie had capacity to add the field.
+     */
+    public boolean put(String s, V v);
+    
+    /* ------------------------------------------------------------ */
+    /** Put a value as both a key and a value.
+     * @param v The value and key
+     * @return True if the Trie had capacity to add the field.
+     */
+    public boolean put(V v);
+
+    /* ------------------------------------------------------------ */
+    public V remove(String s);
+
+    /* ------------------------------------------------------------ */
+    /** Get and exact match from a String key
+     * @param s The key
+     */
+    public V get(String s);
+
+    /* ------------------------------------------------------------ */
+    /** Get and exact match from a String key
+     * @param s The key
+     * @param offset The offset within the string of the key
+     * @param len the length of the key
+     */
+    public V get(String s,int offset,int len);
+
+    /* ------------------------------------------------------------ */
+    /** Get and exact match from a segment of a ByteBuufer as key
+     * @param b The buffer
+     * @return The value or null if not found
+     */
+    public V get(ByteBuffer b);
+
+    /* ------------------------------------------------------------ */
+    /** Get and exact match from a segment of a ByteBuufer as key
+     * @param b The buffer
+     * @param offset The offset within the buffer of the key
+     * @param len the length of the key
+     * @return The value or null if not found
+     */
+    public V get(ByteBuffer b,int offset,int len);
+    
+    /* ------------------------------------------------------------ */
+    /** Get the best match from key in a String.
+     * @param s The string
+     * @return The value or null if not found
+     */
+    public V getBest(String s);
+    
+    /* ------------------------------------------------------------ */
+    /** Get the best match from key in a String.
+     * @param s The string
+     * @param offset The offset within the string of the key
+     * @param len the length of the key
+     * @return The value or null if not found
+     */
+    public V getBest(String s,int offset,int len); 
+
+    /* ------------------------------------------------------------ */
+    /** Get the best match from key in a byte array.
+     * The key is assumed to by ISO_8859_1 characters.
+     * @param b The buffer
+     * @param offset The offset within the array of the key
+     * @param len the length of the key
+     * @return The value or null if not found
+     */
+    public V getBest(byte[] b,int offset,int len);
+
+    /* ------------------------------------------------------------ */
+    /** Get the best match from key in a byte buffer.
+     * The key is assumed to by ISO_8859_1 characters.
+     * @param b The buffer
+     * @param offset The offset within the buffer of the key
+     * @param len the length of the key
+     * @return The value or null if not found
+     */
+    public V getBest(ByteBuffer b,int offset,int len);
+    
+    /* ------------------------------------------------------------ */
+    public Set<String> keySet();
+
+    /* ------------------------------------------------------------ */
+    public boolean isFull();
+
+    /* ------------------------------------------------------------ */
+    public boolean isCaseInsensitive();
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/TypeUtil.java b/lib/jetty/org/eclipse/jetty/util/TypeUtil.java
new file mode 100644 (file)
index 0000000..b96a520
--- /dev/null
@@ -0,0 +1,647 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * TYPE Utilities.
+ * Provides various static utiltiy methods for manipulating types and their
+ * string representations.
+ *
+ * @since Jetty 4.1
+ */
+public class TypeUtil
+{
+    private static final Logger LOG = Log.getLogger(TypeUtil.class);
+    public static final Class<?>[] NO_ARGS = new Class[]{};
+    public static final int CR = '\015';
+    public static final int LF = '\012';
+
+    /* ------------------------------------------------------------ */
+    private static final HashMap<String, Class<?>> name2Class=new HashMap<>();
+    static
+    {
+        name2Class.put("boolean",java.lang.Boolean.TYPE);
+        name2Class.put("byte",java.lang.Byte.TYPE);
+        name2Class.put("char",java.lang.Character.TYPE);
+        name2Class.put("double",java.lang.Double.TYPE);
+        name2Class.put("float",java.lang.Float.TYPE);
+        name2Class.put("int",java.lang.Integer.TYPE);
+        name2Class.put("long",java.lang.Long.TYPE);
+        name2Class.put("short",java.lang.Short.TYPE);
+        name2Class.put("void",java.lang.Void.TYPE);
+
+        name2Class.put("java.lang.Boolean.TYPE",java.lang.Boolean.TYPE);
+        name2Class.put("java.lang.Byte.TYPE",java.lang.Byte.TYPE);
+        name2Class.put("java.lang.Character.TYPE",java.lang.Character.TYPE);
+        name2Class.put("java.lang.Double.TYPE",java.lang.Double.TYPE);
+        name2Class.put("java.lang.Float.TYPE",java.lang.Float.TYPE);
+        name2Class.put("java.lang.Integer.TYPE",java.lang.Integer.TYPE);
+        name2Class.put("java.lang.Long.TYPE",java.lang.Long.TYPE);
+        name2Class.put("java.lang.Short.TYPE",java.lang.Short.TYPE);
+        name2Class.put("java.lang.Void.TYPE",java.lang.Void.TYPE);
+
+        name2Class.put("java.lang.Boolean",java.lang.Boolean.class);
+        name2Class.put("java.lang.Byte",java.lang.Byte.class);
+        name2Class.put("java.lang.Character",java.lang.Character.class);
+        name2Class.put("java.lang.Double",java.lang.Double.class);
+        name2Class.put("java.lang.Float",java.lang.Float.class);
+        name2Class.put("java.lang.Integer",java.lang.Integer.class);
+        name2Class.put("java.lang.Long",java.lang.Long.class);
+        name2Class.put("java.lang.Short",java.lang.Short.class);
+
+        name2Class.put("Boolean",java.lang.Boolean.class);
+        name2Class.put("Byte",java.lang.Byte.class);
+        name2Class.put("Character",java.lang.Character.class);
+        name2Class.put("Double",java.lang.Double.class);
+        name2Class.put("Float",java.lang.Float.class);
+        name2Class.put("Integer",java.lang.Integer.class);
+        name2Class.put("Long",java.lang.Long.class);
+        name2Class.put("Short",java.lang.Short.class);
+
+        name2Class.put(null,java.lang.Void.TYPE);
+        name2Class.put("string",java.lang.String.class);
+        name2Class.put("String",java.lang.String.class);
+        name2Class.put("java.lang.String",java.lang.String.class);
+    }
+
+    /* ------------------------------------------------------------ */
+    private static final HashMap<Class<?>, String> class2Name=new HashMap<>();
+    static
+    {
+        class2Name.put(java.lang.Boolean.TYPE,"boolean");
+        class2Name.put(java.lang.Byte.TYPE,"byte");
+        class2Name.put(java.lang.Character.TYPE,"char");
+        class2Name.put(java.lang.Double.TYPE,"double");
+        class2Name.put(java.lang.Float.TYPE,"float");
+        class2Name.put(java.lang.Integer.TYPE,"int");
+        class2Name.put(java.lang.Long.TYPE,"long");
+        class2Name.put(java.lang.Short.TYPE,"short");
+        class2Name.put(java.lang.Void.TYPE,"void");
+
+        class2Name.put(java.lang.Boolean.class,"java.lang.Boolean");
+        class2Name.put(java.lang.Byte.class,"java.lang.Byte");
+        class2Name.put(java.lang.Character.class,"java.lang.Character");
+        class2Name.put(java.lang.Double.class,"java.lang.Double");
+        class2Name.put(java.lang.Float.class,"java.lang.Float");
+        class2Name.put(java.lang.Integer.class,"java.lang.Integer");
+        class2Name.put(java.lang.Long.class,"java.lang.Long");
+        class2Name.put(java.lang.Short.class,"java.lang.Short");
+
+        class2Name.put(null,"void");
+        class2Name.put(java.lang.String.class,"java.lang.String");
+    }
+
+    /* ------------------------------------------------------------ */
+    private static final HashMap<Class<?>, Method> class2Value=new HashMap<>();
+    static
+    {
+        try
+        {
+            Class<?>[] s ={java.lang.String.class};
+
+            class2Value.put(java.lang.Boolean.TYPE,
+                           java.lang.Boolean.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Byte.TYPE,
+                           java.lang.Byte.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Double.TYPE,
+                           java.lang.Double.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Float.TYPE,
+                           java.lang.Float.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Integer.TYPE,
+                           java.lang.Integer.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Long.TYPE,
+                           java.lang.Long.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Short.TYPE,
+                           java.lang.Short.class.getMethod("valueOf",s));
+
+            class2Value.put(java.lang.Boolean.class,
+                           java.lang.Boolean.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Byte.class,
+                           java.lang.Byte.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Double.class,
+                           java.lang.Double.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Float.class,
+                           java.lang.Float.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Integer.class,
+                           java.lang.Integer.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Long.class,
+                           java.lang.Long.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Short.class,
+                           java.lang.Short.class.getMethod("valueOf",s));
+        }
+        catch(Exception e)
+        {
+            throw new Error(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Array to List.
+     * <p>
+     * Works like {@link Arrays#asList(Object...)}, but handles null arrays.
+     * @return a list backed by the array.
+     */
+    public static <T> List<T> asList(T[] a)
+    {
+        if (a==null)
+            return Collections.emptyList();
+        return Arrays.asList(a);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Class from a canonical name for a type.
+     * @param name A class or type name.
+     * @return A class , which may be a primitive TYPE field..
+     */
+    public static Class<?> fromName(String name)
+    {
+        return name2Class.get(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Canonical name for a type.
+     * @param type A class , which may be a primitive TYPE field.
+     * @return Canonical name.
+     */
+    public static String toName(Class<?> type)
+    {
+        return class2Name.get(type);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert String value to instance.
+     * @param type The class of the instance, which may be a primitive TYPE field.
+     * @param value The value as a string.
+     * @return The value as an Object.
+     */
+    public static Object valueOf(Class<?> type, String value)
+    {
+        try
+        {
+            if (type.equals(java.lang.String.class))
+                return value;
+
+            Method m = class2Value.get(type);
+            if (m!=null)
+                return m.invoke(null, value);
+
+            if (type.equals(java.lang.Character.TYPE) ||
+                type.equals(java.lang.Character.class))
+                return value.charAt(0);
+
+            Constructor<?> c = type.getConstructor(java.lang.String.class);
+            return c.newInstance(value);
+        }
+        catch (NoSuchMethodException | IllegalAccessException | InstantiationException x)
+        {
+            LOG.ignore(x);
+        }
+        catch (InvocationTargetException x)
+        {
+            if (x.getTargetException() instanceof Error)
+                throw (Error)x.getTargetException();
+            LOG.ignore(x);
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert String value to instance.
+     * @param type classname or type (eg int)
+     * @param value The value as a string.
+     * @return The value as an Object.
+     */
+    public static Object valueOf(String type, String value)
+    {
+        return valueOf(fromName(type),value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Parse an int from a substring.
+     * Negative numbers are not handled.
+     * @param s String
+     * @param offset Offset within string
+     * @param length Length of integer or -1 for remainder of string
+     * @param base base of the integer
+     * @return the parsed integer
+     * @throws NumberFormatException if the string cannot be parsed
+     */
+    public static int parseInt(String s, int offset, int length, int base)
+        throws NumberFormatException
+    {
+        int value=0;
+
+        if (length<0)
+            length=s.length()-offset;
+
+        for (int i=0;i<length;i++)
+        {
+            char c=s.charAt(offset+i);
+
+            int digit=convertHexDigit((int)c);
+            if (digit<0 || digit>=base)
+                throw new NumberFormatException(s.substring(offset,offset+length));
+            value=value*base+digit;
+        }
+        return value;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Parse an int from a byte array of ascii characters.
+     * Negative numbers are not handled.
+     * @param b byte array
+     * @param offset Offset within string
+     * @param length Length of integer or -1 for remainder of string
+     * @param base base of the integer
+     * @return the parsed integer
+     * @throws NumberFormatException if the array cannot be parsed into an integer
+     */
+    public static int parseInt(byte[] b, int offset, int length, int base)
+        throws NumberFormatException
+    {
+        int value=0;
+
+        if (length<0)
+            length=b.length-offset;
+
+        for (int i=0;i<length;i++)
+        {
+            char c=(char)(0xff&b[offset+i]);
+
+            int digit=c-'0';
+            if (digit<0 || digit>=base || digit>=10)
+            {
+                digit=10+c-'A';
+                if (digit<10 || digit>=base)
+                    digit=10+c-'a';
+            }
+            if (digit<0 || digit>=base)
+                throw new NumberFormatException(new String(b,offset,length));
+            value=value*base+digit;
+        }
+        return value;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static byte[] parseBytes(String s, int base)
+    {
+        byte[] bytes=new byte[s.length()/2];
+        for (int i=0;i<s.length();i+=2)
+            bytes[i/2]=(byte)TypeUtil.parseInt(s,i,2,base);
+        return bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toString(byte[] bytes, int base)
+    {
+        StringBuilder buf = new StringBuilder();
+        for (byte b : bytes)
+        {
+            int bi=0xff&b;
+            int c='0'+(bi/base)%base;
+            if (c>'9')
+                c= 'a'+(c-'0'-10);
+            buf.append((char)c);
+            c='0'+bi%base;
+            if (c>'9')
+                c= 'a'+(c-'0'-10);
+            buf.append((char)c);
+        }
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param c An ASCII encoded character 0-9 a-f A-F
+     * @return The byte value of the character 0-16.
+     */
+    public static byte convertHexDigit( byte c )
+    {
+        byte b = (byte)((c & 0x1f) + ((c >> 6) * 0x19) - 0x10);
+        if (b<0 || b>15)
+            throw new NumberFormatException("!hex "+c);
+        return b;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param c An ASCII encoded character 0-9 a-f A-F
+     * @return The byte value of the character 0-16.
+     */
+    public static int convertHexDigit( int c )
+    {
+        int d= ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10);
+        if (d<0 || d>15)
+            throw new NumberFormatException("!hex "+c);
+        return d;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void toHex(byte b,Appendable buf)
+    {
+        try
+        {
+            int d=0xf&((0xF0&b)>>4);
+            buf.append((char)((d>9?('A'-10):'0')+d));
+            d=0xf&b;
+            buf.append((char)((d>9?('A'-10):'0')+d));
+        }
+        catch(IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void toHex(int value,Appendable buf) throws IOException
+    {
+        int d=0xf&((0xF0000000&value)>>28);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x0F000000&value)>>24);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x00F00000&value)>>20);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x000F0000&value)>>16);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x0000F000&value)>>12);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x00000F00&value)>>8);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x000000F0&value)>>4);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&value;
+        buf.append((char)((d>9?('A'-10):'0')+d));
+    
+        Integer.toString(0,36);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public static void toHex(long value,Appendable buf) throws IOException
+    {
+        toHex((int)(value>>32),buf);
+        toHex((int)value,buf);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toHexString(byte b)
+    {
+        return toHexString(new byte[]{b}, 0, 1);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toHexString(byte[] b)
+    {
+        return toHexString(b, 0, b.length);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toHexString(byte[] b,int offset,int length)
+    {
+        StringBuilder buf = new StringBuilder();
+        for (int i=offset;i<offset+length;i++)
+        {
+            int bi=0xff&b[i];
+            int c='0'+(bi/16)%16;
+            if (c>'9')
+                c= 'A'+(c-'0'-10);
+            buf.append((char)c);
+            c='0'+bi%16;
+            if (c>'9')
+                c= 'a'+(c-'0'-10);
+            buf.append((char)c);
+        }
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static byte[] fromHexString(String s)
+    {
+        if (s.length()%2!=0)
+            throw new IllegalArgumentException(s);
+        byte[] array = new byte[s.length()/2];
+        for (int i=0;i<array.length;i++)
+        {
+            int b = Integer.parseInt(s.substring(i*2,i*2+2),16);
+            array[i]=(byte)(0xff&b);
+        }
+        return array;
+    }
+
+
+    public static void dump(Class<?> c)
+    {
+        System.err.println("Dump: "+c);
+        dump(c.getClassLoader());
+    }
+
+    public static void dump(ClassLoader cl)
+    {
+        System.err.println("Dump Loaders:");
+        while(cl!=null)
+        {
+            System.err.println("  loader "+cl);
+            cl = cl.getParent();
+        }
+    }
+
+
+    public static Object call(Class<?> oClass, String methodName, Object obj, Object[] arg)
+       throws InvocationTargetException, NoSuchMethodException
+    {
+        // Lets just try all methods for now
+        for (Method method : oClass.getMethods())
+        {
+            if (!method.getName().equals(methodName))
+                continue;            
+            if (method.getParameterTypes().length != arg.length)
+                continue;
+            if (Modifier.isStatic(method.getModifiers()) != (obj == null))
+                continue;
+            if ((obj == null) && method.getDeclaringClass() != oClass)
+                continue;
+
+            try
+            {
+                return method.invoke(obj, arg);
+            }
+            catch (IllegalAccessException | IllegalArgumentException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+        
+        // Lets look for a method with optional arguments
+        Object[] args_with_opts=null;
+        
+        for (Method method : oClass.getMethods())
+        {
+            if (!method.getName().equals(methodName))
+                continue;            
+            if (method.getParameterTypes().length != arg.length+1)
+                continue;
+            if (!method.getParameterTypes()[arg.length].isArray())
+                continue;
+            if (Modifier.isStatic(method.getModifiers()) != (obj == null))
+                continue;
+            if ((obj == null) && method.getDeclaringClass() != oClass)
+                continue;
+
+            if (args_with_opts==null)
+                args_with_opts=ArrayUtil.addToArray(arg,new Object[]{},Object.class);
+            try
+            {
+                return method.invoke(obj, args_with_opts);
+            }
+            catch (IllegalAccessException | IllegalArgumentException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+        
+        
+        throw new NoSuchMethodException(methodName);
+    }
+
+    public static Object construct(Class<?> klass, Object[] arguments) throws InvocationTargetException, NoSuchMethodException
+    {
+        for (Constructor<?> constructor : klass.getConstructors())
+        {
+            if (constructor.getParameterTypes().length != arguments.length)
+                continue;
+
+            try
+            {
+                return constructor.newInstance(arguments);
+            }
+            catch (InstantiationException | IllegalAccessException | IllegalArgumentException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+        throw new NoSuchMethodException("<init>");
+    }
+    
+    public static Object construct(Class<?> klass, Object[] arguments, Map<String, Object> namedArgMap) throws InvocationTargetException, NoSuchMethodException
+    {
+        for (Constructor<?> constructor : klass.getConstructors())
+        {
+            if (constructor.getParameterTypes().length != arguments.length)
+                continue;
+
+            try
+            {
+                Annotation[][] parameterAnnotations = constructor.getParameterAnnotations();
+                
+                // target has no annotations
+                if ( parameterAnnotations == null || parameterAnnotations.length == 0 )
+                {                
+                    LOG.debug("Target has no parameter annotations");                   
+                    return constructor.newInstance(arguments);
+                }
+                else
+                {
+                   Object[] swizzled = new Object[arguments.length];
+                   
+                   int count = 0;
+                   for ( Annotation[] annotations : parameterAnnotations )
+                   {
+                       for ( Annotation annotation : annotations)
+                       {
+                           if ( annotation instanceof Name )
+                           {
+                               Name param = (Name)annotation;
+                               
+                               if (namedArgMap.containsKey(param.value()))
+                               {
+                                   LOG.debug("placing named {} in position {}", param.value(), count);
+                                   swizzled[count] = namedArgMap.get(param.value());
+                               }
+                               else
+                               {
+                                   LOG.debug("placing {} in position {}", arguments[count], count);
+                                   swizzled[count] = arguments[count];
+                               }
+                               ++count;
+                           }
+                           else
+                           {
+                               LOG.debug("passing on annotation {}", annotation);
+                           }
+                       }
+                   }
+                   
+                   return constructor.newInstance(swizzled);
+                }
+                
+            }
+            catch (InstantiationException | IllegalAccessException | IllegalArgumentException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+        throw new NoSuchMethodException("<init>");
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param o Object to test for true
+     * @return True if passed object is not null and is either a Boolean with value true or evaluates to a string that evaluates to true.
+     */
+    public static boolean isTrue(Object o)
+    {
+        if (o==null)
+            return false;
+        if (o instanceof Boolean)
+            return ((Boolean)o).booleanValue();
+        return Boolean.parseBoolean(o.toString());
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param o Object to test for false
+     * @return True if passed object is not null and is either a Boolean with value false or evaluates to a string that evaluates to false.
+     */
+    public static boolean isFalse(Object o)
+    {
+        if (o==null)
+            return false;
+        if (o instanceof Boolean)
+            return !((Boolean)o).booleanValue();
+        return "false".equalsIgnoreCase(o.toString());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/URIUtil.java b/lib/jetty/org/eclipse/jetty/util/URIUtil.java
new file mode 100644 (file)
index 0000000..f333fc3
--- /dev/null
@@ -0,0 +1,694 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+
+
+/* ------------------------------------------------------------ */
+/** URI Holder.
+ * This class assists with the decoding and encoding or HTTP URI's.
+ * It differs from the java.net.URL class as it does not provide
+ * communications ability, but it does assist with query string
+ * formatting.
+ * <P>UTF-8 encoding is used by default for % encoded characters. This
+ * may be overridden with the org.eclipse.jetty.util.URI.charset system property.
+ * @see UrlEncoded
+ * 
+ */
+public class URIUtil
+    implements Cloneable
+{
+    public static final String SLASH="/";
+    public static final String HTTP="http";
+    public static final String HTTP_COLON="http:";
+    public static final String HTTPS="https";
+    public static final String HTTPS_COLON="https:";
+
+    // Use UTF-8 as per http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars
+    public static final Charset __CHARSET;
+
+    static
+    {
+        String charset = System.getProperty("org.eclipse.jetty.util.URI.charset");
+        __CHARSET = charset == null ? StandardCharsets.UTF_8 : Charset.forName(charset);
+    }
+
+    private URIUtil()
+    {}
+    
+    /* ------------------------------------------------------------ */
+    /** Encode a URI path.
+     * This is the same encoding offered by URLEncoder, except that
+     * the '/' character is not encoded.
+     * @param path The path the encode
+     * @return The encoded path
+     */
+    public static String encodePath(String path)
+    {
+        if (path==null || path.length()==0)
+            return path;
+        
+        StringBuilder buf = encodePath(null,path);
+        return buf==null?path:buf.toString();
+    }
+        
+    /* ------------------------------------------------------------ */
+    /** Encode a URI path.
+     * @param path The path the encode
+     * @param buf StringBuilder to encode path into (or null)
+     * @return The StringBuilder or null if no substitutions required.
+     */
+    public static StringBuilder encodePath(StringBuilder buf, String path)
+    {
+        byte[] bytes=null;
+        if (buf==null)
+        {
+        loop:
+            for (int i=0;i<path.length();i++)
+            {
+                char c=path.charAt(i);
+                switch(c)
+                {
+                    case '%':
+                    case '?':
+                    case ';':
+                    case '#':
+                    case '\'':
+                    case '"':
+                    case '<':
+                    case '>':
+                    case ' ':
+                        buf=new StringBuilder(path.length()*2);
+                        break loop;
+                    default:
+                        if (c>127)
+                        {
+                            bytes=path.getBytes(URIUtil.__CHARSET);
+                            buf=new StringBuilder(path.length()*2);
+                            break loop;
+                        }
+                       
+                }
+            }
+            if (buf==null)
+                return null;
+        }
+        
+        synchronized(buf)
+        {
+            if (bytes!=null)
+            {
+                for (int i=0;i<bytes.length;i++)
+                {
+                    byte c=bytes[i];       
+                    switch(c)
+                    {
+                      case '%':
+                          buf.append("%25");
+                          continue;
+                      case '?':
+                          buf.append("%3F");
+                          continue;
+                      case ';':
+                          buf.append("%3B");
+                          continue;
+                      case '#':
+                          buf.append("%23");
+                          continue;
+                      case '"':
+                          buf.append("%22");
+                          continue;
+                      case '\'':
+                          buf.append("%27");
+                          continue;
+                      case '<':
+                          buf.append("%3C");
+                          continue;
+                      case '>':
+                          buf.append("%3E");
+                          continue;
+                      case ' ':
+                          buf.append("%20");
+                          continue;
+                      default:
+                          if (c<0)
+                          {
+                              buf.append('%');
+                              TypeUtil.toHex(c,buf);
+                          }
+                          else
+                              buf.append((char)c);
+                          continue;
+                    }
+                }
+                
+            }
+            else
+            {
+                for (int i=0;i<path.length();i++)
+                {
+                    char c=path.charAt(i);       
+                    switch(c)
+                    {
+                        case '%':
+                            buf.append("%25");
+                            continue;
+                        case '?':
+                            buf.append("%3F");
+                            continue;
+                        case ';':
+                            buf.append("%3B");
+                            continue;
+                        case '#':
+                            buf.append("%23");
+                            continue;
+                        case '"':
+                            buf.append("%22");
+                            continue;
+                        case '\'':
+                            buf.append("%27");
+                            continue;
+                        case '<':
+                            buf.append("%3C");
+                            continue;
+                        case '>':
+                            buf.append("%3E");
+                            continue;
+                        case ' ':
+                            buf.append("%20");
+                            continue;
+                        default:
+                            buf.append(c);
+                            continue;
+                    }
+                }
+            }
+        }
+
+        return buf;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Encode a URI path.
+     * @param path The path the encode
+     * @param buf StringBuilder to encode path into (or null)
+     * @param encode String of characters to encode. % is always encoded.
+     * @return The StringBuilder or null if no substitutions required.
+     */
+    public static StringBuilder encodeString(StringBuilder buf,
+                                             String path,
+                                             String encode)
+    {
+        if (buf==null)
+        {
+        loop:
+            for (int i=0;i<path.length();i++)
+            {
+                char c=path.charAt(i);
+                if (c=='%' || encode.indexOf(c)>=0)
+                {    
+                    buf=new StringBuilder(path.length()<<1);
+                    break loop;
+                }
+            }
+            if (buf==null)
+                return null;
+        }
+        
+        synchronized(buf)
+        {
+            for (int i=0;i<path.length();i++)
+            {
+                char c=path.charAt(i);
+                if (c=='%' || encode.indexOf(c)>=0)
+                {
+                    buf.append('%');
+                    StringUtil.append(buf,(byte)(0xff&c),16);
+                }
+                else
+                    buf.append(c);
+            }
+        }
+
+        return buf;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* Decode a URI path and strip parameters
+     * @param path The path the encode
+     * @param buf StringBuilder to encode path into
+     */
+    public static String decodePath(String path)
+    {
+        if (path==null)
+            return null;
+        // Array to hold all converted characters
+        char[] chars=null;
+        int n=0;
+        // Array to hold a sequence of %encodings
+        byte[] bytes=null;
+        int b=0;
+        
+        int len=path.length();
+        
+        for (int i=0;i<len;i++)
+        {
+            char c = path.charAt(i);
+
+            if (c=='%' && (i+2)<len)
+            {
+                if (chars==null)
+                {
+                    chars=new char[len];
+                    bytes=new byte[len];
+                    path.getChars(0,i,chars,0);
+                }
+                bytes[b++]=(byte)(0xff&TypeUtil.parseInt(path,i+1,2,16));
+                i+=2;
+                continue;
+            }
+            else if (c==';')
+            {
+                if (chars==null)
+                {
+                    chars=new char[len];
+                    path.getChars(0,i,chars,0);
+                    n=i;
+                }
+                break;
+            }
+            else if (bytes==null)
+            {
+                n++;
+                continue;
+            }
+            
+            // Do we have some bytes to convert?
+            if (b>0)
+            {
+                String s=new String(bytes,0,b,__CHARSET);
+                s.getChars(0,s.length(),chars,n);
+                n+=s.length();
+                b=0;
+            }
+            
+            chars[n++]=c;
+        }
+
+        if (chars==null)
+            return path;
+
+        // if we have a remaining sequence of bytes
+        if (b>0)
+        {
+            String s=new String(bytes,0,b,__CHARSET);
+            s.getChars(0,s.length(),chars,n);
+            n+=s.length();
+        }
+        
+        return new String(chars,0,n);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* Decode a URI path and strip parameters.
+     * @param path The path the encode
+     * @param buf StringBuilder to encode path into
+     */
+    public static String decodePath(byte[] buf, int offset, int length)
+    {
+        byte[] bytes=null;
+        int n=0;
+        
+        for (int i=0;i<length;i++)
+        {
+            byte b = buf[i + offset];
+            
+            if (b=='%' && (i+2)<length)
+            {
+                b=(byte)(0xff&TypeUtil.parseInt(buf,i+offset+1,2,16));
+                i+=2;
+            }
+            else if (b==';')
+            {
+                length=i;
+                break;
+            }
+            else if (bytes==null)
+            {
+                n++;
+                continue;
+            }
+            
+            if (bytes==null)
+            {
+                bytes=new byte[length];
+                for (int j=0;j<n;j++)
+                    bytes[j]=buf[j + offset];
+            }
+            
+            bytes[n++]=b;
+        }
+
+        if (bytes==null)
+            return new String(buf,offset,length,__CHARSET);
+        return new String(bytes,0,n,__CHARSET);
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /** Add two URI path segments.
+     * Handles null and empty paths, path and query params (eg ?a=b or
+     * ;JSESSIONID=xxx) and avoids duplicate '/'
+     * @param p1 URI path segment (should be encoded)
+     * @param p2 URI path segment (should be encoded)
+     * @return Legally combined path segments.
+     */
+    public static String addPaths(String p1, String p2)
+    {
+        if (p1==null || p1.length()==0)
+        {
+            if (p1!=null && p2==null)
+                return p1;
+            return p2;
+        }
+        if (p2==null || p2.length()==0)
+            return p1;
+        
+        int split=p1.indexOf(';');
+        if (split<0)
+            split=p1.indexOf('?');
+        if (split==0)
+            return p2+p1;
+        if (split<0)
+            split=p1.length();
+
+        StringBuilder buf = new StringBuilder(p1.length()+p2.length()+2);
+        buf.append(p1);
+        
+        if (buf.charAt(split-1)=='/')
+        {
+            if (p2.startsWith(URIUtil.SLASH))
+            {
+                buf.deleteCharAt(split-1);
+                buf.insert(split-1,p2);
+            }
+            else
+                buf.insert(split,p2);
+        }
+        else
+        {
+            if (p2.startsWith(URIUtil.SLASH))
+                buf.insert(split,p2);
+            else
+            {
+                buf.insert(split,'/');
+                buf.insert(split+1,p2);
+            }
+        }
+
+        return buf.toString();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Return the parent Path.
+     * Treat a URI like a directory path and return the parent directory.
+     */
+    public static String parentPath(String p)
+    {
+        if (p==null || URIUtil.SLASH.equals(p))
+            return null;
+        int slash=p.lastIndexOf('/',p.length()-2);
+        if (slash>=0)
+            return p.substring(0,slash+1);
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convert a path to a cananonical form.
+     * All instances of "." and ".." are factored out.  Null is returned
+     * if the path tries to .. above its root.
+     * @param path 
+     * @return path or null.
+     */
+    public static String canonicalPath(String path)
+    {
+        if (path==null || path.length()==0)
+            return path;
+
+        int end=path.length();
+        int start = path.lastIndexOf('/', end);
+
+    search:
+        while (end>0)
+        {
+            switch(end-start)
+            {
+              case 2: // possible single dot
+                  if (path.charAt(start+1)!='.')
+                      break;
+                  break search;
+              case 3: // possible double dot
+                  if (path.charAt(start+1)!='.' || path.charAt(start+2)!='.')
+                      break;
+                  break search;
+            }
+            
+            end=start;
+            start=path.lastIndexOf('/',end-1);
+        }
+
+        // If we have checked the entire string
+        if (start>=end)
+            return path;
+        
+        StringBuilder buf = new StringBuilder(path);
+        int delStart=-1;
+        int delEnd=-1;
+        int skip=0;
+        
+        while (end>0)
+        {
+            switch(end-start)
+            {       
+              case 2: // possible single dot
+                  if (buf.charAt(start+1)!='.')
+                  {
+                      if (skip>0 && --skip==0)
+                      {   
+                          delStart=start>=0?start:0;
+                          if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
+                              delStart++;
+                      }
+                      break;
+                  }
+                  
+                  if(start<0 && buf.length()>2 && buf.charAt(1)=='/' && buf.charAt(2)=='/')
+                      break;
+                  
+                  if(delEnd<0)
+                      delEnd=end;
+                  delStart=start;
+                  if (delStart<0 || delStart==0&&buf.charAt(delStart)=='/')
+                  {
+                      delStart++;
+                      if (delEnd<buf.length() && buf.charAt(delEnd)=='/')
+                          delEnd++;
+                      break;
+                  }
+                  if (end==buf.length())
+                      delStart++;
+                  
+                  end=start--;
+                  while (start>=0 && buf.charAt(start)!='/')
+                      start--;
+                  continue;
+                  
+              case 3: // possible double dot
+                  if (buf.charAt(start+1)!='.' || buf.charAt(start+2)!='.')
+                  {
+                      if (skip>0 && --skip==0)
+                      {   delStart=start>=0?start:0;
+                          if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
+                              delStart++;
+                      }
+                      break;
+                  }
+                  
+                  delStart=start;
+                  if (delEnd<0)
+                      delEnd=end;
+
+                  skip++;
+                  end=start--;
+                  while (start>=0 && buf.charAt(start)!='/')
+                      start--;
+                  continue;
+
+              default:
+                  if (skip>0 && --skip==0)
+                  {
+                      delStart=start>=0?start:0;
+                      if(delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
+                          delStart++;
+                  }
+            }     
+            
+            // Do the delete
+            if (skip<=0 && delStart>=0 && delEnd>=delStart)
+            {  
+                buf.delete(delStart,delEnd);
+                delStart=delEnd=-1;
+                if (skip>0)
+                    delEnd=end;
+            }
+            
+            end=start--;
+            while (start>=0 && buf.charAt(start)!='/')
+                start--;
+        }      
+
+        // Too many ..
+        if (skip>0)
+            return null;
+        
+        // Do the delete
+        if (delEnd>=0)
+            buf.delete(delStart,delEnd);
+
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert a path to a compact form.
+     * All instances of "//" and "///" etc. are factored out to single "/" 
+     * @param path 
+     * @return path
+     */
+    public static String compactPath(String path)
+    {
+        if (path==null || path.length()==0)
+            return path;
+
+        int state=0;
+        int end=path.length();
+        int i=0;
+        
+        loop:
+        while (i<end)
+        {
+            char c=path.charAt(i);
+            switch(c)
+            {
+                case '?':
+                    return path;
+                case '/':
+                    state++;
+                    if (state==2)
+                        break loop;
+                    break;
+                default:
+                    state=0;
+            }
+            i++;
+        }
+        
+        if (state<2)
+            return path;
+        
+        StringBuffer buf = new StringBuffer(path.length());
+        buf.append(path,0,i);
+        
+        loop2:
+        while (i<end)
+        {
+            char c=path.charAt(i);
+            switch(c)
+            {
+                case '?':
+                    buf.append(path,i,end);
+                    break loop2;
+                case '/':
+                    if (state++==0)
+                        buf.append(c);
+                    break;
+                default:
+                    state=0;
+                    buf.append(c);
+            }
+            i++;
+        }
+        
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param uri URI
+     * @return True if the uri has a scheme
+     */
+    public static boolean hasScheme(String uri)
+    {
+        for (int i=0;i<uri.length();i++)
+        {
+            char c=uri.charAt(i);
+            if (c==':')
+                return true;
+            if (!(c>='a'&&c<='z' ||
+                  c>='A'&&c<='Z' ||
+                  (i>0 &&(c>='0'&&c<='9' ||
+                          c=='.' ||
+                          c=='+' ||
+                          c=='-'))
+                  ))
+                break;
+        }
+        return false;
+    }
+    
+    public static void appendSchemeHostPort(StringBuilder url,String scheme,String server, int port)
+    {
+        if (server.indexOf(':')>=0&&server.charAt(0)!='[')
+            url.append(scheme).append("://").append('[').append(server).append(']');
+        else
+            url.append(scheme).append("://").append(server);
+
+        if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
+            url.append(':').append(port);
+    }
+    
+    public static void appendSchemeHostPort(StringBuffer url,String scheme,String server, int port)
+    {
+        synchronized (url)
+        {
+            if (server.indexOf(':')>=0&&server.charAt(0)!='[')
+                url.append(scheme).append("://").append('[').append(server).append(']');
+            else
+                url.append(scheme).append("://").append(server);
+
+            if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
+                url.append(':').append(port);
+        }
+    }
+}
+
+
+
diff --git a/lib/jetty/org/eclipse/jetty/util/UrlEncoded.java b/lib/jetty/org/eclipse/jetty/util/UrlEncoded.java
new file mode 100644 (file)
index 0000000..ef7f2f5
--- /dev/null
@@ -0,0 +1,1064 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import static org.eclipse.jetty.util.TypeUtil.convertHexDigit;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Handles coding of MIME  "x-www-form-urlencoded".
+ * <p>
+ * This class handles the encoding and decoding for either
+ * the query string of a URL or the _content of a POST HTTP request.
+ *
+ * <h4>Notes</h4>
+ * The UTF-8 charset is assumed, unless otherwise defined by either
+ * passing a parameter or setting the "org.eclipse.jetty.util.UrlEncoding.charset"
+ * System property.
+ * <p>
+ * The hashtable either contains String single values, vectors
+ * of String or arrays of Strings.
+ * <p>
+ * This class is only partially synchronised.  In particular, simple
+ * get operations are not protected from concurrent updates.
+ *
+ * @see java.net.URLEncoder
+ */
+@SuppressWarnings("serial")
+public class UrlEncoded extends MultiMap<String> implements Cloneable
+{
+    static final Logger LOG = Log.getLogger(UrlEncoded.class);
+
+    public static final Charset ENCODING;
+    static
+    {
+        Charset encoding;
+        try
+        {
+            String charset = System.getProperty("org.eclipse.jetty.util.UrlEncoding.charset");
+            encoding = charset == null ? StandardCharsets.UTF_8 : Charset.forName(charset);
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+            encoding=StandardCharsets.UTF_8;
+        }
+        ENCODING=encoding;
+    }
+    
+    /* ----------------------------------------------------------------- */
+    public UrlEncoded(UrlEncoded url)
+    {
+        super(url);
+    }
+    
+    /* ----------------------------------------------------------------- */
+    public UrlEncoded()
+    {
+    }
+    
+    public UrlEncoded(String query)
+    {
+        decodeTo(query,this,ENCODING,-1);
+    }
+
+    /* ----------------------------------------------------------------- */
+    public void decode(String query)
+    {
+        decodeTo(query,this,ENCODING,-1);
+    }
+    
+    /* ----------------------------------------------------------------- */
+    public void decode(String query,Charset charset)
+    {
+        decodeTo(query,this,charset,-1);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Encode Hashtable with % encoding.
+     */
+    public String encode()
+    {
+        return encode(ENCODING,false);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Encode Hashtable with % encoding.
+     */
+    public String encode(Charset charset)
+    {
+        return encode(charset,false);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Encode Hashtable with % encoding.
+     * @param equalsForNullValue if True, then an '=' is always used, even
+     * for parameters without a value. e.g. "blah?a=&b=&c=".
+     */
+    public synchronized String encode(Charset charset, boolean equalsForNullValue)
+    {
+        return encode(this,charset,equalsForNullValue);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Encode Hashtable with % encoding.
+     * @param equalsForNullValue if True, then an '=' is always used, even
+     * for parameters without a value. e.g. "blah?a=&b=&c=".
+     */
+    public static String encode(MultiMap<String> map, Charset charset, boolean equalsForNullValue)
+    {
+        if (charset==null)
+            charset=ENCODING;
+
+        StringBuilder result = new StringBuilder(128);
+
+        boolean delim = false;
+        for(Map.Entry<String, List<String>> entry: map.entrySet())
+        {
+            String key = entry.getKey().toString();
+            List<String> list = entry.getValue();
+            int s=list.size();
+            
+            if (delim)
+            {
+                result.append('&');
+            }
+
+            if (s==0)
+            {
+                result.append(encodeString(key,charset));
+                if(equalsForNullValue)
+                    result.append('=');
+            }
+            else
+            {
+                for (int i=0;i<s;i++)
+                {
+                    if (i>0)
+                        result.append('&');
+                    String val=list.get(i);
+                    result.append(encodeString(key,charset));
+
+                    if (val!=null)
+                    {
+                        String str=val.toString();
+                        if (str.length()>0)
+                        {
+                            result.append('=');
+                            result.append(encodeString(str,charset));
+                        }
+                        else if (equalsForNullValue)
+                            result.append('=');
+                    }
+                    else if (equalsForNullValue)
+                        result.append('=');
+                }
+            }
+            delim = true;
+        }
+        return result.toString();
+    }
+
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param content the string containing the encoded parameters
+     */
+    public static void decodeTo(String content, MultiMap<String> map, String charset, int maxKeys)
+    {
+        decodeTo(content,map,charset==null?null:Charset.forName(charset),maxKeys);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param content the string containing the encoded parameters
+     */
+    public static void decodeTo(String content, MultiMap<String> map, Charset charset, int maxKeys)
+    {
+        if (charset==null)
+            charset=ENCODING;
+
+        synchronized(map)
+        {
+            String key = null;
+            String value = null;
+            int mark=-1;
+            boolean encoded=false;
+            for (int i=0;i<content.length();i++)
+            {
+                char c = content.charAt(i);
+                switch (c)
+                {
+                  case '&':
+                      int l=i-mark-1;
+                      value = l==0?"":
+                          (encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1,i));
+                      mark=i;
+                      encoded=false;
+                      if (key != null)
+                      {
+                          map.add(key,value);
+                      }
+                      else if (value!=null&&value.length()>0)
+                      {
+                          map.add(value,"");
+                      }
+                      key = null;
+                      value=null;
+                      if (maxKeys>0 && map.size()>maxKeys)
+                          throw new IllegalStateException("Form too many keys");
+                      break;
+                  case '=':
+                      if (key!=null)
+                          break;
+                      key = encoded?decodeString(content,mark+1,i-mark-1,charset):content.substring(mark+1,i);
+                      mark=i;
+                      encoded=false;
+                      break;
+                  case '+':
+                      encoded=true;
+                      break;
+                  case '%':
+                      encoded=true;
+                      break;
+                }                
+            }
+            
+            if (key != null)
+            {
+                int l=content.length()-mark-1;
+                value = l==0?"":(encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1));
+                map.add(key,value);
+            }
+            else if (mark<content.length())
+            {
+                key = encoded
+                    ?decodeString(content,mark+1,content.length()-mark-1,charset)
+                    :content.substring(mark+1);
+                if (key != null && key.length() > 0)
+                {
+                    map.add(key,"");
+                }
+            }
+        }
+    }
+
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param raw the byte[] containing the encoded parameters
+     * @param offset the offset within raw to decode from
+     * @param length the length of the section to decode
+     * @param map the {@link MultiMap} to populate
+     */
+    public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap<String> map)
+    {
+        Utf8StringBuilder buffer = new Utf8StringBuilder();
+        synchronized(map)
+        {
+            String key = null;
+            String value = null;
+
+            int end=offset+length;
+            for (int i=offset;i<end;i++)
+            {
+                byte b=raw[i];
+                try
+                {
+                    switch ((char)(0xff&b))
+                    {
+                        case '&':
+                            value = buffer.toReplacedString();
+                            buffer.reset();
+                            if (key != null)
+                            {
+                                map.add(key,value);
+                            }
+                            else if (value!=null&&value.length()>0)
+                            {
+                                map.add(value,"");
+                            }
+                            key = null;
+                            value=null;
+                            break;
+
+                        case '=':
+                            if (key!=null)
+                            {
+                                buffer.append(b);
+                                break;
+                            }
+                            key = buffer.toReplacedString();
+                            buffer.reset();
+                            break;
+
+                        case '+':
+                            buffer.append((byte)' ');
+                            break;
+
+                        case '%':
+                            if (i+2<end)
+                            {
+                                if ('u'==raw[i+1])
+                                {
+                                    i++;
+                                    if (i+4<end)
+                                    {
+                                        byte top=raw[++i];
+                                        byte hi=raw[++i];
+                                        byte lo=raw[++i];
+                                        byte bot=raw[++i];
+                                        buffer.getStringBuilder().append(Character.toChars((convertHexDigit(top)<<12) +(convertHexDigit(hi)<<8) + (convertHexDigit(lo)<<4) +convertHexDigit(bot)));
+                                    }
+                                    else
+                                    {
+                                        buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
+                                        i=end;
+                                    }
+                                }
+                                else
+                                {
+                                    byte hi=raw[++i];
+                                    byte lo=raw[++i];
+                                    buffer.append((byte)((convertHexDigit(hi)<<4) + convertHexDigit(lo)));
+                                }
+                            }
+                            else
+                            {
+                                buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
+                                i=end;
+                            }
+                            break;
+                            
+                        default:
+                            buffer.append(b);
+                            break;
+                    }
+                }
+                catch(NotUtf8Exception e)
+                {
+                    LOG.warn(e.toString());
+                    LOG.debug(e);
+                }
+                catch(NumberFormatException e)
+                {
+                    buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3);
+                    LOG.warn(e.toString());
+                    LOG.debug(e);
+                }
+            }
+            
+            if (key != null)
+            {
+                value = buffer.toReplacedString();
+                buffer.reset();
+                map.add(key,value);
+            }
+            else if (buffer.length()>0)
+            {
+                map.add(buffer.toReplacedString(),"");
+            }
+        }
+    }
+
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param in InputSteam to read
+     * @param map MultiMap to add parameters to
+     * @param maxLength maximum number of keys to read or -1 for no limit
+     */
+    public static void decode88591To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
+    throws IOException
+    {
+        synchronized(map)
+        {
+            StringBuffer buffer = new StringBuffer();
+            String key = null;
+            String value = null;
+            
+            int b;
+
+            int totalLength=0;
+            while ((b=in.read())>=0)
+            {
+                switch ((char) b)
+                {
+                    case '&':
+                        value = buffer.length()==0?"":buffer.toString();
+                        buffer.setLength(0);
+                        if (key != null)
+                        {
+                            map.add(key,value);
+                        }
+                        else if (value!=null&&value.length()>0)
+                        {
+                            map.add(value,"");
+                        }
+                        key = null;
+                        value=null;
+                        if (maxKeys>0 && map.size()>maxKeys)
+                            throw new IllegalStateException("Form too many keys");
+                        break;
+                        
+                    case '=':
+                        if (key!=null)
+                        {
+                            buffer.append((char)b);
+                            break;
+                        }
+                        key = buffer.toString();
+                        buffer.setLength(0);
+                        break;
+                        
+                    case '+':
+                        buffer.append(' ');
+                        break;
+                        
+                    case '%':
+                        int code0=in.read();
+                        if ('u'==code0)
+                        {
+                            int code1=in.read();
+                            if (code1>=0)
+                            {
+                                int code2=in.read();
+                                if (code2>=0)
+                                {
+                                    int code3=in.read();
+                                    if (code3>=0)
+                                        buffer.append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
+                                }
+                            }
+                        }
+                        else if (code0>=0)
+                        {
+                            int code1=in.read();
+                            if (code1>=0)
+                                buffer.append((char)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
+                        }
+                        break;
+                     
+                    default:
+                        buffer.append((char)b);
+                    break;
+                }
+                if (maxLength>=0 && (++totalLength > maxLength))
+                    throw new IllegalStateException("Form too large");
+            }
+            
+            if (key != null)
+            {
+                value = buffer.length()==0?"":buffer.toString();
+                buffer.setLength(0);
+                map.add(key,value);
+            }
+            else if (buffer.length()>0)
+            {
+                map.add(buffer.toString(), "");
+            }
+        }
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param in InputSteam to read
+     * @param map MultiMap to add parameters to
+     * @param maxLength maximum number of keys to read or -1 for no limit
+     */
+    public static void decodeUtf8To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
+    throws IOException
+    {
+        synchronized(map)
+        {
+            Utf8StringBuilder buffer = new Utf8StringBuilder();
+            String key = null;
+            String value = null;
+            
+            int b;
+            
+            int totalLength=0;
+            while ((b=in.read())>=0)
+            {
+                try
+                {
+                    switch ((char) b)
+                    {
+                        case '&':
+                            value = buffer.toReplacedString();
+                            buffer.reset();
+                            if (key != null)
+                            {
+                                map.add(key,value);
+                            }
+                            else if (value!=null&&value.length()>0)
+                            {
+                                map.add(value,"");
+                            }
+                            key = null;
+                            value=null;
+                            if (maxKeys>0 && map.size()>maxKeys)
+                                throw new IllegalStateException("Form too many keys");
+                            break;
+
+                        case '=':
+                            if (key!=null)
+                            {
+                                buffer.append((byte)b);
+                                break;
+                            }
+                            key = buffer.toReplacedString(); 
+                            buffer.reset();
+                            break;
+
+                        case '+':
+                            buffer.append((byte)' ');
+                            break;
+
+                        case '%':
+                            int code0=in.read();
+                            boolean decoded=false;
+                            if ('u'==code0)
+                            {
+                                code0=in.read(); // XXX: we have to read the next byte, otherwise code0 is always 'u'
+                                if (code0>=0)
+                                {
+                                    int code1=in.read();
+                                    if (code1>=0)
+                                    {
+                                        int code2=in.read();
+                                        if (code2>=0)
+                                        {
+                                            int code3=in.read();
+                                            if (code3>=0)
+                                            {
+                                                buffer.getStringBuilder().append(Character.toChars
+                                                    ((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
+                                                decoded=true;
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                            else if (code0>=0)
+                            {
+                                int code1=in.read();
+                                if (code1>=0)
+                                {
+                                    buffer.append((byte)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
+                                    decoded=true;
+                                }
+                            }
+                            
+                            if (!decoded)
+                                buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
+
+                            break;
+                          
+                        default:
+                            buffer.append((byte)b);
+                            break;
+                    }
+                }
+                catch(NotUtf8Exception e)
+                {
+                    LOG.warn(e.toString());
+                    LOG.debug(e);
+                }
+                catch(NumberFormatException e)
+                {
+                    buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3);
+                    LOG.warn(e.toString());
+                    LOG.debug(e);
+                }
+                if (maxLength>=0 && (++totalLength > maxLength))
+                    throw new IllegalStateException("Form too large");
+            }
+            
+            if (key != null)
+            {
+                value = buffer.toReplacedString();
+                buffer.reset();
+                map.add(key,value);
+            }
+            else if (buffer.length()>0)
+            {
+                map.add(buffer.toReplacedString(), "");
+            }
+        }
+    }
+    
+    /* -------------------------------------------------------------- */
+    public static void decodeUtf16To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys) throws IOException
+    {
+        InputStreamReader input = new InputStreamReader(in,StandardCharsets.UTF_16);
+        StringWriter buf = new StringWriter(8192);
+        IO.copy(input,buf,maxLength);
+        
+        decodeTo(buf.getBuffer().toString(),map,StandardCharsets.UTF_16,maxKeys);
+    }
+
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param in the stream containing the encoded parameters
+     */
+    public static void decodeTo(InputStream in, MultiMap<String> map, String charset, int maxLength, int maxKeys)
+    throws IOException
+    {
+        if (charset==null)
+        {
+            if (ENCODING.equals(StandardCharsets.UTF_8))
+                decodeUtf8To(in,map,maxLength,maxKeys);
+            else
+                decodeTo(in,map,ENCODING,maxLength,maxKeys);
+        }
+        else if (StringUtil.__UTF8.equalsIgnoreCase(charset))
+            decodeUtf8To(in,map,maxLength,maxKeys);
+        else if (StringUtil.__ISO_8859_1.equalsIgnoreCase(charset))
+            decode88591To(in,map,maxLength,maxKeys);
+        else if (StringUtil.__UTF16.equalsIgnoreCase(charset))
+            decodeUtf16To(in,map,maxLength,maxKeys);
+        else
+            decodeTo(in,map,Charset.forName(charset),maxLength,maxKeys);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param in the stream containing the encoded parameters
+     */
+    public static void decodeTo(InputStream in, MultiMap<String> map, Charset charset, int maxLength, int maxKeys)
+    throws IOException
+    {
+        //no charset present, use the configured default
+        if (charset==null) 
+           charset=ENCODING;
+            
+        if (StandardCharsets.UTF_8.equals(charset))
+        {
+            decodeUtf8To(in,map,maxLength,maxKeys);
+            return;
+        }
+        
+        if (StandardCharsets.ISO_8859_1.equals(charset))
+        {
+            decode88591To(in,map,maxLength,maxKeys);
+            return;
+        }
+
+        if (StandardCharsets.UTF_16.equals(charset)) // Should be all 2 byte encodings
+        {
+            decodeUtf16To(in,map,maxLength,maxKeys);
+            return;
+        }
+
+        synchronized(map)
+        {
+            String key = null;
+            String value = null;
+            
+            int c;
+            
+            int totalLength = 0;
+            ByteArrayOutputStream2 output = new ByteArrayOutputStream2();
+            
+            int size=0;
+            
+            while ((c=in.read())>0)
+            {
+                switch ((char) c)
+                {
+                    case '&':
+                        size=output.size();
+                        value = size==0?"":output.toString(charset);
+                        output.setCount(0);
+                        if (key != null)
+                        {
+                            map.add(key,value);
+                        }
+                        else if (value!=null&&value.length()>0)
+                        {
+                            map.add(value,"");
+                        }
+                        key = null;
+                        value=null;
+                        if (maxKeys>0 && map.size()>maxKeys)
+                            throw new IllegalStateException("Form too many keys");
+                        break;
+                    case '=':
+                        if (key!=null)
+                        {
+                            output.write(c);
+                            break;
+                        }
+                        size=output.size();
+                        key = size==0?"":output.toString(charset);
+                        output.setCount(0);
+                        break;
+                    case '+':
+                        output.write(' ');
+                        break;
+                    case '%':
+                        int code0=in.read();
+                        if ('u'==code0)
+                        {
+                            int code1=in.read();
+                            if (code1>=0)
+                            {
+                                int code2=in.read();
+                                if (code2>=0)
+                                {
+                                    int code3=in.read();
+                                    if (code3>=0)
+                                        output.write(new String(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))).getBytes(charset));
+                                }
+                            }
+                            
+                        }
+                        else if (code0>=0)
+                        {
+                            int code1=in.read();
+                            if (code1>=0)
+                                output.write((convertHexDigit(code0)<<4)+convertHexDigit(code1));
+                        }
+                        break;
+                    default:
+                        output.write(c);
+                    break;
+                }
+                
+                totalLength++;
+                if (maxLength>=0 && totalLength > maxLength)
+                    throw new IllegalStateException("Form too large");
+            }
+
+            size=output.size();
+            if (key != null)
+            {
+                value = size==0?"":output.toString(charset);
+                output.setCount(0);
+                map.add(key,value);
+            }
+            else if (size>0)
+                map.add(output.toString(charset),"");
+        }
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Decode String with % encoding.
+     * This method makes the assumption that the majority of calls
+     * will need no decoding.
+     */
+    public static String decodeString(String encoded,int offset,int length,Charset charset)
+    {
+        if (charset==null || StandardCharsets.UTF_8.equals(charset))
+        {
+            Utf8StringBuffer buffer=null;
+
+            for (int i=0;i<length;i++)
+            {
+                char c = encoded.charAt(offset+i);
+                if (c<0||c>0xff)
+                {
+                    if (buffer==null)
+                    {
+                        buffer=new Utf8StringBuffer(length);
+                        buffer.getStringBuffer().append(encoded,offset,offset+i+1);
+                    }
+                    else
+                        buffer.getStringBuffer().append(c);
+                }
+                else if (c=='+')
+                {
+                    if (buffer==null)
+                    {
+                        buffer=new Utf8StringBuffer(length);
+                        buffer.getStringBuffer().append(encoded,offset,offset+i);
+                    }
+                    
+                    buffer.getStringBuffer().append(' ');
+                }
+                else if (c=='%')
+                {
+                    if (buffer==null)
+                    {
+                        buffer=new Utf8StringBuffer(length);
+                        buffer.getStringBuffer().append(encoded,offset,offset+i);
+                    }
+                    
+                    if ((i+2)<length)
+                    {
+                        try
+                        {
+                            if ('u'==encoded.charAt(offset+i+1))
+                            {
+                                if((i+5)<length)
+                                {
+                                    int o=offset+i+2;
+                                    i+=5;
+                                    String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
+                                    buffer.getStringBuffer().append(unicode); 
+                                }
+                                else
+                                {
+                                    i=length;
+                                    buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT); 
+                                }
+                            }
+                            else
+                            {
+                                int o=offset+i+1;
+                                i+=2;
+                                byte b=(byte)TypeUtil.parseInt(encoded,o,2,16);
+                                buffer.append(b);
+                            }
+                        }
+                        catch(NotUtf8Exception e)
+                        {
+                            LOG.warn(e.toString());
+                            LOG.debug(e);
+                        }
+                        catch(NumberFormatException e)
+                        {
+                            LOG.warn(e.toString());
+                            LOG.debug(e);
+                            buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);  
+                        }
+                    }
+                    else
+                    {
+                        buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT); 
+                        i=length;
+                    }
+                }
+                else if (buffer!=null)
+                    buffer.getStringBuffer().append(c);
+            }
+
+            if (buffer==null)
+            {
+                if (offset==0 && encoded.length()==length)
+                    return encoded;
+                return encoded.substring(offset,offset+length);
+            }
+
+            return buffer.toReplacedString();
+        }
+        else
+        {
+            StringBuffer buffer=null;
+
+            for (int i=0;i<length;i++)
+            {
+                char c = encoded.charAt(offset+i);
+                if (c<0||c>0xff)
+                {
+                    if (buffer==null)
+                    {
+                        buffer=new StringBuffer(length);
+                        buffer.append(encoded,offset,offset+i+1);
+                    }
+                    else
+                        buffer.append(c);
+                }
+                else if (c=='+')
+                {
+                    if (buffer==null)
+                    {
+                        buffer=new StringBuffer(length);
+                        buffer.append(encoded,offset,offset+i);
+                    }
+
+                    buffer.append(' ');
+                }
+                else if (c=='%')
+                {
+                    if (buffer==null)
+                    {
+                        buffer=new StringBuffer(length);
+                        buffer.append(encoded,offset,offset+i);
+                    }
+
+                    byte[] ba=new byte[length];
+                    int n=0;
+                    while(c>=0 && c<=0xff)
+                    {
+                        if (c=='%')
+                        {   
+                            if(i+2<length)
+                            {
+                                try
+                                {
+                                    if ('u'==encoded.charAt(offset+i+1))
+                                    {
+                                        if (i+6<length)
+                                        {
+                                            int o=offset+i+2;
+                                            i+=6;
+                                            String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
+                                            byte[] reencoded = unicode.getBytes(charset);
+                                            System.arraycopy(reencoded,0,ba,n,reencoded.length);
+                                            n+=reencoded.length;
+                                        }
+                                        else
+                                        {
+                                            ba[n++] = (byte)'?';
+                                            i=length;
+                                        }
+                                    }
+                                    else
+                                    {
+                                        int o=offset+i+1;
+                                        i+=3;
+                                        ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16);
+                                        n++;
+                                    }
+                                }
+                                catch(Exception e)
+                                {   
+                                    LOG.warn(e.toString());
+                                    LOG.debug(e);
+                                    ba[n++] = (byte)'?';
+                                }
+                            }
+                            else
+                            {
+                                    ba[n++] = (byte)'?';
+                                    i=length;
+                            }
+                        }
+                        else if (c=='+')
+                        {
+                            ba[n++]=(byte)' ';
+                            i++;
+                        }
+                        else
+                        {
+                            ba[n++]=(byte)c;
+                            i++;
+                        }
+
+                        if (i>=length)
+                            break;
+                        c = encoded.charAt(offset+i);
+                    }
+
+                    i--;
+                    buffer.append(new String(ba,0,n,charset));
+
+                }
+                else if (buffer!=null)
+                    buffer.append(c);
+            }
+
+            if (buffer==null)
+            {
+                if (offset==0 && encoded.length()==length)
+                    return encoded;
+                return encoded.substring(offset,offset+length);
+            }
+
+            return buffer.toString();
+        }
+
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Perform URL encoding.
+     * @param string 
+     * @return encoded string.
+     */
+    public static String encodeString(String string)
+    {
+        return encodeString(string,ENCODING);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Perform URL encoding.
+     * @param string 
+     * @return encoded string.
+     */
+    public static String encodeString(String string,Charset charset)
+    {
+        if (charset==null)
+            charset=ENCODING;
+        byte[] bytes=null;
+        bytes=string.getBytes(charset);
+        
+        int len=bytes.length;
+        byte[] encoded= new byte[bytes.length*3];
+        int n=0;
+        boolean noEncode=true;
+        
+        for (int i=0;i<len;i++)
+        {
+            byte b = bytes[i];
+            
+            if (b==' ')
+            {
+                noEncode=false;
+                encoded[n++]=(byte)'+';
+            }
+            else if (b>='a' && b<='z' ||
+                     b>='A' && b<='Z' ||
+                     b>='0' && b<='9')
+            {
+                encoded[n++]=b;
+            }
+            else
+            {
+                noEncode=false;
+                encoded[n++]=(byte)'%';
+                byte nibble= (byte) ((b&0xf0)>>4);
+                if (nibble>=10)
+                    encoded[n++]=(byte)('A'+nibble-10);
+                else
+                    encoded[n++]=(byte)('0'+nibble);
+                nibble= (byte) (b&0xf);
+                if (nibble>=10)
+                    encoded[n++]=(byte)('A'+nibble-10);
+                else
+                    encoded[n++]=(byte)('0'+nibble);
+            }
+        }
+
+        if (noEncode)
+            return string;
+        
+        return new String(encoded,0,n,charset);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** 
+     */
+    @Override
+    public Object clone()
+    {
+        return new UrlEncoded(this);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Utf8Appendable.java b/lib/jetty/org/eclipse/jetty/util/Utf8Appendable.java
new file mode 100644 (file)
index 0000000..ff58764
--- /dev/null
@@ -0,0 +1,256 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Utf8 Appendable abstract base class
+ *
+ * This abstract class wraps a standard {@link java.lang.Appendable} and provides methods to append UTF-8 encoded bytes, that are converted into characters.
+ *
+ * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before state a character is appended to the string buffer.
+ *
+ * The UTF-8 decoding is done by this class and no additional buffers or Readers are used. The UTF-8 code was inspired by
+ * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ *
+ * License information for Bjoern Hoehrmann's code:
+ *
+ * Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ **/
+public abstract class Utf8Appendable
+{
+    protected static final Logger LOG = Log.getLogger(Utf8Appendable.class);
+    public static final char REPLACEMENT = '\ufffd';
+    public static final byte[] REPLACEMENT_UTF8 = new byte[] {(byte)0xEF,(byte)0xBF,(byte)0xBD };
+    private static final int UTF8_ACCEPT = 0;
+    private static final int UTF8_REJECT = 12;
+
+    protected final Appendable _appendable;
+    protected int _state = UTF8_ACCEPT;
+
+    private static final byte[] BYTE_TABLE =
+    {
+        // The first part of the table maps bytes to character classes that
+        // to reduce the size of the transition table and create bitmasks.
+         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+         1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+         7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+         8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+        10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8
+    };
+
+    private static final byte[] TRANS_TABLE =
+    {
+        // The second part is a transition table that maps a combination
+        // of a state of the automaton and a character class to a state.
+         0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
+        12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
+        12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
+        12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
+        12,36,12,12,12,12,12,12,12,12,12,12
+    };
+
+    private int _codep;
+
+    public Utf8Appendable(Appendable appendable)
+    {
+        _appendable = appendable;
+    }
+
+    public abstract int length();
+
+    protected void reset()
+    {
+        _state = UTF8_ACCEPT;
+    }
+
+    public void append(byte b)
+    {
+        try
+        {
+            appendByte(b);
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    public void append(ByteBuffer buf)
+    {
+        try
+        {
+            while (buf.remaining() > 0)
+            {
+                appendByte(buf.get());
+            }
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void append(byte[] b, int offset, int length)
+    {
+        try
+        {
+            int end = offset + length;
+            for (int i = offset; i < end; i++)
+                appendByte(b[i]);
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public boolean append(byte[] b, int offset, int length, int maxChars)
+    {
+        try
+        {
+            int end = offset + length;
+            for (int i = offset; i < end; i++)
+            {
+                if (length() > maxChars)
+                    return false;
+                appendByte(b[i]);
+            }
+            return true;
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected void appendByte(byte b) throws IOException
+    {
+
+        if (b > 0 && _state == UTF8_ACCEPT)
+        {
+            _appendable.append((char)(b & 0xFF));
+        }
+        else
+        {
+            int i = b & 0xFF;
+            int type = BYTE_TABLE[i];
+            _codep = _state == UTF8_ACCEPT ? (0xFF >> type) & i : (i & 0x3F) | (_codep << 6);
+            int next = TRANS_TABLE[_state + type];
+
+            switch(next)
+            {
+                case UTF8_ACCEPT:
+                    _state=next;
+                    if (_codep < Character.MIN_HIGH_SURROGATE)
+                    {
+                        _appendable.append((char)_codep);
+                    }
+                    else
+                    {
+                        for (char c : Character.toChars(_codep))
+                            _appendable.append(c);
+                    }
+                    break;
+                    
+                case UTF8_REJECT:
+                    String reason = "byte "+TypeUtil.toHexString(b)+" in state "+(_state/12);
+                    _codep=0;
+                    _state = UTF8_ACCEPT;
+                    _appendable.append(REPLACEMENT);
+                    throw new NotUtf8Exception(reason);
+                    
+                default:
+                    _state=next;
+                    
+            }
+        }
+    }
+
+    public boolean isUtf8SequenceComplete()
+    {
+        return _state == UTF8_ACCEPT;
+    }
+
+    @SuppressWarnings("serial")
+    public static class NotUtf8Exception extends IllegalArgumentException
+    {
+        public NotUtf8Exception(String reason)
+        {
+            super("Not valid UTF8! "+reason);
+        }
+    }
+
+    protected void checkState()
+    {
+        if (!isUtf8SequenceComplete())
+        {
+            _codep=0;
+            _state = UTF8_ACCEPT;
+            try
+            {
+                _appendable.append(REPLACEMENT);
+            }
+            catch(IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+            throw new NotUtf8Exception("incomplete UTF8 sequence");
+        }
+    }
+    
+    public String toReplacedString()
+    {
+        if (!isUtf8SequenceComplete())
+        {
+            _codep=0;
+            _state = UTF8_ACCEPT;
+            try
+            {
+                _appendable.append(REPLACEMENT);
+            }
+            catch(IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+            Throwable th= new NotUtf8Exception("incomplete UTF8 sequence");
+            LOG.warn(th.toString());
+            LOG.debug(th);
+        }
+        return _appendable.toString();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Utf8LineParser.java b/lib/jetty/org/eclipse/jetty/util/Utf8LineParser.java
new file mode 100644 (file)
index 0000000..b54cf41
--- /dev/null
@@ -0,0 +1,101 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
+
+/**
+ * Stateful parser for lines of UTF8 formatted text, looking for <code>"\n"</code> as a line termination character.
+ * <p>
+ * For use with new IO framework that is based on ByteBuffer parsing.
+ */
+public class Utf8LineParser
+{
+    private enum State
+    {
+        START,
+        PARSE,
+        END;
+    }
+
+    private State state;
+    private Utf8StringBuilder utf;
+
+    public Utf8LineParser()
+    {
+        this.state = State.START;
+    }
+
+    /**
+     * Parse a ByteBuffer (could be a partial buffer), and return once a complete line of UTF8 parsed text has been reached.
+     *
+     * @param buf
+     *            the buffer to parse (could be an incomplete buffer)
+     * @return the line of UTF8 parsed text, or null if no line end termination has been reached within the {@link ByteBuffer#remaining() remaining} bytes of
+     *         the provided ByteBuffer. (In the case of a null, a subsequent ByteBuffer with a line end termination should be provided)
+     * @throws NotUtf8Exception
+     *             if the input buffer has bytes that do not conform to UTF8 validation (validation performed by {@link Utf8StringBuilder}
+     */
+    public String parse(ByteBuffer buf)
+    {
+        byte b;
+        while (buf.remaining() > 0)
+        {
+            b = buf.get();
+            if (parseByte(b))
+            {
+                state = State.START;
+                return utf.toString();
+            }
+        }
+        // have not reached end of line (yet)
+        return null;
+    }
+
+    private boolean parseByte(byte b)
+    {
+        switch (state)
+        {
+            case START:
+                utf = new Utf8StringBuilder();
+                state = State.PARSE;
+                return parseByte(b);
+            case PARSE:
+                // not waiting on more UTF sequence parts.
+                if (utf.isUtf8SequenceComplete() && ((b == '\r') || (b == '\n')))
+                {
+                    state = State.END;
+                    return parseByte(b);
+                }
+                utf.append(b);
+                break;
+            case END:
+                if (b == '\n')
+                {
+                    // we've reached the end
+                    state = State.START;
+                    return true;
+                }
+                break;
+        }
+        return false;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Utf8StringBuffer.java b/lib/jetty/org/eclipse/jetty/util/Utf8StringBuffer.java
new file mode 100644 (file)
index 0000000..63fb1ac
--- /dev/null
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+/* ------------------------------------------------------------ */
+/**
+ * UTF-8 StringBuffer.
+ *
+ * This class wraps a standard {@link java.lang.StringBuffer} and provides methods to append
+ * UTF-8 encoded bytes, that are converted into characters.
+ *
+ * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before
+ * state a character is appended to the string buffer.
+ *
+ * The UTF-8 decoding is done by this class and no additional buffers or Readers are used.
+ * The UTF-8 code was inspired by http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ */
+public class Utf8StringBuffer extends Utf8Appendable
+{
+    final StringBuffer _buffer;
+
+    public Utf8StringBuffer()
+    {
+        super(new StringBuffer());
+        _buffer = (StringBuffer)_appendable;
+    }
+
+    public Utf8StringBuffer(int capacity)
+    {
+        super(new StringBuffer(capacity));
+        _buffer = (StringBuffer)_appendable;
+    }
+
+    @Override
+    public int length()
+    {
+        return _buffer.length();
+    }
+
+    @Override
+    public void reset()
+    {
+        super.reset();
+        _buffer.setLength(0);
+    }
+
+    public StringBuffer getStringBuffer()
+    {
+        checkState();
+        return _buffer;
+    }
+
+    @Override
+    public String toString()
+    {
+        checkState();
+        return _buffer.toString();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Utf8StringBuilder.java b/lib/jetty/org/eclipse/jetty/util/Utf8StringBuilder.java
new file mode 100644 (file)
index 0000000..28fa20b
--- /dev/null
@@ -0,0 +1,78 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+
+/* ------------------------------------------------------------ */
+/** UTF-8 StringBuilder.
+ *
+ * This class wraps a standard {@link java.lang.StringBuilder} and provides methods to append
+ * UTF-8 encoded bytes, that are converted into characters.
+ *
+ * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before
+ * state a character is appended to the string buffer.
+ *
+ * The UTF-8 decoding is done by this class and no additional buffers or Readers are used.
+ * The UTF-8 code was inspired by http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ *
+ */
+public class Utf8StringBuilder extends Utf8Appendable
+{
+    final StringBuilder _buffer;
+
+    public Utf8StringBuilder()
+    {
+        super(new StringBuilder());
+        _buffer=(StringBuilder)_appendable;
+    }
+
+    public Utf8StringBuilder(int capacity)
+    {
+        super(new StringBuilder(capacity));
+        _buffer=(StringBuilder)_appendable;
+    }
+
+    @Override
+    public int length()
+    {
+        return _buffer.length();
+    }
+
+    @Override
+    public void reset()
+    {
+        super.reset();
+        _buffer.setLength(0);
+    }
+
+    public StringBuilder getStringBuilder()
+    {
+        checkState();
+        return _buffer;
+    }
+
+    @Override
+    public String toString()
+    {
+        checkState();
+        return _buffer.toString();
+    }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/annotation/ManagedAttribute.java b/lib/jetty/org/eclipse/jetty/util/annotation/ManagedAttribute.java
new file mode 100644 (file)
index 0000000..29a805f
--- /dev/null
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The @ManagedAttribute annotation is used to indicate that a given method 
+ * exposes a JMX attribute. This annotation is placed always on the reader 
+ * method of a given attribute. Unless it is marked as read-only in the 
+ * configuration of the annotation a corresponding setter is looked for 
+ * following normal naming conventions. For example if this annotation is 
+ * on a method called getFoo() then a method called setFoo() would be looked 
+ * for and if found wired automatically into the jmx attribute.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target( { ElementType.METHOD } )
+public @interface ManagedAttribute
+{
+    /**
+     * Description of the Managed Attribute
+     * 
+     * @returngit checkout
+     */
+    String value() default "Not Specified";
+    
+    /**
+     * name to use for the attribute
+     * 
+     * @return the name of the attribute
+     */
+    String name() default "";
+    
+    /**
+     * Is the managed field read-only?
+     * 
+     * Required only when a setter exists but should not be exposed via JMX
+     * 
+     * @return true if readonly
+     */
+    boolean readonly() default false;
+  
+    /**
+     * Does the managed field exist on a proxy object?
+     * 
+     * 
+     * @return true if a proxy object is involved
+     */
+    boolean proxied() default false;
+    
+    
+    /**
+     * If is a field references a setter that doesn't conform to standards for discovery
+     * it can be set here.
+     * 
+     * @return the full name of the setter in question
+     */
+    String setter() default "";
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/annotation/ManagedObject.java b/lib/jetty/org/eclipse/jetty/util/annotation/ManagedObject.java
new file mode 100644 (file)
index 0000000..15f4b55
--- /dev/null
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The @ManagedObject annotation is used on a class at the top level to 
+ * indicate that it should be exposed as an mbean. It has only one attribute 
+ * to it which is used as the description of the MBean. Should multiple 
+ * @ManagedObject annotations be found in the chain of influence then the 
+ * first description is used.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target( { ElementType.TYPE } )
+public @interface ManagedObject
+{
+    /**
+     * Description of the Managed Object
+     */
+    String value() default "Not Specified";
+  
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/annotation/ManagedOperation.java b/lib/jetty/org/eclipse/jetty/util/annotation/ManagedOperation.java
new file mode 100644 (file)
index 0000000..3a29368
--- /dev/null
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The @ManagedOperation annotation is used to indicate that a given method 
+ * should be considered a JMX operation.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target( { ElementType.METHOD } )
+public @interface ManagedOperation
+{
+    /**
+     * Description of the Managed Object
+     */
+    String value() default "Not Specified";
+    
+    /**
+     * The impact of an operation. 
+     * 
+     * NOTE: Valid values are UNKNOWN, ACTION, INFO, ACTION_INFO
+     * 
+     * NOTE: applies to METHOD
+     * 
+     * @return String representing the impact of the operation
+     */
+    String impact() default "UNKNOWN";
+    
+    /**
+     * Does the managed field exist on a proxy object?
+     * 
+     * 
+     * @return true if a proxy object is involved
+     */
+    boolean proxied() default false;
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/annotation/Name.java b/lib/jetty/org/eclipse/jetty/util/annotation/Name.java
new file mode 100644 (file)
index 0000000..b79e76e
--- /dev/null
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used to describe variables in method 
+ * signatures so that when rendered into tools like JConsole 
+ * it is clear what the parameters are. For example:
+ *
+ * public void doodle(@Name(value="doodle", description="A description of the argument") String doodle)
+ * 
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target( { ElementType.PARAMETER } )
+public @interface Name
+{
+    /**
+     * the name of the parameter
+     */
+    String value();
+    
+    /**
+     * the description of the parameter
+     */
+    String description() default "";
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/annotation/package-info.java b/lib/jetty/org/eclipse/jetty/util/annotation/package-info.java
new file mode 100644 (file)
index 0000000..5f0038b
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Utility Annotations
+ */
+package org.eclipse.jetty.util.annotation;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/component/AbstractLifeCycle.java b/lib/jetty/org/eclipse/jetty/util/component/AbstractLifeCycle.java
new file mode 100644 (file)
index 0000000..8f2d9dc
--- /dev/null
@@ -0,0 +1,234 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.lang.management.ManagementFactory;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Basic implementation of the life cycle interface for components.
+ */
+@ManagedObject("Abstract Implementation of LifeCycle")
+public abstract class AbstractLifeCycle implements LifeCycle
+{
+    private static final Logger LOG = Log.getLogger(AbstractLifeCycle.class);
+    
+    public static final String STOPPED="STOPPED";
+    public static final String FAILED="FAILED";
+    public static final String STARTING="STARTING";
+    public static final String STARTED="STARTED";
+    public static final String STOPPING="STOPPING";
+    public static final String RUNNING="RUNNING";
+
+    private final CopyOnWriteArrayList<LifeCycle.Listener> _listeners=new CopyOnWriteArrayList<LifeCycle.Listener>();
+    private final Object _lock = new Object();
+    private final int __FAILED = -1, __STOPPED = 0, __STARTING = 1, __STARTED = 2, __STOPPING = 3;
+    private volatile int _state = __STOPPED;
+    private long _stopTimeout = 30000;
+
+    protected void doStart() throws Exception
+    {
+    }
+
+    protected void doStop() throws Exception
+    {
+    }
+    
+    @Override
+    public final void start() throws Exception
+    {
+        synchronized (_lock)
+        {
+            try
+            {
+                if (_state == __STARTED || _state == __STARTING)
+                    return;
+                setStarting();
+                doStart();
+                setStarted();
+            }
+            catch (Throwable e)
+            {
+                setFailed(e);
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public final void stop() throws Exception
+    {
+        synchronized (_lock)
+        {
+            try
+            {
+                if (_state == __STOPPING || _state == __STOPPED)
+                    return;
+                setStopping();
+                doStop();
+                setStopped();
+            }
+            catch (Throwable e)
+            {
+                setFailed(e);
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public boolean isRunning()
+    {
+        final int state = _state;
+
+        return state == __STARTED || state == __STARTING;
+    }
+
+    @Override
+    public boolean isStarted()
+    {
+        return _state == __STARTED;
+    }
+
+    @Override
+    public boolean isStarting()
+    {
+        return _state == __STARTING;
+    }
+
+    @Override
+    public boolean isStopping()
+    {
+        return _state == __STOPPING;
+    }
+
+    @Override
+    public boolean isStopped()
+    {
+        return _state == __STOPPED;
+    }
+
+    @Override
+    public boolean isFailed()
+    {
+        return _state == __FAILED;
+    }
+
+    @Override
+    public void addLifeCycleListener(LifeCycle.Listener listener)
+    {
+        _listeners.add(listener);
+    }
+
+    @Override
+    public void removeLifeCycleListener(LifeCycle.Listener listener)
+    {
+        _listeners.remove(listener);
+    }
+
+    @ManagedAttribute(value="Lifecycle State for this instance", readonly=true)
+    public String getState()
+    {
+        switch(_state)
+        {
+            case __FAILED: return FAILED;
+            case __STARTING: return STARTING;
+            case __STARTED: return STARTED;
+            case __STOPPING: return STOPPING;
+            case __STOPPED: return STOPPED;
+        }
+        return null;
+    }
+
+    public static String getState(LifeCycle lc)
+    {
+        if (lc.isStarting()) return STARTING;
+        if (lc.isStarted()) return STARTED;
+        if (lc.isStopping()) return STOPPING;
+        if (lc.isStopped()) return STOPPED;
+        return FAILED;
+    }
+
+    private void setStarted()
+    {
+        _state = __STARTED;
+        if (LOG.isDebugEnabled())
+            
+        LOG.debug(STARTED+" @{}ms {}",ManagementFactory.getRuntimeMXBean().getUptime(),this);
+        for (Listener listener : _listeners)
+            listener.lifeCycleStarted(this);
+    }
+
+    private void setStarting()
+    {
+        LOG.debug("starting {}",this);
+        _state = __STARTING;
+        for (Listener listener : _listeners)
+            listener.lifeCycleStarting(this);
+    }
+
+    private void setStopping()
+    {
+        LOG.debug("stopping {}",this);
+        _state = __STOPPING;
+        for (Listener listener : _listeners)
+            listener.lifeCycleStopping(this);
+    }
+
+    private void setStopped()
+    {
+        _state = __STOPPED;
+        LOG.debug("{} {}",STOPPED,this);
+        for (Listener listener : _listeners)
+            listener.lifeCycleStopped(this);
+    }
+
+    private void setFailed(Throwable th)
+    {
+        _state = __FAILED;
+        LOG.warn(FAILED+" " + this+": "+th,th);
+        for (Listener listener : _listeners)
+            listener.lifeCycleFailure(this,th);
+    }
+
+    @ManagedAttribute(value="The stop timeout in milliseconds")
+    public long getStopTimeout()
+    {
+        return _stopTimeout;
+    }
+
+    public void setStopTimeout(long stopTimeout)
+    {
+        this._stopTimeout = stopTimeout;
+    }
+
+    public static abstract class AbstractLifeCycleListener implements LifeCycle.Listener
+    {
+        @Override public void lifeCycleFailure(LifeCycle event, Throwable cause) {}
+        @Override public void lifeCycleStarted(LifeCycle event) {}
+        @Override public void lifeCycleStarting(LifeCycle event) {}
+        @Override public void lifeCycleStopped(LifeCycle event) {}
+        @Override public void lifeCycleStopping(LifeCycle event) {}
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/Container.java b/lib/jetty/org/eclipse/jetty/util/component/Container.java
new file mode 100644 (file)
index 0000000..a5a4c75
--- /dev/null
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.util.Collection;
+
+public interface Container
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Add a bean.  If the bean is-a {@link Listener}, then also do an implicit {@link #addEventListener(Listener)}.
+     * @param o the bean object to add
+     * @return true if the bean was added, false if it was already present
+     */
+    public boolean addBean(Object o);
+
+    /**
+     * @return the list of beans known to this aggregate
+     * @see #getBean(Class)
+     */
+    public Collection<Object> getBeans();
+
+    /**
+     * @param clazz the class of the beans
+     * @return the list of beans of the given class (or subclass)
+     * @see #getBeans()
+     */
+    public <T> Collection<T> getBeans(Class<T> clazz);
+
+    /**
+     * @param clazz the class of the bean
+     * @return the first bean of a specific class (or subclass), or null if no such bean exist
+     */
+    public <T> T getBean(Class<T> clazz);
+
+    /**
+     * Removes the given bean.
+     * If the bean is-a {@link Listener}, then also do an implicit {@link #removeEventListener(Listener)}.
+     * @return whether the bean was removed
+     */
+    public boolean removeBean(Object o);
+    
+    /**
+     * Add an event listener. 
+     * @see Container#addBean(Object)
+     * @param listener
+     */
+    public void addEventListener(Listener listener);
+    
+    /**
+     * Remove an event listener. 
+     * @see Container#removeBean(Object)
+     * @param listener
+     */
+    public void removeEventListener(Listener listener);
+
+    /**
+     * A listener for Container events.
+     * If an added bean implements this interface it will receive the events
+     * for this container.
+     */
+    public interface Listener
+    {
+        void beanAdded(Container parent,Object child);
+        void beanRemoved(Container parent,Object child);
+    }
+    
+    /**
+     * Inherited Listener.
+     * If an added bean implements this interface, then it will 
+     * be added to all contained beans that are themselves Containers
+     */
+    public interface InheritedListener extends Listener
+    {
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/ContainerLifeCycle.java b/lib/jetty/org/eclipse/jetty/util/component/ContainerLifeCycle.java
new file mode 100644 (file)
index 0000000..464c0f7
--- /dev/null
@@ -0,0 +1,811 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * An ContainerLifeCycle is an {@link LifeCycle} implementation for a collection of contained beans.
+ * <p>
+ * Beans can be added the ContainerLifeCycle either as managed beans or as unmanaged beans.  A managed bean is started, stopped and destroyed with the aggregate.
+ * An unmanaged bean is associated with the aggregate for the purposes of {@link #dump()}, but it's lifecycle must be managed externally.
+ * <p>
+ * When a {@link LifeCycle} bean is added without a managed state being specified the state is determined heuristically:
+ * <ul>
+ *   <li>If the added bean is running, it will be added as an unmanaged bean.
+ *   <li>If the added bean is !running and the container is !running, it will be added as an AUTO bean (see below).
+ *   <li>If the added bean is !running and the container is starting, it will be added as an managed bean and will be started (this handles the frequent case of 
+ *   new beans added during calls to doStart).
+ *   <li>If the added bean is !running and the container is started, it will be added as an unmanaged bean.
+ * </ul>
+ * When the container is started, then all contained managed beans will also be started.  Any contained Auto beans 
+ * will be check for their status and if already started will be switched unmanaged beans, else they will be 
+ * started and switched to managed beans.  Beans added after a container is started are not started and their state needs to
+ * be explicitly managed.
+ * <p>
+ * When stopping the container, a contained bean will be stopped by this aggregate only if it
+ * is started by this aggregate.
+ * <p>
+ * The methods {@link #addBean(Object, boolean)}, {@link #manage(Object)} and {@link #unmanage(Object)} can be used to
+ * explicitly control the life cycle relationship.
+ * <p>
+ * If adding a bean that is shared between multiple {@link ContainerLifeCycle} instances, then it should be started before being added, so it is unmanaged, or
+ * the API must be used to explicitly set it as unmanaged.
+ * <p>
+ * This class also provides utility methods to dump deep structures of objects.  It the dump, the following symbols are used to indicate the type of contained object:
+ * <pre>
+ * SomeContainerLifeCycleInstance
+ *   +- contained POJO instance
+ *   += contained MANAGED object, started and stopped with this instance
+ *   +~ referenced UNMANAGED object, with separate lifecycle
+ *   +? referenced AUTO object that could become MANAGED or UNMANAGED.
+ * </pre>
+ */
+
+/* ------------------------------------------------------------ */
+/**
+ */
+@ManagedObject("Implementation of Container and LifeCycle")
+public class ContainerLifeCycle extends AbstractLifeCycle implements Container, Destroyable, Dumpable
+{
+    private static final Logger LOG = Log.getLogger(ContainerLifeCycle.class);
+    private final List<Bean> _beans = new CopyOnWriteArrayList<>();
+    private final List<Container.Listener> _listeners = new CopyOnWriteArrayList<>();
+    private boolean _doStarted = false;
+
+
+    public ContainerLifeCycle()
+    {
+    }
+
+    /**
+     * Starts the managed lifecycle beans in the order they were added.
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        // indicate that we are started, so that addBean will start other beans added.
+        _doStarted = true;
+
+        // start our managed and auto beans
+        for (Bean b : _beans)
+        {
+            if (b._bean instanceof LifeCycle)
+            {
+                LifeCycle l = (LifeCycle)b._bean;
+                switch(b._managed)
+                {
+                    case MANAGED:
+                        if (!l.isRunning())
+                            start(l);
+                        break;
+                    case AUTO:
+                        if (l.isRunning())
+                            unmanage(b);
+                        else
+                        {
+                            manage(b);
+                            start(l);
+                        }
+                        break;
+                }
+            }
+        }
+
+        super.doStart();
+    }
+
+    /**
+     * Starts the given lifecycle.
+     *
+     * @param l
+     * @throws Exception
+     */
+    protected void start(LifeCycle l) throws Exception
+    {
+        l.start();
+    }
+    
+    /**
+     * Stops the given lifecycle.
+     *
+     * @param l
+     * @throws Exception
+     */
+    protected void stop(LifeCycle l) throws Exception
+    {
+        l.stop();
+    }
+
+    /**
+     * Stops the managed lifecycle beans in the reverse order they were added.
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        _doStarted = false;
+        super.doStop();
+        List<Bean> reverse = new ArrayList<>(_beans);
+        Collections.reverse(reverse);
+        for (Bean b : reverse)
+        {
+            if (b._managed==Managed.MANAGED && b._bean instanceof LifeCycle)
+            {
+                LifeCycle l = (LifeCycle)b._bean;
+                if (l.isRunning())
+                    stop(l);
+            }
+        }
+    }
+
+    /**
+     * Destroys the managed Destroyable beans in the reverse order they were added.
+     */
+    @Override
+    public void destroy()
+    {
+        List<Bean> reverse = new ArrayList<>(_beans);
+        Collections.reverse(reverse);
+        for (Bean b : reverse)
+        {
+            if (b._bean instanceof Destroyable && (b._managed==Managed.MANAGED || b._managed==Managed.POJO))
+            {
+                Destroyable d = (Destroyable)b._bean;
+                d.destroy();
+            }
+        }
+        _beans.clear();
+    }
+
+
+    /**
+     * @param bean the bean to test
+     * @return whether this aggregate contains the bean
+     */
+    public boolean contains(Object bean)
+    {
+        for (Bean b : _beans)
+            if (b._bean == bean)
+                return true;
+        return false;
+    }
+
+    /**
+     * @param bean the bean to test
+     * @return whether this aggregate contains and manages the bean
+     */
+    public boolean isManaged(Object bean)
+    {
+        for (Bean b : _beans)
+            if (b._bean == bean)
+                return b.isManaged();
+        return false;
+    }
+
+    /**
+     * Adds the given bean, detecting whether to manage it or not.
+     * If the bean is a {@link LifeCycle}, then it will be managed if it is not
+     * already started and not managed if it is already started.
+     * The {@link #addBean(Object, boolean)}
+     * method should be used if this is not correct, or the {@link #manage(Object)} and {@link #unmanage(Object)}
+     * methods may be used after an add to change the status.
+     *
+     * @param o the bean object to add
+     * @return true if the bean was added, false if it was already present
+     */
+    @Override
+    public boolean addBean(Object o)
+    {
+        if (o instanceof LifeCycle)
+        {
+            LifeCycle l = (LifeCycle)o;
+            return addBean(o,l.isRunning()?Managed.UNMANAGED:Managed.AUTO);
+        }
+
+        return addBean(o,Managed.POJO);
+    }
+
+    /**
+     * Adds the given bean, explicitly managing it or not.
+     *
+     * @param o       The bean object to add
+     * @param managed whether to managed the lifecycle of the bean
+     * @return true if the bean was added, false if it was already present
+     */
+    public boolean addBean(Object o, boolean managed)
+    {
+        if (o instanceof LifeCycle)
+            return addBean(o,managed?Managed.MANAGED:Managed.UNMANAGED);
+        return addBean(o,managed?Managed.POJO:Managed.UNMANAGED);
+    }
+
+    public boolean addBean(Object o, Managed managed)
+    {
+        if (contains(o))
+            return false;
+
+        Bean new_bean = new Bean(o);
+
+        // if the bean is a Listener
+        if (o instanceof Container.Listener)
+            addEventListener((Container.Listener)o);
+
+        // Add the bean
+        _beans.add(new_bean);
+
+        // Tell existing listeners about the new bean
+        for (Container.Listener l:_listeners)
+            l.beanAdded(this,o);
+
+        try
+        {
+            switch (managed)
+            {
+                case UNMANAGED:
+                    unmanage(new_bean);
+                    break;
+
+                case MANAGED:
+                    manage(new_bean);
+
+                    if (isStarting() && _doStarted)
+                    {
+                        LifeCycle l = (LifeCycle)o;
+                        if (!l.isRunning())
+                            start(l);
+                    }
+                    break;
+
+                case AUTO:
+                    if (o instanceof LifeCycle)
+                    {
+                        LifeCycle l = (LifeCycle)o;
+                        if (isStarting())
+                        {
+                            if (l.isRunning())
+                                unmanage(new_bean);
+                            else if (_doStarted)
+                            {
+                                manage(new_bean);
+                                start(l);
+                            }
+                            else
+                                new_bean._managed=Managed.AUTO;      
+                        }
+                        else if (isStarted())
+                            unmanage(new_bean);
+                        else
+                            new_bean._managed=Managed.AUTO;
+                    }
+                    else
+                        new_bean._managed=Managed.POJO;
+                    break;
+
+                case POJO:
+                    new_bean._managed=Managed.POJO;
+            }
+        }
+        catch (RuntimeException | Error e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+
+        LOG.debug("{} added {}",this,new_bean);
+
+        return true;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /** Add a managed lifecycle.
+     * <p>This is a conveniance method that uses addBean(lifecycle,true)
+     * and then ensures that the added bean is started iff this container
+     * is running.  Exception from nested calls to start are caught and 
+     * wrapped as RuntimeExceptions
+     * @param lifecycle
+     */
+    public void addManaged(LifeCycle lifecycle)
+    {
+        addBean(lifecycle,true);
+        try
+        {
+            if (isRunning() && !lifecycle.isRunning())
+                start(lifecycle);
+        }
+        catch (RuntimeException | Error e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void addEventListener(Container.Listener listener)
+    {
+        if (_listeners.contains(listener))
+            return;
+        
+        _listeners.add(listener);
+
+        // tell it about existing beans
+        for (Bean b:_beans)
+        {
+            listener.beanAdded(this,b._bean);
+
+            // handle inheritance
+            if (listener instanceof InheritedListener && b.isManaged() && b._bean instanceof Container)
+            {
+                if (b._bean instanceof ContainerLifeCycle)
+                     ((ContainerLifeCycle)b._bean).addBean(listener, false);
+                 else
+                     ((Container)b._bean).addBean(listener);
+            }
+        }
+    }
+
+    /**
+     * Manages a bean already contained by this aggregate, so that it is started/stopped/destroyed with this
+     * aggregate.
+     *
+     * @param bean The bean to manage (must already have been added).
+     */
+    public void manage(Object bean)
+    {
+        for (Bean b : _beans)
+        {
+            if (b._bean == bean)
+            {
+                manage(b);
+                return;
+            }
+        }
+        throw new IllegalArgumentException("Unknown bean " + bean);
+    }
+
+    private void manage(Bean bean)
+    {
+        if (bean._managed!=Managed.MANAGED)
+        {
+            bean._managed=Managed.MANAGED;
+
+            if (bean._bean instanceof Container)
+            {
+                for (Container.Listener l:_listeners)
+                {
+                    if (l instanceof InheritedListener)
+                    {
+                        if (bean._bean instanceof ContainerLifeCycle)
+                            ((ContainerLifeCycle)bean._bean).addBean(l,false);
+                        else
+                            ((Container)bean._bean).addBean(l);
+                    }
+                }
+            }
+
+            if (bean._bean instanceof AbstractLifeCycle)
+            {
+                ((AbstractLifeCycle)bean._bean).setStopTimeout(getStopTimeout());
+            }
+        }
+    }
+
+    /**
+     * Unmanages a bean already contained by this aggregate, so that it is not started/stopped/destroyed with this
+     * aggregate.
+     *
+     * @param bean The bean to unmanage (must already have been added).
+     */
+    public void unmanage(Object bean)
+    {
+        for (Bean b : _beans)
+        {
+            if (b._bean == bean)
+            {
+                unmanage(b);
+                return;
+            }
+        }
+        throw new IllegalArgumentException("Unknown bean " + bean);
+    }
+
+    private void unmanage(Bean bean)
+    {
+        if (bean._managed!=Managed.UNMANAGED)
+        {
+            if (bean._managed==Managed.MANAGED && bean._bean instanceof Container)
+            {
+                for (Container.Listener l:_listeners)
+                {
+                    if (l instanceof InheritedListener)
+                        ((Container)bean._bean).removeBean(l);
+                }
+            }
+            bean._managed=Managed.UNMANAGED;
+        }
+    }
+
+    @Override
+    public Collection<Object> getBeans()
+    {
+        return getBeans(Object.class);
+    }
+
+    public void setBeans(Collection<Object> beans)
+    {
+        for (Object bean : beans)
+            addBean(bean);
+    }
+
+    @Override
+    public <T> Collection<T> getBeans(Class<T> clazz)
+    {
+        ArrayList<T> beans = new ArrayList<>();
+        for (Bean b : _beans)
+        {
+            if (clazz.isInstance(b._bean))
+                beans.add(clazz.cast(b._bean));
+        }
+        return beans;
+    }
+
+    @Override
+    public <T> T getBean(Class<T> clazz)
+    {
+        for (Bean b : _beans)
+        {
+            if (clazz.isInstance(b._bean))
+                return clazz.cast(b._bean);
+        }
+        return null;
+    }
+
+    /**
+     * Removes all bean
+     */
+    public void removeBeans()
+    {
+        ArrayList<Bean> beans= new ArrayList<>(_beans);
+        for (Bean b : beans)
+            remove(b);
+    }
+
+    private Bean getBean(Object o)
+    {
+        for (Bean b : _beans)
+        {
+            if (b._bean == o)
+                return b;
+        }
+        return null;
+    }
+
+    @Override
+    public boolean removeBean(Object o)
+    {
+        Bean b=getBean(o);
+        return b!=null && remove(b);
+    }
+
+    private boolean remove(Bean bean)
+    {
+        if (_beans.remove(bean))
+        {
+            
+            unmanage(bean);
+
+            for (Container.Listener l:_listeners)
+                l.beanRemoved(this,bean._bean);
+
+            if (bean._bean instanceof Container.Listener)
+                removeEventListener((Container.Listener)bean._bean);
+
+            // stop managed beans
+            if (bean._managed==Managed.MANAGED && bean._bean instanceof LifeCycle)
+            {
+                try
+                {
+                    stop((LifeCycle)bean._bean);
+                }
+                catch(RuntimeException | Error e)
+                {
+                    throw e;
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException(e);
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void removeEventListener(Container.Listener listener)
+    {
+        if (_listeners.remove(listener))
+        {
+            // remove existing beans
+            for (Bean b:_beans)
+            {
+                listener.beanRemoved(this,b._bean);
+
+                if (listener instanceof InheritedListener && b.isManaged() && b._bean instanceof Container)
+                    ((Container)b._bean).removeBean(listener);
+            }
+        }
+    }
+
+    @Override
+    public void setStopTimeout(long stopTimeout)
+    {
+        super.setStopTimeout(stopTimeout);
+        for (Bean bean : _beans)
+        {
+            if (bean.isManaged() && bean._bean instanceof AbstractLifeCycle)
+                ((AbstractLifeCycle)bean._bean).setStopTimeout(stopTimeout);
+        }
+    }
+
+    /**
+     * Dumps to {@link System#err}.
+     * @see #dump()
+     */
+    @ManagedOperation("Dump the object to stderr")
+    public void dumpStdErr()
+    {
+        try
+        {
+            dump(System.err, "");
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+    @Override
+    @ManagedOperation("Dump the object to a string")
+    public String dump()
+    {
+        return dump(this);
+    }
+
+    public static String dump(Dumpable dumpable)
+    {
+        StringBuilder b = new StringBuilder();
+        try
+        {
+            dumpable.dump(b, "");
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+        }
+        return b.toString();
+    }
+
+    public void dump(Appendable out) throws IOException
+    {
+        dump(out, "");
+    }
+
+    protected void dumpThis(Appendable out) throws IOException
+    {
+        out.append(String.valueOf(this)).append(" - ").append(getState()).append("\n");
+    }
+
+    public static void dumpObject(Appendable out, Object o) throws IOException
+    {
+        try
+        {
+            if (o instanceof LifeCycle)
+                out.append(String.valueOf(o)).append(" - ").append((AbstractLifeCycle.getState((LifeCycle)o))).append("\n");
+            else
+                out.append(String.valueOf(o)).append("\n");
+        }
+        catch (Throwable th)
+        {
+            out.append(" => ").append(th.toString()).append('\n');
+        }
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        dumpBeans(out,indent);
+    }
+
+    protected void dumpBeans(Appendable out, String indent, Collection<?>... collections) throws IOException
+    {
+        dumpThis(out);
+        int size = _beans.size();
+        for (Collection<?> c : collections)
+            size += c.size();
+        if (size == 0)
+            return;
+        int i = 0;
+        for (Bean b : _beans)
+        {
+            i++;
+
+            switch(b._managed)
+            {
+                case POJO:
+                    out.append(indent).append(" +- ");
+                    if (b._bean instanceof Dumpable)
+                        ((Dumpable)b._bean).dump(out, indent + (i == size ? "    " : " |  "));
+                    else
+                        dumpObject(out, b._bean);
+                    break;
+
+                case MANAGED:
+                    out.append(indent).append(" += ");
+                    if (b._bean instanceof Dumpable)
+                        ((Dumpable)b._bean).dump(out, indent + (i == size ? "    " : " |  "));
+                    else
+                        dumpObject(out, b._bean);
+                    break;
+
+                case UNMANAGED:
+                    out.append(indent).append(" +~ ");
+                    dumpObject(out, b._bean);
+                    break;
+
+                case AUTO:
+                    out.append(indent).append(" +? ");
+                    if (b._bean instanceof Dumpable)
+                        ((Dumpable)b._bean).dump(out, indent + (i == size ? "    " : " |  "));
+                    else
+                        dumpObject(out, b._bean);
+                    break;
+
+            }
+        }
+
+        if (i<size)
+            out.append(indent).append(" |\n");
+
+        for (Collection<?> c : collections)
+        {
+            for (Object o : c)
+            {
+                i++;
+                out.append(indent).append(" +> ");
+
+                if (o instanceof Dumpable)
+                    ((Dumpable)o).dump(out, indent + (i == size ? "    " : " |  "));
+                else
+                    dumpObject(out, o);
+            }
+        }
+    }
+
+    public static void dump(Appendable out, String indent, Collection<?>... collections) throws IOException
+    {
+        if (collections.length == 0)
+            return;
+        int size = 0;
+        for (Collection<?> c : collections)
+            size += c.size();
+        if (size == 0)
+            return;
+
+        int i = 0;
+        for (Collection<?> c : collections)
+        {
+            for (Object o : c)
+            {
+                i++;
+                out.append(indent).append(" +- ");
+
+                if (o instanceof Dumpable)
+                    ((Dumpable)o).dump(out, indent + (i == size ? "    " : " |  "));
+                else
+                    dumpObject(out, o);
+            }
+        }
+    }
+
+
+    enum Managed { POJO, MANAGED, UNMANAGED, AUTO };
+
+    private static class Bean
+    {
+        private final Object _bean;
+        private volatile Managed _managed = Managed.POJO;
+
+        private Bean(Object b)
+        {
+            _bean = b;
+        }
+
+        public boolean isManaged()
+        {
+            return _managed==Managed.MANAGED;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("{%s,%s}", _bean, _managed);
+        }
+    }
+
+    public void updateBean(Object oldBean, final Object newBean)
+    {
+        if (newBean!=oldBean)
+        {
+            if (oldBean!=null)
+                removeBean(oldBean);
+            if (newBean!=null)
+                addBean(newBean);
+        }
+    }
+
+    public void updateBeans(Object[] oldBeans, final Object[] newBeans)
+    {
+        // remove oldChildren not in newChildren
+        if (oldBeans!=null)
+        {
+            loop: for (Object o:oldBeans)
+            {
+                if (newBeans!=null)
+                {
+                    for (Object n:newBeans)
+                        if (o==n)
+                            continue loop;
+                }
+                removeBean(o);
+            }
+        }
+
+        // add new beans not in old
+        if (newBeans!=null)
+        {
+            loop: for (Object n:newBeans)
+            {
+                if (oldBeans!=null)
+                {
+                    for (Object o:oldBeans)
+                        if (o==n)
+                            continue loop;
+                }
+                addBean(n);
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/Destroyable.java b/lib/jetty/org/eclipse/jetty/util/component/Destroyable.java
new file mode 100644 (file)
index 0000000..2e7e441
--- /dev/null
@@ -0,0 +1,31 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+
+/**
+ * <p>A Destroyable is an object which can be destroyed.</p>
+ * <p>Typically a Destroyable is a {@link LifeCycle} component that can hold onto
+ * resources over multiple start/stop cycles.   A call to destroy will release all
+ * resources and will prevent any further start/stop cycles from being successful.</p>
+ */
+public interface Destroyable
+{
+    void destroy();
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/Dumpable.java b/lib/jetty/org/eclipse/jetty/util/component/Dumpable.java
new file mode 100644 (file)
index 0000000..2a1882b
--- /dev/null
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+
+@ManagedObject("Dumpable Object")
+public interface Dumpable
+{
+    @ManagedOperation(value="Dump the nested Object state as a String", impact="INFO")
+    String dump();
+    
+    void dump(Appendable out,String indent) throws IOException;
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/FileDestroyable.java b/lib/jetty/org/eclipse/jetty/util/component/FileDestroyable.java
new file mode 100644 (file)
index 0000000..9493645
--- /dev/null
@@ -0,0 +1,95 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+public class FileDestroyable implements Destroyable
+{
+    private static final Logger LOG = Log.getLogger(FileDestroyable.class);
+    final List<File> _files = new ArrayList<File>();
+
+    public FileDestroyable()
+    {
+    }
+    
+    public FileDestroyable(String file) throws IOException
+    {
+        _files.add(Resource.newResource(file).getFile());
+    }
+    
+    public FileDestroyable(File file)
+    {
+        _files.add(file);
+    }
+    
+    public void addFile(String file) throws IOException
+    {
+        try(Resource r = Resource.newResource(file);)
+        {
+            _files.add(r.getFile());
+        }
+    }
+    
+    public void addFile(File file)
+    {
+        _files.add(file);
+    }
+    
+    public void addFiles(Collection<File> files)
+    {
+        _files.addAll(files);
+    }
+    
+    public void removeFile(String file) throws IOException
+    {
+        try(Resource r = Resource.newResource(file);)
+        {
+            _files.remove(r.getFile());
+        }
+    }
+    
+    public void removeFile(File file)
+    {
+        _files.remove(file);
+    }
+    
+    @Override
+    public void destroy()
+    {
+        for (File file : _files)
+        {
+            if (file.exists())
+            {
+                LOG.debug("Destroy {}",file);
+                IO.delete(file);
+            }
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java b/lib/jetty/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java
new file mode 100644 (file)
index 0000000..1c4dcec
--- /dev/null
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.FileWriter;
+import java.io.Writer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** A LifeCycle Listener that writes state changes to a file.
+ * <p>This can be used with the jetty.sh script to wait for successful startup.
+ */
+public class FileNoticeLifeCycleListener implements LifeCycle.Listener
+{
+    private static final Logger LOG = Log.getLogger(FileNoticeLifeCycleListener.class);
+    
+    private final String _filename;
+    
+    public FileNoticeLifeCycleListener(String filename)
+    {
+        _filename=filename;
+    }
+
+    private void writeState(String action, LifeCycle lifecycle)
+    {
+        try (Writer out = new FileWriter(_filename,true))
+        {
+            out.append(action).append(" ").append(lifecycle.toString()).append("\n");
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+    
+    public void lifeCycleStarting(LifeCycle event)
+    {  
+        writeState("STARTING",event);      
+    }
+
+    public void lifeCycleStarted(LifeCycle event)
+    {        
+        writeState("STARTED",event); 
+    }
+
+    public void lifeCycleFailure(LifeCycle event, Throwable cause)
+    {        
+        writeState("FAILED",event);
+    }
+
+    public void lifeCycleStopping(LifeCycle event)
+    {        
+        writeState("STOPPING",event);
+    }
+
+    public void lifeCycleStopped(LifeCycle event)
+    {        
+        writeState("STOPPED",event);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/Graceful.java b/lib/jetty/org/eclipse/jetty/util/component/Graceful.java
new file mode 100644 (file)
index 0000000..96bec33
--- /dev/null
@@ -0,0 +1,29 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.util.concurrent.Future;
+
+/* ------------------------------------------------------------ */
+/* A Lifecycle that can be gracefully shutdown.
+ */
+public interface Graceful
+{
+    public Future<Void> shutdown();
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/LifeCycle.java b/lib/jetty/org/eclipse/jetty/util/component/LifeCycle.java
new file mode 100644 (file)
index 0000000..f58fd1a
--- /dev/null
@@ -0,0 +1,125 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.util.EventListener;
+
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+
+/* ------------------------------------------------------------ */
+/**
+ * The lifecycle interface for generic components.
+ * <br />
+ * Classes implementing this interface have a defined life cycle
+ * defined by the methods of this interface.
+ *
+ * 
+ */
+@ManagedObject("Lifecycle Interface for startable components")
+public interface LifeCycle
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Starts the component.
+     * @throws Exception If the component fails to start
+     * @see #isStarted()
+     * @see #stop()
+     * @see #isFailed()
+     */
+    @ManagedOperation(value="Starts the instance", impact="ACTION")
+    public void start()
+        throws Exception;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Stops the component.
+     * The component may wait for current activities to complete
+     * normally, but it can be interrupted.
+     * @exception Exception If the component fails to stop
+     * @see #isStopped()
+     * @see #start()
+     * @see #isFailed()
+     */
+    @ManagedOperation(value="Stops the instance", impact="ACTION")
+    public void stop()
+        throws Exception;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component is starting or has been started.
+     */
+    public boolean isRunning();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component has been started.
+     * @see #start()
+     * @see #isStarting()
+     */
+    public boolean isStarted();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component is starting.
+     * @see #isStarted()
+     */
+    public boolean isStarting();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component is stopping.
+     * @see #isStopped()
+     */
+    public boolean isStopping();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component has been stopped.
+     * @see #stop()
+     * @see #isStopping()
+     */
+    public boolean isStopped();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component has failed to start or has failed to stop.
+     */
+    public boolean isFailed();
+    
+    /* ------------------------------------------------------------ */
+    public void addLifeCycleListener(LifeCycle.Listener listener);
+
+    /* ------------------------------------------------------------ */
+    public void removeLifeCycleListener(LifeCycle.Listener listener);
+    
+
+    /* ------------------------------------------------------------ */
+    /** Listener.
+     * A listener for Lifecycle events.
+     */
+    public interface Listener extends EventListener
+    {
+        public void lifeCycleStarting(LifeCycle event);
+        public void lifeCycleStarted(LifeCycle event);
+        public void lifeCycleFailure(LifeCycle event,Throwable cause);
+        public void lifeCycleStopping(LifeCycle event);
+        public void lifeCycleStopped(LifeCycle event);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/package-info.java b/lib/jetty/org/eclipse/jetty/util/component/package-info.java
new file mode 100644 (file)
index 0000000..2ae3d19
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Jetty Lifecycle Management
+ */
+package org.eclipse.jetty.util.component;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/log/AbstractLogger.java b/lib/jetty/org/eclipse/jetty/util/log/AbstractLogger.java
new file mode 100644 (file)
index 0000000..6645376
--- /dev/null
@@ -0,0 +1,84 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+
+/* ------------------------------------------------------------ */
+/** Abstract Logger.
+ * Manages the atomic registration of the logger by name.
+ */
+public abstract class AbstractLogger implements Logger
+{
+    @Override
+    public final Logger getLogger(String name)
+    {
+        if (isBlank(name))
+            return this;
+
+        final String basename = getName();
+        final String fullname = (isBlank(basename) || Log.getRootLogger()==this)?name:(basename + "." + name);
+        
+        Logger logger = Log.getLoggers().get(fullname);
+        if (logger == null)
+        {
+            Logger newlog = newLogger(fullname);
+            
+            logger = Log.getMutableLoggers().putIfAbsent(fullname,newlog);
+            if (logger == null)
+                logger=newlog;
+        }
+
+        return logger;
+    }
+    
+
+    protected abstract Logger newLogger(String fullname);
+
+    /**
+     * A more robust form of name blank test. Will return true for null names, and names that have only whitespace
+     *
+     * @param name
+     *            the name to test
+     * @return true for null or blank name, false if any non-whitespace character is found.
+     */
+    private static boolean isBlank(String name)
+    {
+        if (name == null)
+        {
+            return true;
+        }
+        int size = name.length();
+        char c;
+        for (int i = 0; i < size; i++)
+        {
+            c = name.charAt(i);
+            if (!Character.isWhitespace(c))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    public void debug(String msg, long arg)
+    {
+        if (isDebugEnabled())
+            debug(msg,new Long(arg));
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/log/JavaUtilLog.java b/lib/jetty/org/eclipse/jetty/util/log/JavaUtilLog.java
new file mode 100644 (file)
index 0000000..094d978
--- /dev/null
@@ -0,0 +1,172 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+import java.util.logging.Level;
+
+/**
+ * <p>
+ * Implementation of Jetty {@link Logger} based on {@link java.util.logging.Logger}.
+ * </p>
+ *
+ * <p>
+ * You can also set the logger level using <a href="http://java.sun.com/j2se/1.5.0/docs/guide/logging/overview.html">
+ * standard java.util.logging configuration</a>.
+ * </p>
+ */
+public class JavaUtilLog extends AbstractLogger
+{
+    private Level configuredLevel;
+    private java.util.logging.Logger _logger;
+
+    public JavaUtilLog()
+    {
+        this("org.eclipse.jetty.util.log");
+    }
+
+    public JavaUtilLog(String name)
+    {
+        _logger = java.util.logging.Logger.getLogger(name);
+        if (Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.DEBUG", "false")))
+        {
+            _logger.setLevel(Level.FINE);
+        }
+        configuredLevel = _logger.getLevel();
+    }
+
+    public String getName()
+    {
+        return _logger.getName();
+    }
+
+    public void warn(String msg, Object... args)
+    {
+        if (_logger.isLoggable(Level.WARNING))
+            _logger.log(Level.WARNING,format(msg,args));
+    }
+
+    public void warn(Throwable thrown)
+    {
+        warn("", thrown);
+    }
+
+    public void warn(String msg, Throwable thrown)
+    {
+        _logger.log(Level.WARNING, msg, thrown);
+    }
+
+    public void info(String msg, Object... args)
+    {
+        if (_logger.isLoggable(Level.INFO))
+            _logger.log(Level.INFO, format(msg, args));
+    }
+
+    public void info(Throwable thrown)
+    {
+        info("", thrown);
+    }
+
+    public void info(String msg, Throwable thrown)
+    {
+        _logger.log(Level.INFO, msg, thrown);
+    }
+
+    public boolean isDebugEnabled()
+    {
+        return _logger.isLoggable(Level.FINE);
+    }
+
+    public void setDebugEnabled(boolean enabled)
+    {
+        if (enabled)
+        {
+            configuredLevel = _logger.getLevel();
+            _logger.setLevel(Level.FINE);
+        }
+        else
+        {
+            _logger.setLevel(configuredLevel);
+        }
+    }
+
+    public void debug(String msg, Object... args)
+    {
+        if (_logger.isLoggable(Level.FINE))
+            _logger.log(Level.FINE,format(msg, args));
+    }
+
+    public void debug(String msg, long arg)
+    {
+        if (_logger.isLoggable(Level.FINE))
+            _logger.log(Level.FINE,format(msg, arg));
+    }
+
+    public void debug(Throwable thrown)
+    {
+        debug("", thrown);
+    }
+
+    public void debug(String msg, Throwable thrown)
+    {
+        _logger.log(Level.FINE, msg, thrown);
+    }
+
+    /**
+     * Create a Child Logger of this Logger.
+     */
+    protected Logger newLogger(String fullname)
+    {
+        return new JavaUtilLog(fullname);
+    }
+
+    public void ignore(Throwable ignored)
+    {
+        if (Log.isIgnored())
+        {
+            warn(Log.IGNORED, ignored);
+        }
+    }
+
+    private String format(String msg, Object... args)
+    {
+        msg = String.valueOf(msg); // Avoids NPE
+        String braces = "{}";
+        StringBuilder builder = new StringBuilder();
+        int start = 0;
+        for (Object arg : args)
+        {
+            int bracesIndex = msg.indexOf(braces, start);
+            if (bracesIndex < 0)
+            {
+                builder.append(msg.substring(start));
+                builder.append(" ");
+                builder.append(arg);
+                start = msg.length();
+            }
+            else
+            {
+                builder.append(msg.substring(start, bracesIndex));
+                builder.append(String.valueOf(arg));
+                start = bracesIndex + braces.length();
+            }
+        }
+        builder.append(msg.substring(start));
+        return builder.toString();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/log/Log.java b/lib/jetty/org/eclipse/jetty/util/log/Log.java
new file mode 100644 (file)
index 0000000..91b61f9
--- /dev/null
@@ -0,0 +1,317 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+
+/**
+ * Logging.
+ * This class provides a static logging interface.  If an instance of the
+ * org.slf4j.Logger class is found on the classpath, the static log methods
+ * are directed to a slf4j logger for "org.eclipse.log".   Otherwise the logs
+ * are directed to stderr.
+ * <p>
+ * The "org.eclipse.jetty.util.log.class" system property can be used
+ * to select a specific logging implementation.
+ * <p>
+ * If the system property org.eclipse.jetty.util.log.IGNORED is set,
+ * then ignored exceptions are logged in detail.
+ *
+ * @see StdErrLog
+ * @see Slf4jLog
+ */
+public class Log
+{
+    public final static String EXCEPTION= "EXCEPTION ";
+    public final static String IGNORED= "IGNORED ";
+
+    /**
+     * Logging Configuration Properties
+     */
+    protected static final Properties __props;
+    /**
+     * The {@link Logger} implementation class name
+     */
+    public static String __logClass;
+    /**
+     * Legacy flag indicating if {@link Logger#ignore(Throwable)} methods produce any output in the {@link Logger}s
+     */
+    public static boolean __ignored;
+
+    /**
+     * Hold loggers only.
+     */
+    private final static ConcurrentMap<String, Logger> __loggers = new ConcurrentHashMap<>();
+
+
+    static
+    {
+        /* Instantiate a default configuration properties (empty)
+         */
+        __props = new Properties();
+
+        AccessController.doPrivileged(new PrivilegedAction<Object>()
+        {
+            public Object run()
+            {
+                /* First see if the jetty-logging.properties object exists in the classpath.
+                 * This is an optional feature used by embedded mode use, and test cases to allow for early
+                 * configuration of the Log class in situations where access to the System.properties are
+                 * either too late or just impossible.
+                 */
+                loadProperties("jetty-logging.properties",__props);
+
+                /*
+                 * Next see if an OS specific jetty-logging.properties object exists in the classpath. 
+                 * This really for setting up test specific logging behavior based on OS.
+                 */
+                String osName = System.getProperty("os.name");
+                // NOTE: cannot use jetty-util's StringUtil as that initializes logging itself.
+                if (osName != null && osName.length() > 0)
+                {
+                    osName = osName.toLowerCase(Locale.ENGLISH).replace(' ','-');
+                    loadProperties("jetty-logging-" + osName + ".properties",__props);
+                }
+
+                /* Now load the System.properties as-is into the __props, these values will override
+                 * any key conflicts in __props.
+                 */
+                @SuppressWarnings("unchecked")
+                Enumeration<String> systemKeyEnum = (Enumeration<String>)System.getProperties().propertyNames();
+                while (systemKeyEnum.hasMoreElements())
+                {
+                    String key = systemKeyEnum.nextElement();
+                    String val = System.getProperty(key);
+                    // protect against application code insertion of non-String values (returned as null)
+                    if (val != null)
+                    {
+                        __props.setProperty(key,val);
+                    }
+                }
+
+                /* Now use the configuration properties to configure the Log statics
+                 */
+                __logClass = __props.getProperty("org.eclipse.jetty.util.log.class","org.eclipse.jetty.util.log.Slf4jLog");
+                __ignored = Boolean.parseBoolean(__props.getProperty("org.eclipse.jetty.util.log.IGNORED","false"));
+                return null;
+            }
+        });
+    }
+    
+    private static void loadProperties(String resourceName, Properties props)
+    {
+        URL testProps = Loader.getResource(Log.class,resourceName);
+        if (testProps != null)
+        {
+            try (InputStream in = testProps.openStream())
+            {
+                Properties p = new Properties();
+                p.load(in);
+                for (Object key : p.keySet())
+                {
+                    Object value = p.get(key);
+                    if (value != null)
+                    {
+                        props.put(key,value);
+                    }
+                }
+            }
+            catch (IOException e)
+            {
+                System.err.println("[WARN] Error loading logging config: " + testProps);
+                e.printStackTrace(System.err);
+            }
+        }
+    }
+
+    private static Logger LOG;
+    private static boolean __initialized=false;
+
+    public static void initialized()
+    {   
+        synchronized (Log.class)
+        {
+            if (__initialized)
+                return;
+            __initialized = true;
+
+            final long uptime=ManagementFactory.getRuntimeMXBean().getUptime();
+
+            try
+            {
+                Class<?> log_class = Loader.loadClass(Log.class, __logClass);
+                if (LOG == null || !LOG.getClass().equals(log_class))
+                {
+                    LOG = (Logger)log_class.newInstance();
+                    LOG.debug("Logging to {} via {}", LOG, log_class.getName());
+                }
+            }
+            catch(Throwable e)
+            {
+                // Unable to load specified Logger implementation, default to standard logging.
+                initStandardLogging(e);
+            }
+
+            if (LOG!=null)
+                LOG.info(String.format("Logging initialized @%dms",uptime));
+        }
+    }
+
+    private static void initStandardLogging(Throwable e)
+    {
+        Class<?> log_class;
+        if(e != null && __ignored)
+        {
+            e.printStackTrace(System.err);
+        }
+
+        if (LOG == null)
+        {
+            log_class = StdErrLog.class;
+            LOG = new StdErrLog();
+            LOG.debug("Logging to {} via {}", LOG, log_class.getName());
+        }
+    }
+    
+    public static Logger getLog()
+    {
+        initialized();
+        return LOG;
+    }
+
+    public static void setLog(Logger log)
+    {
+        Log.LOG = log;
+    }
+
+    /**
+     * Get the root logger.
+     * @return the root logger
+     */
+    public static Logger getRootLogger() {
+        initialized();
+        return LOG;
+    }
+
+    static boolean isIgnored()
+    {
+        return __ignored;
+    }
+
+    /**
+     * Set Log to parent Logger.
+     * <p>
+     * If there is a different Log class available from a parent classloader,
+     * call {@link #getLogger(String)} on it and construct a {@link LoggerLog} instance
+     * as this Log's Logger, so that logging is delegated to the parent Log.
+     * <p>
+     * This should be used if a webapp is using Log, but wishes the logging to be
+     * directed to the containers log.
+     * <p>
+     * If there is not parent Log, then this call is equivalent to<pre>
+     *   Log.setLog(Log.getLogger(name));
+     * </pre>
+     * @param name Logger name
+     */
+    public static void setLogToParent(String name)
+    {
+        ClassLoader loader = Log.class.getClassLoader();
+        if (loader!=null && loader.getParent()!=null)
+        {
+            try
+            {
+                Class<?> uberlog = loader.getParent().loadClass("org.eclipse.jetty.util.log.Log");
+                Method getLogger = uberlog.getMethod("getLogger", new Class[]{String.class});
+                Object logger = getLogger.invoke(null,name);
+                setLog(new LoggerLog(logger));
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+        else
+        {
+            setLog(getLogger(name));
+        }
+    }
+
+    /**
+     * Obtain a named Logger based on the fully qualified class name.
+     *
+     * @param clazz
+     *            the class to base the Logger name off of
+     * @return the Logger with the given name
+     */
+    public static Logger getLogger(Class<?> clazz)
+    {
+        return getLogger(clazz.getName());
+    }
+
+    /**
+     * Obtain a named Logger or the default Logger if null is passed.
+     * @param name the Logger name
+     * @return the Logger with the given name
+     */
+    public static Logger getLogger(String name)
+    {
+        initialized();
+
+        if(name==null)
+            return LOG;
+
+        Logger logger = __loggers.get(name);
+        if(logger==null)
+            logger = LOG.getLogger(name);
+
+        return logger;
+    }
+
+    static ConcurrentMap<String, Logger> getMutableLoggers()
+    {
+        return __loggers;
+    }
+    
+    /**
+     * Get a map of all configured {@link Logger} instances.
+     *
+     * @return a map of all configured {@link Logger} instances
+     */
+    @ManagedAttribute("list of all instantiated loggers")
+    public static Map<String, Logger> getLoggers()
+    {
+        return Collections.unmodifiableMap(__loggers);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/log/Logger.java b/lib/jetty/org/eclipse/jetty/util/log/Logger.java
new file mode 100644 (file)
index 0000000..ac6e086
--- /dev/null
@@ -0,0 +1,122 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+/**
+ * A simple logging facade that is intended simply to capture the style of logging as used by Jetty.
+ */
+public interface Logger
+{
+    /**
+     * @return the name of this logger
+     */
+    public String getName();
+
+    /**
+     * Formats and logs at warn level.
+     * @param msg the formatting string
+     * @param args the optional arguments
+     */
+    public void warn(String msg, Object... args);
+
+    /**
+     * Logs the given Throwable information at warn level
+     * @param thrown the Throwable to log
+     */
+    public void warn(Throwable thrown);
+
+    /**
+     * Logs the given message at warn level, with Throwable information.
+     * @param msg the message to log
+     * @param thrown the Throwable to log
+     */
+    public void warn(String msg, Throwable thrown);
+
+    /**
+     * Formats and logs at info level.
+     * @param msg the formatting string
+     * @param args the optional arguments
+     */
+    public void info(String msg, Object... args);
+
+    /**
+     * Logs the given Throwable information at info level
+     * @param thrown the Throwable to log
+     */
+    public void info(Throwable thrown);
+
+    /**
+     * Logs the given message at info level, with Throwable information.
+     * @param msg the message to log
+     * @param thrown the Throwable to log
+     */
+    public void info(String msg, Throwable thrown);
+
+    /**
+     * @return whether the debug level is enabled
+     */
+    public boolean isDebugEnabled();
+
+    /**
+     * Mutator used to turn debug on programmatically.
+     * @param enabled whether to enable the debug level
+     */
+    public void setDebugEnabled(boolean enabled);
+
+    /**
+     * Formats and logs at debug level.
+     * @param msg the formatting string
+     * @param args the optional arguments
+     */
+    public void debug(String msg, Object... args);
+    
+
+    /**
+     * Formats and logs at debug level.
+     * avoids autoboxing of integers
+     * @param msg the formatting string
+     * @param value long value
+     */
+    public void debug(String msg, long value);
+
+    /**
+     * Logs the given Throwable information at debug level
+     * @param thrown the Throwable to log
+     */
+    public void debug(Throwable thrown);
+
+    /**
+     * Logs the given message at debug level, with Throwable information.
+     * @param msg the message to log
+     * @param thrown the Throwable to log
+     */
+    public void debug(String msg, Throwable thrown);
+
+    /**
+     * @param name the name of the logger
+     * @return a logger with the given name
+     */
+    public Logger getLogger(String name);
+    
+    /**
+     * Ignore an exception.
+     * <p>This should be used rather than an empty catch block.
+     */
+    public void ignore(Throwable ignored); 
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/log/LoggerLog.java b/lib/jetty/org/eclipse/jetty/util/log/LoggerLog.java
new file mode 100644 (file)
index 0000000..1026cb0
--- /dev/null
@@ -0,0 +1,229 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+import java.lang.reflect.Method;
+
+/**
+ *
+ */
+public class LoggerLog extends AbstractLogger
+{
+    private final Object _logger;
+    private final Method _debugMT;
+    private final Method _debugMAA;
+    private final Method _infoMT;
+    private final Method _infoMAA;
+    private final Method _warnMT;
+    private final Method _warnMAA;
+    private final Method _setDebugEnabledE;
+    private final Method _getLoggerN;
+    private final Method _getName;
+    private volatile boolean _debug;
+
+    public LoggerLog(Object logger)
+    {
+        try
+        {
+            _logger = logger;
+            Class<?> lc = logger.getClass();
+            _debugMT = lc.getMethod("debug", new Class[]{String.class, Throwable.class});
+            _debugMAA = lc.getMethod("debug", new Class[]{String.class, Object[].class});
+            _infoMT = lc.getMethod("info", new Class[]{String.class, Throwable.class});
+            _infoMAA = lc.getMethod("info", new Class[]{String.class, Object[].class});
+            _warnMT = lc.getMethod("warn", new Class[]{String.class, Throwable.class});
+            _warnMAA = lc.getMethod("warn", new Class[]{String.class, Object[].class});
+            Method _isDebugEnabled = lc.getMethod("isDebugEnabled");
+            _setDebugEnabledE = lc.getMethod("setDebugEnabled", new Class[]{Boolean.TYPE});
+            _getLoggerN = lc.getMethod("getLogger", new Class[]{String.class});
+            _getName = lc.getMethod("getName");
+
+            _debug = (Boolean)_isDebugEnabled.invoke(_logger);
+        }
+        catch(Exception x)
+        {
+            throw new IllegalStateException(x);
+        }
+    }
+
+    public String getName()
+    {
+        try
+        {
+            return (String)_getName.invoke(_logger);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public void warn(String msg, Object... args)
+    {
+        try
+        {
+            _warnMAA.invoke(_logger, args);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void warn(Throwable thrown)
+    {
+        warn("", thrown);
+    }
+
+    public void warn(String msg, Throwable thrown)
+    {
+        try
+        {
+            _warnMT.invoke(_logger, msg, thrown);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void info(String msg, Object... args)
+    {
+        try
+        {
+            _infoMAA.invoke(_logger, args);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void info(Throwable thrown)
+    {
+        info("", thrown);
+    }
+
+    public void info(String msg, Throwable thrown)
+    {
+        try
+        {
+            _infoMT.invoke(_logger, msg, thrown);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public boolean isDebugEnabled()
+    {
+        return _debug;
+    }
+
+    public void setDebugEnabled(boolean enabled)
+    {
+        try
+        {
+            _setDebugEnabledE.invoke(_logger, enabled);
+            _debug = enabled;
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    
+    public void debug(String msg, Object... args)
+    {
+        if (!_debug)
+            return;
+
+        try
+        {
+            _debugMAA.invoke(_logger, args);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void debug(Throwable thrown)
+    {
+        debug("", thrown);
+    }
+
+    public void debug(String msg, Throwable th)
+    {
+        if (!_debug)
+            return;
+
+        try
+        {
+            _debugMT.invoke(_logger, msg, th);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void debug(String msg, long value)
+    {
+        if (!_debug)
+            return;
+
+        try
+        {
+            _debugMAA.invoke(_logger, new Object[]{new Long(value)});
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+    
+    public void ignore(Throwable ignored)
+    {
+        if (Log.isIgnored())
+        {
+            warn(Log.IGNORED, ignored);
+        }
+    }
+
+    /**
+     * Create a Child Logger of this Logger.
+     */
+    protected Logger newLogger(String fullname)
+    {
+        try
+        {
+            Object logger=_getLoggerN.invoke(_logger, fullname);
+            return new LoggerLog(logger);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            return this;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/log/StacklessLogging.java b/lib/jetty/org/eclipse/jetty/util/log/StacklessLogging.java
new file mode 100644 (file)
index 0000000..fd6fb32
--- /dev/null
@@ -0,0 +1,69 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+/**
+ * A try-with-resources compatible layer for {@link StdErrLog#setHideStacks(boolean) hiding stacktraces} within the scope of the <code>try</code> block when
+ * logging with {@link StdErrLog} implementation.
+ * <p>
+ * Use of other logging implementation cause no effect when using this class
+ * <p>
+ * Example:
+ * 
+ * <pre>
+ * try (StacklessLogging scope = new StacklessLogging(EventDriver.class,Noisy.class))
+ * {
+ *     doActionThatCausesStackTraces();
+ * }
+ * </pre>
+ */
+public class StacklessLogging implements AutoCloseable
+{
+    private final Class<?> clazzes[];
+
+    public StacklessLogging(Class<?>... classesToSquelch)
+    {
+        this.clazzes = classesToSquelch;
+        hideStacks(true);
+    }
+
+    @Override
+    public void close() throws Exception
+    {
+        hideStacks(false);
+    }
+
+    private void hideStacks(boolean hide)
+    {
+        for (Class<?> clazz : clazzes)
+        {
+            Logger log = Log.getLogger(clazz);
+            if (log == null)
+            {
+                // not interested in classes without loggers
+                continue;
+            }
+            if (log instanceof StdErrLog)
+            {
+                // only operate on loggers that are of type StdErrLog
+                ((StdErrLog)log).setHideStacks(hide);
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/log/StdErrLog.java b/lib/jetty/org/eclipse/jetty/util/log/StdErrLog.java
new file mode 100644 (file)
index 0000000..fda1c72
--- /dev/null
@@ -0,0 +1,779 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+import java.io.PrintStream;
+import java.security.AccessControlException;
+import java.util.Properties;
+
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+/**
+ * StdErr Logging implementation. 
+ * <p>
+ * A Jetty {@link Logger} that sends all logs to STDERR ({@link System#err}) with basic formatting.
+ * <p>
+ * Supports named loggers, and properties based configuration. 
+ * <p>
+ * Configuration Properties:
+ * <dl>
+ *   <dt>${name|hierarchy}.LEVEL=(ALL|DEBUG|INFO|WARN|OFF)</dt>
+ *   <dd>
+ *   Sets the level that the Logger should log at.<br/>
+ *   Names can be a package name, or a fully qualified class name.<br/>
+ *   Default: INFO<br/>
+ *   <br/>
+ *   Examples:
+ *   <dl>
+ *   <dt>org.eclipse.jetty.LEVEL=WARN</dt>
+ *   <dd>indicates that all of the jetty specific classes, in any package that 
+ *   starts with <code>org.eclipse.jetty</code> should log at level WARN.</dd>
+ *   <dt>org.eclipse.jetty.io.ChannelEndPoint.LEVEL=ALL</dt>
+ *   <dd>indicates that the specific class, ChannelEndPoint, should log all
+ *   logging events that it can generate, including DEBUG, INFO, WARN (and even special
+ *   internally ignored exception cases).</dd>
+ *   </dl>  
+ *   </dd>
+ *   
+ *   <dt>${name}.SOURCE=(true|false)</dt>
+ *   <dd>
+ *   Logger specific, attempt to print the java source file name and line number
+ *   where the logging event originated from.<br/>
+ *   Name must be a fully qualified class name (package name hierarchy is not supported
+ *   by this configurable)<br/>
+ *   Warning: this is a slow operation and will have an impact on performance!<br/>
+ *   Default: false
+ *   </dd>
+ *   
+ *   <dt>${name}.STACKS=(true|false)</dt>
+ *   <dd>
+ *   Logger specific, control the display of stacktraces.<br/>
+ *   Name must be a fully qualified class name (package name hierarchy is not supported
+ *   by this configurable)<br/>
+ *   Default: true
+ *   </dd>
+ *   
+ *   <dt>org.eclipse.jetty.util.log.stderr.SOURCE=(true|false)</dt>
+ *   <dd>Special Global Configuration, attempt to print the java source file name and line number
+ *   where the logging event originated from.<br/>
+ *   Default: false
+ *   </dd>
+ *   
+ *   <dt>org.eclipse.jetty.util.log.stderr.LONG=(true|false)</dt>
+ *   <dd>Special Global Configuration, when true, output logging events to STDERR using
+ *   long form, fully qualified class names.  when false, use abbreviated package names<br/>
+ *   Default: false
+ *   </dd>
+ *   <dt>org.eclipse.jetty.util.log.stderr.ESCAPE=(true|false)</dt>
+ *   <dd>Global Configuration, when true output logging events to STDERR are always
+ *   escaped so that control characters are replaced with '?";  '\r' with '<' and '\n' replaced '|'<br/>
+ *   Default: true
+ *   </dd>
+ * </dl>
+ */
+@ManagedObject("Jetty StdErr Logging Implementation")
+public class StdErrLog extends AbstractLogger
+{
+    private static final String EOL = System.getProperty("line.separator");
+    private static DateCache _dateCache;
+    private static final Properties __props = new Properties();
+
+    private final static boolean __source = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.SOURCE",
+            Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE","false")));
+    private final static boolean __long = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.LONG","false"));
+    private final static boolean __escape = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.ESCAPE","true"));
+
+    static
+    {
+        __props.putAll(Log.__props);
+
+        String deprecatedProperties[] =
+        { "DEBUG", "org.eclipse.jetty.util.log.DEBUG", "org.eclipse.jetty.util.log.stderr.DEBUG" };
+
+        // Toss a message to users about deprecated system properties
+        for (String deprecatedProp : deprecatedProperties)
+        {
+            if (System.getProperty(deprecatedProp) != null)
+            {
+                System.err.printf("System Property [%s] has been deprecated! (Use org.eclipse.jetty.LEVEL=DEBUG instead)%n",deprecatedProp);
+            }
+        }
+
+        try
+        {
+            _dateCache = new DateCache("yyyy-MM-dd HH:mm:ss");
+        }
+        catch (Exception x)
+        {
+            x.printStackTrace(System.err);
+        }
+    }
+
+    public static final int LEVEL_ALL = 0;
+    public static final int LEVEL_DEBUG = 1;
+    public static final int LEVEL_INFO = 2;
+    public static final int LEVEL_WARN = 3;
+    public static final int LEVEL_OFF = 10;
+
+    private int _level = LEVEL_INFO;
+    // Level that this Logger was configured as (remembered in special case of .setDebugEnabled())
+    private int _configuredLevel;
+    private PrintStream _stderr = null;
+    private boolean _source = __source;
+    // Print the long form names, otherwise use abbreviated
+    private boolean _printLongNames = __long;
+    // The full log name, as provided by the system.
+    private final String _name;
+    // The abbreviated log name (used by default, unless _long is specified)
+    private final String _abbrevname;
+    private boolean _hideStacks = false;
+
+    /**
+     * Obtain a StdErrLog reference for the specified class, a convenience method used most often during testing to allow for control over a specific logger.
+     * <p>
+     * Must be actively using StdErrLog as the Logger implementation.
+     * 
+     * @param clazz
+     *            the Class reference for the logger to use.
+     * @return the StdErrLog logger
+     * @throws RuntimeException
+     *             if StdErrLog is not the active Logger implementation.
+     */
+    public static StdErrLog getLogger(Class<?> clazz)
+    {
+        Logger log = Log.getLogger(clazz);
+        if (log instanceof StdErrLog)
+        {
+            return (StdErrLog)log;
+        }
+        throw new RuntimeException("Logger for " + clazz + " is not of type StdErrLog");
+    }
+
+    /**
+     * Construct an anonymous StdErrLog (no name).
+     * <p>
+     * NOTE: Discouraged usage!
+     */
+    public StdErrLog()
+    {
+        this(null);
+    }
+
+    /**
+     * Construct a named StdErrLog using the {@link Log} defined properties
+     * 
+     * @param name
+     *            the name of the logger
+     */
+    public StdErrLog(String name)
+    {
+        this(name,__props);
+    }
+
+    /**
+     * Construct a named Logger using the provided properties to configure logger.
+     * 
+     * @param name
+     *            the name of the logger
+     * @param props
+     *            the configuration properties
+     */
+    public StdErrLog(String name, Properties props)
+    {
+        if (props!=null && props!=__props)
+            __props.putAll(props);
+        this._name = name == null?"":name;
+        this._abbrevname = condensePackageString(this._name);
+        this._level = getLoggingLevel(props,this._name);
+        this._configuredLevel = this._level;
+
+        try
+        {
+            String source = getLoggingProperty(props,_name,"SOURCE");
+            _source = source==null?__source:Boolean.parseBoolean(source);
+        }
+        catch (AccessControlException ace)
+        {
+            _source = __source;
+        }
+
+        try
+        {
+            // allow stacktrace display to be controlled by properties as well
+            String stacks = getLoggingProperty(props,_name,"STACKS");
+            _hideStacks = stacks==null?false:!Boolean.parseBoolean(stacks);
+        }
+        catch (AccessControlException ignore)
+        {
+            /* ignore */
+        }        
+    }
+
+    /**
+     * Get the Logging Level for the provided log name. Using the FQCN first, then each package segment from longest to
+     * shortest.
+     *
+     * @param props
+     *            the properties to check
+     * @param name
+     *            the name to get log for
+     * @return the logging level
+     */
+    public static int getLoggingLevel(Properties props, final String name)
+    {
+        // Calculate the level this named logger should operate under.
+        // Checking with FQCN first, then each package segment from longest to shortest.
+        String nameSegment = name;
+
+        while ((nameSegment != null) && (nameSegment.length() > 0))
+        {
+            String levelStr = props.getProperty(nameSegment + ".LEVEL");
+            // System.err.printf("[StdErrLog.CONFIG] Checking for property [%s.LEVEL] = %s%n",nameSegment,levelStr);
+            int level = getLevelId(nameSegment + ".LEVEL",levelStr);
+            if (level != (-1))
+            {
+                return level;
+            }
+
+            // Trim and try again.
+            int idx = nameSegment.lastIndexOf('.');
+            if (idx >= 0)
+            {
+                nameSegment = nameSegment.substring(0,idx);
+            }
+            else
+            {
+                nameSegment = null;
+            }
+        }
+
+        // Default Logging Level
+        return getLevelId("log.LEVEL",props.getProperty("log.LEVEL","INFO"));
+    }
+    
+    public static String getLoggingProperty(Properties props, String name, String property)
+    {
+        // Calculate the level this named logger should operate under.
+        // Checking with FQCN first, then each package segment from longest to shortest.
+        String nameSegment = name;
+
+        while ((nameSegment != null) && (nameSegment.length() > 0))
+        {
+            String s = props.getProperty(nameSegment+"."+property);
+            if (s!=null)
+                return s;
+
+            // Trim and try again.
+            int idx = nameSegment.lastIndexOf('.');
+            nameSegment = (idx >= 0)?nameSegment.substring(0,idx):null;
+        }
+
+        return null;
+    }
+
+    protected static int getLevelId(String levelSegment, String levelName)
+    {
+        if (levelName == null)
+        {
+            return -1;
+        }
+        String levelStr = levelName.trim();
+        if ("ALL".equalsIgnoreCase(levelStr))
+        {
+            return LEVEL_ALL;
+        }
+        else if ("DEBUG".equalsIgnoreCase(levelStr))
+        {
+            return LEVEL_DEBUG;
+        }
+        else if ("INFO".equalsIgnoreCase(levelStr))
+        {
+            return LEVEL_INFO;
+        }
+        else if ("WARN".equalsIgnoreCase(levelStr))
+        {
+            return LEVEL_WARN;
+        }
+        else if ("OFF".equalsIgnoreCase(levelStr))
+        {
+            return LEVEL_OFF;
+        }
+
+        System.err.println("Unknown StdErrLog level [" + levelSegment + "]=[" + levelStr + "], expecting only [ALL, DEBUG, INFO, WARN, OFF] as values.");
+        return -1;
+    }
+
+    /**
+     * Condenses a classname by stripping down the package name to just the first character of each package name
+     * segment.Configured
+     * <p>
+     *
+     * <pre>
+     * Examples:
+     * "org.eclipse.jetty.test.FooTest"           = "oejt.FooTest"
+     * "org.eclipse.jetty.server.logging.LogTest" = "orjsl.LogTest"
+     * </pre>
+     *
+     * @param classname
+     *            the fully qualified class name
+     * @return the condensed name
+     */
+    protected static String condensePackageString(String classname)
+    {
+        String parts[] = classname.split("\\.");
+        StringBuilder dense = new StringBuilder();
+        for (int i = 0; i < (parts.length - 1); i++)
+        {
+            dense.append(parts[i].charAt(0));
+        }
+        if (dense.length() > 0)
+        {
+            dense.append('.');
+        }
+        dense.append(parts[parts.length - 1]);
+        return dense.toString();
+    }
+
+    public String getName()
+    {
+        return _name;
+    }
+
+    public void setPrintLongNames(boolean printLongNames)
+    {
+        this._printLongNames = printLongNames;
+    }
+
+    public boolean isPrintLongNames()
+    {
+        return this._printLongNames;
+    }
+
+    public boolean isHideStacks()
+    {
+        return _hideStacks;
+    }
+
+    public void setHideStacks(boolean hideStacks)
+    {
+        _hideStacks = hideStacks;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Is the source of a log, logged
+     *
+     * @return true if the class, method, file and line number of a log is logged.
+     */
+    public boolean isSource()
+    {
+        return _source;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set if a log source is logged.
+     *
+     * @param source
+     *            true if the class, method, file and line number of a log is logged.
+     */
+    public void setSource(boolean source)
+    {
+        _source = source;
+    }
+
+    public void warn(String msg, Object... args)
+    {
+        if (_level <= LEVEL_WARN)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":WARN:",msg,args);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    public void warn(Throwable thrown)
+    {
+        warn("",thrown);
+    }
+
+    public void warn(String msg, Throwable thrown)
+    {
+        if (_level <= LEVEL_WARN)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":WARN:",msg,thrown);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    public void info(String msg, Object... args)
+    {
+        if (_level <= LEVEL_INFO)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":INFO:",msg,args);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    public void info(Throwable thrown)
+    {
+        info("",thrown);
+    }
+
+    public void info(String msg, Throwable thrown)
+    {
+        if (_level <= LEVEL_INFO)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":INFO:",msg,thrown);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    @ManagedAttribute("is debug enabled for root logger Log.LOG")
+    public boolean isDebugEnabled()
+    {
+        return (_level <= LEVEL_DEBUG);
+    }
+
+    /**
+     * Legacy interface where a programmatic configuration of the logger level
+     * is done as a wholesale approach.
+     */
+    @Override
+    public void setDebugEnabled(boolean enabled)
+    {
+        if (enabled)
+        {
+            this._level = LEVEL_DEBUG;
+
+            for (Logger log : Log.getLoggers().values())
+            {                
+                if (log.getName().startsWith(getName()) && log instanceof StdErrLog)
+                    ((StdErrLog)log).setLevel(LEVEL_DEBUG);
+            }
+        }
+        else
+        {
+            this._level = this._configuredLevel;
+
+            for (Logger log : Log.getLoggers().values())
+            {
+                if (log.getName().startsWith(getName()) && log instanceof StdErrLog)
+                    ((StdErrLog)log).setLevel(((StdErrLog)log)._configuredLevel);
+            }
+        }
+    }
+
+    public int getLevel()
+    {
+        return _level;
+    }
+
+    /**
+     * Set the level for this logger.
+     * <p>
+     * Available values ({@link StdErrLog#LEVEL_ALL}, {@link StdErrLog#LEVEL_DEBUG}, {@link StdErrLog#LEVEL_INFO},
+     * {@link StdErrLog#LEVEL_WARN})
+     *
+     * @param level
+     *            the level to set the logger to
+     */
+    public void setLevel(int level)
+    {
+        this._level = level;
+    }
+
+    public void setStdErrStream(PrintStream stream)
+    {
+        this._stderr = stream==System.err?null:stream;
+    }
+
+    public void debug(String msg, Object... args)
+    {
+        if (_level <= LEVEL_DEBUG)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":DBUG:",msg,args);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    public void debug(String msg, long arg)
+    {
+        if (isDebugEnabled())
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":DBUG:",msg,arg);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+    
+    public void debug(Throwable thrown)
+    {
+        debug("",thrown);
+    }
+
+    public void debug(String msg, Throwable thrown)
+    {
+        if (_level <= LEVEL_DEBUG)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":DBUG:",msg,thrown);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    private void format(StringBuilder buffer, String level, String msg, Object... args)
+    {
+        long now = System.currentTimeMillis();
+        int ms=(int)(now%1000);
+        String d = _dateCache.formatNow(now);
+        tag(buffer,d,ms,level);
+        format(buffer,msg,args);
+    }
+
+    private void format(StringBuilder buffer, String level, String msg, Throwable thrown)
+    {
+        format(buffer,level,msg);
+        if (isHideStacks())
+        {
+            format(buffer,": "+String.valueOf(thrown));
+        }
+        else
+        {
+            format(buffer,thrown);
+        }
+    }
+
+    private void tag(StringBuilder buffer, String d, int ms, String tag)
+    {
+        buffer.setLength(0);
+        buffer.append(d);
+        if (ms > 99)
+        {
+            buffer.append('.');
+        }
+        else if (ms > 9)
+        {
+            buffer.append(".0");
+        }
+        else
+        {
+            buffer.append(".00");
+        }
+        buffer.append(ms).append(tag);
+        if (_printLongNames)
+        {
+            buffer.append(_name);
+        }
+        else
+        {
+            buffer.append(_abbrevname);
+        }
+        buffer.append(':');
+        buffer.append(Thread.currentThread().getName()).append(": ");
+        if (_source)
+        {
+            Throwable source = new Throwable();
+            StackTraceElement[] frames = source.getStackTrace();
+            for (int i = 0; i < frames.length; i++)
+            {
+                final StackTraceElement frame = frames[i];
+                String clazz = frame.getClassName();
+                if (clazz.equals(StdErrLog.class.getName()) || clazz.equals(Log.class.getName()))
+                {
+                    continue;
+                }
+                if (!_printLongNames && clazz.startsWith("org.eclipse.jetty."))
+                {
+                    buffer.append(condensePackageString(clazz));
+                }
+                else
+                {
+                    buffer.append(clazz);
+                }
+                buffer.append('#').append(frame.getMethodName());
+                if (frame.getFileName() != null)
+                {
+                    buffer.append('(').append(frame.getFileName()).append(':').append(frame.getLineNumber()).append(')');
+                }
+                buffer.append(':');
+                break;
+            }
+        }
+    }
+
+    private void format(StringBuilder builder, String msg, Object... args)
+    {
+        if (msg == null)
+        {
+            msg = "";
+            for (int i = 0; i < args.length; i++)
+            {
+                msg += "{} ";
+            }
+        }
+        String braces = "{}";
+        int start = 0;
+        for (Object arg : args)
+        {
+            int bracesIndex = msg.indexOf(braces,start);
+            if (bracesIndex < 0)
+            {
+                escape(builder,msg.substring(start));
+                builder.append(" ");
+                builder.append(arg);
+                start = msg.length();
+            }
+            else
+            {
+                escape(builder,msg.substring(start,bracesIndex));
+                builder.append(String.valueOf(arg));
+                start = bracesIndex + braces.length();
+            }
+        }
+        escape(builder,msg.substring(start));
+    }
+
+    private void escape(StringBuilder builder, String string)
+    {
+        if (__escape)
+        {
+            for (int i = 0; i < string.length(); ++i)
+            {
+                char c = string.charAt(i);
+                if (Character.isISOControl(c))
+                {
+                    if (c == '\n')
+                    {
+                        builder.append('|');
+                    }
+                    else if (c == '\r')
+                    {
+                        builder.append('<');
+                    }
+                    else
+                    {
+                        builder.append('?');
+                    }
+                }
+                else
+                {
+                    builder.append(c);
+                }
+            }
+        }
+        else
+            builder.append(string);
+    }
+
+    private void format(StringBuilder buffer, Throwable thrown)
+    {
+        if (thrown == null)
+        {
+            buffer.append("null");
+        }
+        else
+        {
+            buffer.append(EOL);
+            format(buffer,thrown.toString());
+            StackTraceElement[] elements = thrown.getStackTrace();
+            for (int i = 0; elements != null && i < elements.length; i++)
+            {
+                buffer.append(EOL).append("\tat ");
+                format(buffer,elements[i].toString());
+            }
+
+            Throwable cause = thrown.getCause();
+            if (cause != null && cause != thrown)
+            {
+                buffer.append(EOL).append("Caused by: ");
+                format(buffer,cause);
+            }
+        }
+    }
+
+
+    /**
+     * Create a Child Logger of this Logger.
+     */
+    @Override
+    protected Logger newLogger(String fullname)
+    {
+        StdErrLog logger = new StdErrLog(fullname);
+        // Preserve configuration for new loggers configuration
+        logger.setPrintLongNames(_printLongNames);
+        logger._stderr = this._stderr;
+
+        // Force the child to have any programmatic configuration
+        if (_level!=_configuredLevel)
+            logger._level=_level;
+
+        return logger;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder s = new StringBuilder();
+        s.append("StdErrLog:");
+        s.append(_name);
+        s.append(":LEVEL=");
+        switch (_level)
+        {
+            case LEVEL_ALL:
+                s.append("ALL");
+                break;
+            case LEVEL_DEBUG:
+                s.append("DEBUG");
+                break;
+            case LEVEL_INFO:
+                s.append("INFO");
+                break;
+            case LEVEL_WARN:
+                s.append("WARN");
+                break;
+            default:
+                s.append("?");
+                break;
+        }
+        return s.toString();
+    }
+
+    public static void setProperties(Properties props)
+    {
+        __props.clear();
+        __props.putAll(props);
+    }
+
+    public void ignore(Throwable ignored)
+    {
+        if (_level <= LEVEL_ALL)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":IGNORED:","",ignored);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/log/package-info.java b/lib/jetty/org/eclipse/jetty/util/log/package-info.java
new file mode 100644 (file)
index 0000000..27166b6
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Logging Integrations
+ */
+package org.eclipse.jetty.util.log;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/package-info.java b/lib/jetty/org/eclipse/jetty/util/package-info.java
new file mode 100644 (file)
index 0000000..6545cbe
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Utility Classes
+ */
+package org.eclipse.jetty.util;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java
new file mode 100644 (file)
index 0000000..0f6caf9
--- /dev/null
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+import java.awt.Toolkit;
+
+/**
+ * AWTLeakPreventer
+ *
+ * See https://issues.jboss.org/browse/AS7-3733
+ * 
+ * The java.awt.Toolkit class has a static field that is the default toolkit. 
+ * Creating the default toolkit causes the creation of an EventQueue, which has a 
+ * classloader field initialized by the thread context class loader. 
+ *
+ */
+public class AWTLeakPreventer extends AbstractLeakPreventer
+{
+   
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        LOG.debug("Pinning classloader for java.awt.EventQueue using "+loader);
+        Toolkit.getDefaultToolkit();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/AbstractLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/AbstractLeakPreventer.java
new file mode 100644 (file)
index 0000000..2322a76
--- /dev/null
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util.preventers;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * AbstractLeakPreventer
+ *
+ * Abstract base class for code that seeks to avoid pinning of webapp classloaders by using the jetty classloader to
+ * proactively call the code that pins them (generally pinned as static data members, or as static
+ * data members that are daemon threads (which use the context classloader)).
+ * 
+ * Instances of subclasses of this class should be set with Server.addBean(), which will
+ * ensure that they are called when the Server instance starts up, which will have the jetty
+ * classloader in scope.
+ *
+ */
+public abstract class AbstractLeakPreventer extends AbstractLifeCycle
+{
+    protected static final Logger LOG = Log.getLogger(AbstractLeakPreventer.class);
+    
+    /* ------------------------------------------------------------ */
+    abstract public void prevent(ClassLoader loader);
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        try
+        {
+            Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+            prevent(getClass().getClassLoader());
+            super.doStart();
+        }
+        finally
+        {
+            Thread.currentThread().setContextClassLoader(loader);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java
new file mode 100644 (file)
index 0000000..0cfd3c9
--- /dev/null
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+import javax.imageio.ImageIO;
+
+/**
+ * AppContextLeakPreventer
+ *
+ * Cause the classloader that is pinned by AppContext.getAppContext() to be 
+ * a container or system classloader, not a webapp classloader.
+ * 
+ * Inspired by Tomcat JreMemoryLeakPrevention.
+ */
+public class AppContextLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        LOG.debug("Pinning classloader for AppContext.getContext() with "+loader);
+        ImageIO.getUseCache();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/DOMLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/DOMLeakPreventer.java
new file mode 100644 (file)
index 0000000..5fee365
--- /dev/null
@@ -0,0 +1,56 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * DOMLeakPreventer
+ *
+ * See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6916498
+ * 
+ * Prevent the RuntimeException that is a static member of AbstractDOMParser
+ * from pinning a webapp classloader by causing it to be set here by a non-webapp classloader.
+ * 
+ * Note that according to the bug report, a heap dump may not identify the GCRoot, making 
+ * it difficult to identify the cause of the leak.
+ *
+ */
+public class DOMLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        try 
+        {
+            factory.newDocumentBuilder();
+        } 
+        catch (Exception e) 
+        {
+            LOG.warn(e);
+        }
+
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java
new file mode 100644 (file)
index 0000000..d229ba7
--- /dev/null
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util.preventers;
+
+import java.sql.DriverManager;
+
+
+/**
+ * DriverManagerLeakPreventer
+ *
+ * Cause DriverManager.getCallerClassLoader() to be called, which will pin the classloader.
+ * 
+ * Inspired by Tomcat JreMemoryLeakPrevention.
+ */
+public class DriverManagerLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        LOG.debug("Pinning DriverManager classloader with "+loader);
+        DriverManager.getDrivers();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java
new file mode 100644 (file)
index 0000000..6ea4de2
--- /dev/null
@@ -0,0 +1,64 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+import java.lang.reflect.Method;
+
+/**
+ * GCThreadLeakPreventer
+ *
+ * Prevents a call to sun.misc.GC.requestLatency pinning a webapp classloader
+ * by calling it with a non-webapp classloader. The problem appears to be that
+ * when this method is called, a daemon thread is created which takes the 
+ * context classloader. A known caller of this method is the RMI impl. See
+ * http://stackoverflow.com/questions/6626680/does-java-garbage-collection-log-entry-full-gc-system-mean-some-class-called
+ * 
+ * This preventer will start the thread with the longest possible interval, although
+ * subsequent calls can vary that. Recommend to only use this class if you're doing
+ * RMI.
+ * 
+ * Inspired by Tomcat JreMemoryLeakPrevention.
+ *
+ */
+public class GCThreadLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        try
+        {
+            Class clazz = Class.forName("sun.misc.GC");
+            Method requestLatency = clazz.getMethod("requestLatency", new Class[] {long.class});
+            requestLatency.invoke(null, Long.valueOf(Long.MAX_VALUE-1));
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.ignore(e);
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/Java2DLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/Java2DLeakPreventer.java
new file mode 100644 (file)
index 0000000..3a2ad82
--- /dev/null
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+/**
+ * Java2DLeakPreventer
+ *
+ * Prevent pinning of webapp classloader by pre-loading sun.java2d.Disposer class
+ * before webapp classloaders are created.
+ * 
+ * See https://issues.apache.org/bugzilla/show_bug.cgi?id=51687
+ *
+ */
+public class Java2DLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        try
+        {
+            Class.forName("sun.java2d.Disposer", true, loader);
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/LDAPLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/LDAPLeakPreventer.java
new file mode 100644 (file)
index 0000000..5c497d1
--- /dev/null
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+/**
+ * LDAPLeakPreventer
+ *
+ * If com.sun.jndi.LdapPoolManager class is loaded and the system property
+ * com.sun.jndi.ldap.connect.pool.timeout is set to a nonzero value, a daemon
+ * thread is started which can pin a webapp classloader if it is the first to
+ * load the LdapPoolManager.
+ * 
+ * Inspired by Tomcat JreMemoryLeakPrevention
+ *
+ */
+public class LDAPLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        try
+        {
+            Class.forName("com.sun.jndi.LdapPoolManager", true, loader);
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/LoginConfigurationLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/LoginConfigurationLeakPreventer.java
new file mode 100644 (file)
index 0000000..c1d9fe2
--- /dev/null
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+/**
+ * LoginConfigurationLeakPreventer
+ *
+ * The javax.security.auth.login.Configuration class keeps a static reference to the 
+ * thread context classloader. We prevent a webapp context classloader being used for
+ * that by invoking the classloading here.
+ * 
+ * Inspired by Tomcat JreMemoryLeakPrevention
+ */
+public class LoginConfigurationLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        try
+        {
+            Class.forName("javax.security.auth.login.Configuration", true, loader);
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/SecurityProviderLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/SecurityProviderLeakPreventer.java
new file mode 100644 (file)
index 0000000..9976c56
--- /dev/null
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+import java.security.Security;
+
+/**
+ * SecurityProviderLeakPreventer
+ *
+ * Some security providers, such as sun.security.pkcs11.SunPKCS11 start a deamon thread,
+ * which will use the thread context classloader. Load them here to ensure the classloader
+ * is not a webapp classloader.
+ *
+ * Inspired by Tomcat JreMemoryLeakPrevention
+ */
+public class SecurityProviderLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        Security.getProviders();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/package-info.java b/lib/jetty/org/eclipse/jetty/util/preventers/package-info.java
new file mode 100644 (file)
index 0000000..a6800e8
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Memory Leak Prevention Tooling
+ */
+package org.eclipse.jetty.util.preventers;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/BadResource.java b/lib/jetty/org/eclipse/jetty/util/resource/BadResource.java
new file mode 100644 (file)
index 0000000..72a9ed4
--- /dev/null
@@ -0,0 +1,130 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+
+/* ------------------------------------------------------------ */
+/** Bad Resource.
+ *
+ * A Resource that is returned for a bade URL.  Acts as a resource
+ * that does not exist and throws appropriate exceptions.
+ *
+ * 
+ */
+class BadResource extends URLResource
+{
+    /* ------------------------------------------------------------ */
+    private String _message=null;
+        
+    /* -------------------------------------------------------- */
+    BadResource(URL url,  String message)
+    {
+        super(url,null);
+        _message=message;
+    }
+    
+
+    /* -------------------------------------------------------- */
+    @Override
+    public boolean exists()
+    {
+        return false;
+    }
+        
+    /* -------------------------------------------------------- */
+    @Override
+    public long lastModified()
+    {
+        return -1;
+    }
+
+    /* -------------------------------------------------------- */
+    @Override
+    public boolean isDirectory()
+    {
+        return false;
+    }
+
+    /* --------------------------------------------------------- */
+    @Override
+    public long length()
+    {
+        return -1;
+    }
+        
+        
+    /* ------------------------------------------------------------ */
+    @Override
+    public File getFile()
+    {
+        return null;
+    }
+        
+    /* --------------------------------------------------------- */
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        throw new FileNotFoundException(_message);
+    }
+        
+    /* --------------------------------------------------------- */
+    @Override
+    public boolean delete()
+        throws SecurityException
+    {
+        throw new SecurityException(_message);
+    }
+
+    /* --------------------------------------------------------- */
+    @Override
+    public boolean renameTo( Resource dest)
+        throws SecurityException
+    {
+        throw new SecurityException(_message);
+    }
+
+    /* --------------------------------------------------------- */
+    @Override
+    public String[] list()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void copyTo(File destination)
+        throws IOException
+    {
+        throw new SecurityException(_message);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return super.toString()+"; BadResource="+_message;
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/EmptyResource.java b/lib/jetty/org/eclipse/jetty/util/resource/EmptyResource.java
new file mode 100644 (file)
index 0000000..3dad17b
--- /dev/null
@@ -0,0 +1,130 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.channels.ReadableByteChannel;
+
+/**
+ * EmptyResource
+ *
+ * Represents a resource that does does not refer to any file, url, jar etc. 
+ */
+public class EmptyResource extends Resource
+{
+    public static final Resource INSTANCE = new EmptyResource();
+    
+    private EmptyResource()
+    {
+    }
+
+    @Override
+    public boolean isContainedIn(Resource r) throws MalformedURLException
+    {
+        return false;
+    }
+
+    @Override
+    public void close()
+    {
+    }
+
+    @Override
+    public boolean exists()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isDirectory()
+    {
+        return false;
+    }
+
+    @Override
+    public long lastModified()
+    {
+        return 0;
+    }
+
+    @Override
+    public long length()
+    {
+        return 0;
+    }
+
+    @Override
+    public URL getURL()
+    {
+        return null;
+    }
+
+    @Override
+    public File getFile() throws IOException
+    {
+        return null;
+    }
+
+    @Override
+    public String getName()
+    {
+        return null;
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        return null;
+    }
+
+    @Override
+    public ReadableByteChannel getReadableByteChannel() throws IOException
+    {
+        return null;
+    }
+
+    @Override
+    public boolean delete() throws SecurityException
+    {
+        return false;
+    }
+
+    @Override
+    public boolean renameTo(Resource dest) throws SecurityException
+    {
+        return false;
+    }
+
+    @Override
+    public String[] list()
+    {
+        return null;
+    }
+
+    @Override
+    public Resource addPath(String path) throws IOException, MalformedURLException
+    {
+        return null;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/FileResource.java b/lib/jetty/org/eclipse/jetty/util/resource/FileResource.java
new file mode 100644 (file)
index 0000000..4498842
--- /dev/null
@@ -0,0 +1,429 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.StandardOpenOption;
+import java.security.Permission;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** File Resource.
+ *
+ * Handle resources of implied or explicit file type.
+ * This class can check for aliasing in the filesystem (eg case
+ * insensitivity).  By default this is turned on, or it can be controlled 
+ * by calling the static method @see FileResource#setCheckAliases(boolean)
+ * 
+ */
+public class FileResource extends Resource
+{
+    private static final Logger LOG = Log.getLogger(FileResource.class);
+
+    /* ------------------------------------------------------------ */
+    private final File _file;
+    private final String _uri;
+    private final URI _alias;
+    
+    /* -------------------------------------------------------- */
+    public FileResource(URL url)
+        throws IOException, URISyntaxException
+    {
+        File file;
+        try
+        {
+            // Try standard API to convert URL to file.
+            file =new File(url.toURI());
+        }
+        catch (URISyntaxException e) 
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            if (!url.toString().startsWith("file:"))
+                throw new IllegalArgumentException("!file:");
+            
+            LOG.ignore(e);
+            try
+            {
+                // Assume that File.toURL produced unencoded chars. So try encoding them.
+                String file_url="file:"+URIUtil.encodePath(url.toString().substring(5));           
+                URI uri = new URI(file_url);
+                if (uri.getAuthority()==null) 
+                    file = new File(uri);
+                else
+                    file = new File("//"+uri.getAuthority()+URIUtil.decodePath(url.getFile()));
+            }
+            catch (Exception e2)
+            {
+                LOG.ignore(e2);
+                // Still can't get the file.  Doh! try good old hack!
+                URLConnection connection=url.openConnection();
+                Permission perm = connection.getPermission();
+                file = new File(perm==null?url.getFile():perm.getName());
+            }
+        }
+        
+        _file=file;
+        _uri=normalizeURI(_file,url.toURI());
+        _alias=checkAlias(_file);
+    }
+
+    /* -------------------------------------------------------- */
+    public FileResource(URI uri)
+    {
+        File file=new File(uri);
+        _file=file;
+        URI file_uri=_file.toURI();
+        _uri=normalizeURI(_file,uri);
+        
+        if (!_uri.equals(file_uri.toString()))
+        {
+            // URI and File URI are different.  Is it just an encoding difference?
+            if (!file_uri.toString().equals(URIUtil.decodePath(uri.toString())))
+                 _alias=_file.toURI();
+            else
+                _alias=checkAlias(_file);
+        }
+        else
+            _alias=checkAlias(_file);
+    }
+
+    /* -------------------------------------------------------- */
+    FileResource(File file)
+    {
+        _file=file;
+        _uri=normalizeURI(_file,_file.toURI());
+        _alias=checkAlias(_file);
+    }
+
+    /* -------------------------------------------------------- */
+    private static String normalizeURI(File file, URI uri)
+    {
+        String u =uri.toASCIIString();
+        if (file.isDirectory())
+        {
+            if(!u.endsWith("/"))
+                u+="/";
+        } 
+        else if (file.exists() && u.endsWith("/"))
+            u=u.substring(0,u.length()-1);
+        return u;
+    }
+
+    /* -------------------------------------------------------- */
+    private static URI checkAlias(File file)
+    {
+        try
+        {
+            String abs=file.getAbsolutePath();
+            String can=file.getCanonicalPath();
+
+            if (!abs.equals(can))
+            {
+                LOG.debug("ALIAS abs={} can={}",abs,can);
+
+                URI alias=new File(can).toURI();
+                // Have to encode the path as File.toURI does not!
+                return new URI("file://"+URIUtil.encodePath(alias.getPath()));  
+            }
+        }
+        catch(Exception e)
+        {
+            LOG.warn("bad alias for {}: {}",file,e.toString());
+            LOG.debug(e);
+            try
+            {
+                return new URI("http://eclipse.org/bad/canonical/alias");
+            }
+            catch(Exception e2)
+            {
+                LOG.ignore(e2);
+                throw new RuntimeException(e);
+            }
+        }
+
+        return null;
+    }
+    
+    /* -------------------------------------------------------- */
+    @Override
+    public Resource addPath(String path)
+        throws IOException,MalformedURLException
+    {
+        path = org.eclipse.jetty.util.URIUtil.canonicalPath(path);
+
+        if (path==null)
+            throw new MalformedURLException();   
+        
+        if ("/".equals(path))
+            return this;
+        
+        path=URIUtil.encodePath(path);
+        // The encoded path should be a suffix of the resource (give or take a directory / )
+        URI uri;
+        try
+        {
+            if (_file.isDirectory())
+            {
+                // treat all paths being added as relative
+                uri=new URI(URIUtil.addPaths(_uri,path));
+            }
+            else
+            {
+                uri=new URI(_uri+path);
+            }
+        }
+        catch(final URISyntaxException e)
+        {
+            throw new MalformedURLException(){{initCause(e);}};
+        }
+
+        return new FileResource(uri);
+    }
+   
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public URI getAlias()
+    {
+        return _alias;
+    }
+    
+    /* -------------------------------------------------------- */
+    /**
+     * Returns true if the resource exists.
+     */
+    @Override
+    public boolean exists()
+    {
+        return _file.exists();
+    }
+        
+    /* -------------------------------------------------------- */
+    /**
+     * Returns the last modified time
+     */
+    @Override
+    public long lastModified()
+    {
+        return _file.lastModified();
+    }
+
+    /* -------------------------------------------------------- */
+    /**
+     * Returns true if the resource is a container/directory.
+     */
+    @Override
+    public boolean isDirectory()
+    {
+        return _file.exists() && _file.isDirectory() || _uri.endsWith("/");
+    }
+
+    /* --------------------------------------------------------- */
+    /**
+     * Return the length of the resource
+     */
+    @Override
+    public long length()
+    {
+        return _file.length();
+    }
+        
+
+    /* --------------------------------------------------------- */
+    /**
+     * Returns the name of the resource
+     */
+    @Override
+    public String getName()
+    {
+        return _file.getAbsolutePath();
+    }
+        
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an File representing the given resource or NULL if this
+     * is not possible.
+     */
+    @Override
+    public File getFile()
+    {
+        return _file;
+    }
+        
+    /* --------------------------------------------------------- */
+    /**
+     * Returns an input stream to the resource
+     */
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        return new FileInputStream(_file);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public ReadableByteChannel getReadableByteChannel() throws IOException
+    {
+        return FileChannel.open(_file.toPath(),StandardOpenOption.READ);
+    }
+        
+    /* --------------------------------------------------------- */
+    /**
+     * Deletes the given resource
+     */
+    @Override
+    public boolean delete()
+        throws SecurityException
+    {
+        return _file.delete();
+    }
+
+    /* --------------------------------------------------------- */
+    /**
+     * Rename the given resource
+     */
+    @Override
+    public boolean renameTo( Resource dest)
+        throws SecurityException
+    {
+        if( dest instanceof FileResource)
+            return _file.renameTo( ((FileResource)dest)._file);
+        else
+            return false;
+    }
+
+    /* --------------------------------------------------------- */
+    /**
+     * Returns a list of resources contained in the given resource
+     */
+    @Override
+    public String[] list()
+    {
+        String[] list =_file.list();
+        if (list==null)
+            return null;
+        for (int i=list.length;i-->0;)
+        {
+            if (new File(_file,list[i]).isDirectory() &&
+                !list[i].endsWith("/"))
+                list[i]+="/";
+        }
+        return list;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param o
+     * @return <code>true</code> of the object <code>o</code> is a {@link FileResource} pointing to the same file as this resource. 
+     */
+    @Override
+    public boolean equals( Object o)
+    {
+        if (this == o)
+            return true;
+
+        if (null == o || ! (o instanceof FileResource))
+            return false;
+
+        FileResource f=(FileResource)o;
+        return f._file == _file || (null != _file && _file.equals(f._file));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the hashcode.
+     */
+    @Override
+    public int hashCode()
+    {
+       return null == _file ? super.hashCode() : _file.hashCode();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void copyTo(File destination)
+        throws IOException
+    {
+        if (isDirectory())
+        {
+            IO.copyDir(getFile(),destination);
+        }
+        else
+        {
+            if (destination.exists())
+                throw new IllegalArgumentException(destination+" exists");
+            IO.copy(getFile(),destination);
+        }
+    }
+
+    @Override
+    public boolean isContainedIn(Resource r) throws MalformedURLException
+    {
+        return false;
+    }
+
+    @Override
+    public void close()
+    {
+    }
+
+    @Override
+    public URL getURL()
+    {
+        try
+        {
+            return new URL(_uri);
+        }
+        catch (MalformedURLException e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+    
+    @Override
+    public URI getURI()
+    {
+        return _file.toURI();
+    }
+
+    @Override
+    public String toString()
+    {
+        return _uri;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/JarFileResource.java b/lib/jetty/org/eclipse/jetty/util/resource/JarFileResource.java
new file mode 100644 (file)
index 0000000..434aa88
--- /dev/null
@@ -0,0 +1,412 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+class JarFileResource extends JarResource
+{
+    private static final Logger LOG = Log.getLogger(JarFileResource.class);
+    private JarFile _jarFile;
+    private File _file;
+    private String[] _list;
+    private JarEntry _entry;
+    private boolean _directory;
+    private String _jarUrl;
+    private String _path;
+    private boolean _exists;
+    
+    /* -------------------------------------------------------- */
+    protected JarFileResource(URL url)
+    {
+        super(url);
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected JarFileResource(URL url, boolean useCaches)
+    {
+        super(url, useCaches);
+    }
+   
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public synchronized void close()
+    {
+        _list=null;
+        _entry=null;
+        _file=null;
+        //if the jvm is not doing url caching, then the JarFiles will not be cached either,
+        //and so they are safe to close
+        if (!getUseCaches())
+        {
+            if ( _jarFile != null )
+            {
+                try
+                {
+                    LOG.debug("Closing JarFile "+_jarFile.getName());
+                    _jarFile.close();
+                }
+                catch ( IOException ioe )
+                {
+                    LOG.ignore(ioe);
+                }
+            }
+        }
+        _jarFile=null;
+        super.close();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected synchronized boolean checkConnection()
+    {
+        try
+        {
+            super.checkConnection();
+        }
+        finally
+        {
+            if (_jarConnection==null)
+            {
+                _entry=null;
+                _file=null;
+                _jarFile=null;
+                _list=null;
+            }
+        }
+        return _jarFile!=null;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected synchronized void newConnection()
+        throws IOException
+    {
+        super.newConnection();
+        
+        _entry=null;
+        _file=null;
+        _jarFile=null;
+        _list=null;
+        
+        int sep = _urlString.indexOf("!/");
+        _jarUrl=_urlString.substring(0,sep+2);
+        _path=_urlString.substring(sep+2);
+        if (_path.length()==0)
+            _path=null;   
+        _jarFile=_jarConnection.getJarFile();
+        _file=new File(_jarFile.getName());
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the represented resource exists.
+     */
+    @Override
+    public boolean exists()
+    {
+        if (_exists)
+            return true;
+
+        if (_urlString.endsWith("!/"))
+        {
+            
+            String file_url=_urlString.substring(4,_urlString.length()-2);
+            try{return newResource(file_url).exists();}
+            catch(Exception e) {LOG.ignore(e); return false;}
+        }
+        
+        boolean check=checkConnection();
+        
+        // Is this a root URL?
+        if (_jarUrl!=null && _path==null)
+        {
+            // Then if it exists it is a directory
+            _directory=check;
+            return true;
+        }
+        else 
+        {
+            // Can we find a file for it?
+            JarFile jarFile=null;
+            if (check)
+                // Yes
+                jarFile=_jarFile;
+            else
+            {
+                // No - so lets look if the root entry exists.
+                try
+                {
+                    JarURLConnection c=(JarURLConnection)((new URL(_jarUrl)).openConnection());
+                    c.setUseCaches(getUseCaches());
+                    jarFile=c.getJarFile();
+                }
+                catch(Exception e)
+                {
+                       LOG.ignore(e);
+                }
+            }
+
+            // Do we need to look more closely?
+            if (jarFile!=null && _entry==null && !_directory)
+            {
+                // OK - we have a JarFile, lets look at the entries for our path
+                Enumeration<JarEntry> e=jarFile.entries();
+                while(e.hasMoreElements())
+                {
+                    JarEntry entry = e.nextElement();
+                    String name=entry.getName().replace('\\','/');
+                    
+                    // Do we have a match
+                    if (name.equals(_path))
+                    {
+                        _entry=entry;
+                        // Is the match a directory
+                        _directory=_path.endsWith("/");
+                        break;
+                    }
+                    else if (_path.endsWith("/"))
+                    {
+                        if (name.startsWith(_path))
+                        {
+                            _directory=true;
+                            break;
+                        }
+                    }
+                    else if (name.startsWith(_path) && name.length()>_path.length() && name.charAt(_path.length())=='/')
+                    {
+                        _directory=true;
+                        break;
+                    }
+                }
+            }
+        }    
+        
+        _exists= ( _directory || _entry!=null);
+        return _exists;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the represented resource is a container/directory.
+     * If the resource is not a file, resources ending with "/" are
+     * considered directories.
+     */
+    @Override
+    public boolean isDirectory()
+    {
+        return _urlString.endsWith("/") || exists() && _directory;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the last modified time
+     */
+    @Override
+    public long lastModified()
+    {
+        if (checkConnection() && _file!=null)
+        {
+            if (exists() && _entry!=null)
+                return _entry.getTime();
+            return _file.lastModified();
+        }
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public synchronized String[] list()
+    {
+        if (isDirectory() && _list==null)
+        {
+            List<String> list = null;
+            try
+            {
+                list = listEntries();
+            }
+            catch (Exception e)
+            {
+                //Sun's JarURLConnection impl for jar: protocol will close a JarFile in its connect() method if
+                //useCaches == false (eg someone called URLConnection with defaultUseCaches==true).
+                //As their sun.net.www.protocol.jar package caches JarFiles and/or connections, we can wind up in 
+                //the situation where the JarFile we have remembered in our _jarFile member has actually been closed
+                //by other code.
+                //So, do one retry to drop a connection and get a fresh JarFile
+                LOG.warn("Retrying list:"+e);
+                LOG.debug(e);
+                release();
+                list = listEntries();
+            }
+
+            if (list != null)
+            {
+                _list=new String[list.size()];
+                list.toArray(_list);
+            }  
+        }
+        return _list;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private List<String> listEntries ()
+    {
+        checkConnection();
+        
+        ArrayList<String> list = new ArrayList<String>(32);
+        JarFile jarFile=_jarFile;
+        if(jarFile==null)
+        {
+            try
+            {
+                JarURLConnection jc=(JarURLConnection)((new URL(_jarUrl)).openConnection());
+                jc.setUseCaches(getUseCaches());
+                jarFile=jc.getJarFile();
+            }
+            catch(Exception e)
+            {
+
+                e.printStackTrace();
+                 LOG.ignore(e);
+            }
+                if(jarFile==null)
+                    throw new IllegalStateException();
+        }
+        
+        Enumeration<JarEntry> e=jarFile.entries();
+        String dir=_urlString.substring(_urlString.indexOf("!/")+2);
+        while(e.hasMoreElements())
+        {
+            JarEntry entry = e.nextElement();               
+            String name=entry.getName().replace('\\','/');               
+            if(!name.startsWith(dir) || name.length()==dir.length())
+            {
+                continue;
+            }
+            String listName=name.substring(dir.length());               
+            int dash=listName.indexOf('/');
+            if (dash>=0)
+            {
+                //when listing jar:file urls, you get back one
+                //entry for the dir itself, which we ignore
+                if (dash==0 && listName.length()==1)
+                    continue;
+                //when listing jar:file urls, all files and
+                //subdirs have a leading /, which we remove
+                if (dash==0)
+                    listName=listName.substring(dash+1, listName.length());
+                else
+                    listName=listName.substring(0,dash+1);
+                
+                if (list.contains(listName))
+                    continue;
+            }
+            
+            list.add(listName);
+        }
+        
+        return list;
+    }
+    
+    
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Return the length of the resource
+     */
+    @Override
+    public long length()
+    {
+        if (isDirectory())
+            return -1;
+
+        if (_entry!=null)
+            return _entry.getSize();
+        
+        return -1;
+    }
+
+    
+    /**
+     * Take a Resource that possibly might use URLConnection caching
+     * and turn it into one that doesn't.
+     * @param resource
+     * @return the non-caching resource
+     */
+    public static Resource getNonCachingResource (Resource resource)
+    {
+        if (!(resource instanceof JarFileResource))
+            return resource;
+        
+        JarFileResource oldResource = (JarFileResource)resource;
+        
+        JarFileResource newResource = new JarFileResource(oldResource.getURL(), false);
+        return newResource;
+        
+    }
+    
+    /**
+     * Check if this jar:file: resource is contained in the
+     * named resource. Eg <code>jar:file:///a/b/c/foo.jar!/x.html</code> isContainedIn <code>file:///a/b/c/foo.jar</code>
+     * @param resource
+     * @return true if resource is contained in the named resource
+     * @throws MalformedURLException
+     */
+    @Override
+    public boolean isContainedIn (Resource resource) 
+    throws MalformedURLException
+    {
+        String string = _urlString;
+        int index = string.indexOf("!/");
+        if (index > 0)
+            string = string.substring(0,index);
+        if (string.startsWith("jar:"))
+            string = string.substring(4);
+        URL url = new URL(string);
+        return url.sameFile(resource.getURL());     
+    }
+}
+
+
+
+
+
+
+
+
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/JarResource.java b/lib/jetty/org/eclipse/jetty/util/resource/JarResource.java
new file mode 100644 (file)
index 0000000..3dbb70a
--- /dev/null
@@ -0,0 +1,269 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+public class JarResource extends URLResource
+{
+    private static final Logger LOG = Log.getLogger(JarResource.class);
+    protected JarURLConnection _jarConnection;
+    
+    /* -------------------------------------------------------- */
+    protected JarResource(URL url)
+    {
+        super(url,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected JarResource(URL url, boolean useCaches)
+    {
+        super(url, null, useCaches);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public synchronized void close()
+    {
+        _jarConnection=null;
+        super.close();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected synchronized boolean checkConnection()
+    {
+        super.checkConnection();
+        try
+        {
+            if (_jarConnection!=_connection)
+                newConnection();
+        }
+        catch(IOException e)
+        {
+            LOG.ignore(e);
+            _jarConnection=null;
+        }
+        
+        return _jarConnection!=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @throws IOException Sub-classes of <code>JarResource</code> may throw an IOException (or subclass) 
+     */
+    protected void newConnection() throws IOException
+    {
+        _jarConnection=(JarURLConnection)_connection;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the respresenetd resource exists.
+     */
+    @Override
+    public boolean exists()
+    {
+        if (_urlString.endsWith("!/"))
+            return checkConnection();
+        else
+            return super.exists();
+    }    
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public File getFile()
+        throws IOException
+    {
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public InputStream getInputStream()
+        throws java.io.IOException
+    {     
+        checkConnection();
+        if (!_urlString.endsWith("!/"))
+            return new FilterInputStream(super.getInputStream()) 
+            {
+                @Override
+                public void close() throws IOException {this.in=IO.getClosedStream();}
+            };
+
+        URL url = new URL(_urlString.substring(4,_urlString.length()-2));      
+        InputStream is = url.openStream();
+        return is;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void copyTo(File directory)
+        throws IOException
+    {
+        if (!exists())
+            return;
+        
+        if(LOG.isDebugEnabled())
+            LOG.debug("Extract "+this+" to "+directory);
+        
+        String urlString = this.getURL().toExternalForm().trim();
+        int endOfJarUrl = urlString.indexOf("!/");
+        int startOfJarUrl = (endOfJarUrl >= 0?4:0);
+        
+        if (endOfJarUrl < 0)
+            throw new IOException("Not a valid jar url: "+urlString);
+        
+        URL jarFileURL = new URL(urlString.substring(startOfJarUrl, endOfJarUrl));
+        String subEntryName = (endOfJarUrl+2 < urlString.length() ? urlString.substring(endOfJarUrl + 2) : null);
+        boolean subEntryIsDir = (subEntryName != null && subEntryName.endsWith("/")?true:false);
+      
+        if (LOG.isDebugEnabled()) 
+            LOG.debug("Extracting entry = "+subEntryName+" from jar "+jarFileURL);
+        
+        try (InputStream is = jarFileURL.openConnection().getInputStream();
+                JarInputStream jin = new JarInputStream(is))
+        {
+            JarEntry entry;
+            boolean shouldExtract;
+            while((entry=jin.getNextJarEntry())!=null)
+            {
+                String entryName = entry.getName();
+                if ((subEntryName != null) && (entryName.startsWith(subEntryName)))
+                {
+                    // is the subentry really a dir?
+                    if (!subEntryIsDir && subEntryName.length()+1==entryName.length() && entryName.endsWith("/"))
+                            subEntryIsDir=true;
+
+                    //if there is a particular subEntry that we are looking for, only
+                    //extract it.
+                    if (subEntryIsDir)
+                    {
+                        //if it is a subdirectory we are looking for, then we
+                        //are looking to extract its contents into the target
+                        //directory. Remove the name of the subdirectory so
+                        //that we don't wind up creating it too.
+                        entryName = entryName.substring(subEntryName.length());
+                        if (!entryName.equals(""))
+                        {
+                            //the entry is
+                            shouldExtract = true;
+                        }
+                        else
+                            shouldExtract = false;
+                    }
+                    else
+                        shouldExtract = true;
+                }
+                else if ((subEntryName != null) && (!entryName.startsWith(subEntryName)))
+                {
+                    //there is a particular entry we are looking for, and this one
+                    //isn't it
+                    shouldExtract = false;
+                }
+                else
+                {
+                    //we are extracting everything
+                    shouldExtract =  true;
+                }
+
+                if (!shouldExtract)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Skipping entry: "+entryName);
+                    continue;
+                }
+
+                String dotCheck = entryName.replace('\\', '/');
+                dotCheck = URIUtil.canonicalPath(dotCheck);
+                if (dotCheck == null)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Invalid entry: "+entryName);
+                    continue;
+                }
+
+                File file=new File(directory,entryName);
+
+                if (entry.isDirectory())
+                {
+                    // Make directory
+                    if (!file.exists())
+                        file.mkdirs();
+                }
+                else
+                {
+                    // make directory (some jars don't list dirs)
+                    File dir = new File(file.getParent());
+                    if (!dir.exists())
+                        dir.mkdirs();
+
+                    // Make file
+                    try (OutputStream fout = new FileOutputStream(file))
+                    {
+                        IO.copy(jin,fout);
+                    }
+
+                    // touch the file.
+                    if (entry.getTime()>=0)
+                        file.setLastModified(entry.getTime());
+                }
+            }
+
+            if ((subEntryName == null) || (subEntryName != null && subEntryName.equalsIgnoreCase("META-INF/MANIFEST.MF")))
+            {
+                Manifest manifest = jin.getManifest();
+                if (manifest != null)
+                {
+                    File metaInf = new File (directory, "META-INF");
+                    metaInf.mkdir();
+                    File f = new File(metaInf, "MANIFEST.MF");
+                    try (OutputStream fout = new FileOutputStream(f))
+                    {
+                        manifest.write(fout);
+                    }
+                }
+            }
+        }
+    }   
+    
+    public static Resource newJarResource(Resource resource) throws IOException
+    {
+        if (resource instanceof JarResource)
+            return resource;
+        return Resource.newResource("jar:" + resource + "!/");
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/Resource.java b/lib/jetty/org/eclipse/jetty/util/resource/Resource.java
new file mode 100644 (file)
index 0000000..b36a935
--- /dev/null
@@ -0,0 +1,709 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.channels.ReadableByteChannel;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * Abstract resource class.
+ * <p>
+ * This class provides a resource abstraction, where a resource may be
+ * a file, a URL or an entry in a jar file.
+ * </p>
+ */
+public abstract class Resource implements ResourceFactory, Closeable
+{
+    private static final Logger LOG = Log.getLogger(Resource.class);
+    public static boolean __defaultUseCaches = true;
+    volatile Object _associate;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Change the default setting for url connection caches.
+     * Subsequent URLConnections will use this default.
+     * @param useCaches
+     */
+    public static void setDefaultUseCaches (boolean useCaches)
+    {
+        __defaultUseCaches=useCaches;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static boolean getDefaultUseCaches ()
+    {
+        return __defaultUseCaches;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Construct a resource from a uri.
+     * @param uri A URI.
+     * @return A Resource object.
+     * @throws MalformedURLException Problem accessing URI
+     */
+    public static Resource newResource(URI uri)
+        throws MalformedURLException
+    {
+        return newResource(uri.toURL());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Construct a resource from a url.
+     * @param url A URL.
+     * @return A Resource object.
+     */
+    public static Resource newResource(URL url)
+    {
+        return newResource(url, __defaultUseCaches);
+    }
+    
+    /* ------------------------------------------------------------ */   
+    /**
+     * Construct a resource from a url.
+     * @param url the url for which to make the resource
+     * @param useCaches true enables URLConnection caching if applicable to the type of resource
+     * @return
+     */
+    static Resource newResource(URL url, boolean useCaches)
+    {
+        if (url==null)
+            return null;
+
+        String url_string=url.toExternalForm();
+        if( url_string.startsWith( "file:"))
+        {
+            try
+            {
+                FileResource fileResource= new FileResource(url);
+                return fileResource;
+            }
+            catch(Exception e)
+            {
+                LOG.warn(e.toString());
+                LOG.debug(Log.EXCEPTION,e);
+                return new BadResource(url,e.toString());
+            }
+        }
+        else if( url_string.startsWith( "jar:file:"))
+        {
+            return new JarFileResource(url, useCaches);
+        }
+        else if( url_string.startsWith( "jar:"))
+        {
+            return new JarResource(url, useCaches);
+        }
+
+        return new URLResource(url,null,useCaches);
+    }
+
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Construct a resource from a string.
+     * @param resource A URL or filename.
+     * @throws MalformedURLException Problem accessing URI
+     * @return A Resource object.
+     */
+    public static Resource newResource(String resource)
+        throws MalformedURLException
+    {
+        return newResource(resource, __defaultUseCaches);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Construct a resource from a string.
+     * @param resource A URL or filename.
+     * @param useCaches controls URLConnection caching
+     * @return A Resource object.
+     * @throws MalformedURLException Problem accessing URI
+     */
+    public static Resource newResource(String resource, boolean useCaches)       
+        throws MalformedURLException
+    {
+        URL url=null;
+        try
+        {
+            // Try to format as a URL?
+            url = new URL(resource);
+        }
+        catch(MalformedURLException e)
+        {
+            if(!resource.startsWith("ftp:") &&
+               !resource.startsWith("file:") &&
+               !resource.startsWith("jar:"))
+            {
+                try
+                {
+                    // It's a file.
+                    if (resource.startsWith("./"))
+                        resource=resource.substring(2);
+                    
+                    File file=new File(resource).getCanonicalFile();
+                    return new FileResource(file);
+                }
+                catch(Exception e2)
+                {
+                    LOG.debug(Log.EXCEPTION,e2);
+                    throw e;
+                }
+            }
+            else
+            {
+                LOG.warn("Bad Resource: "+resource);
+                throw e;
+            }
+        }
+
+        return newResource(url);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static Resource newResource(File file)
+    {
+        return new FileResource(file);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Construct a system resource from a string.
+     * The resource is tried as classloader resource before being
+     * treated as a normal resource.
+     * @param resource Resource as string representation 
+     * @return The new Resource
+     * @throws IOException Problem accessing resource.
+     */
+    public static Resource newSystemResource(String resource)
+        throws IOException
+    {
+        URL url=null;
+        // Try to format as a URL?
+        ClassLoader loader=Thread.currentThread().getContextClassLoader();
+        if (loader!=null)
+        {
+            try
+            {
+                url = loader.getResource(resource);
+                if (url == null && resource.startsWith("/"))
+                    url = loader.getResource(resource.substring(1));
+            }
+            catch (IllegalArgumentException e)
+            {
+                // Catches scenario where a bad Windows path like "C:\dev" is
+                // improperly escaped, which various downstream classloaders
+                // tend to have a problem with
+                url = null;
+            }
+        }
+        if (url==null)
+        {
+            loader=Resource.class.getClassLoader();
+            if (loader!=null)
+            {
+                url=loader.getResource(resource);
+                if (url==null && resource.startsWith("/"))
+                    url=loader.getResource(resource.substring(1));
+            }
+        }
+        
+        if (url==null)
+        {
+            url=ClassLoader.getSystemResource(resource);
+            if (url==null && resource.startsWith("/"))
+                url=ClassLoader.getSystemResource(resource.substring(1));
+        }
+        
+        if (url==null)
+            return null;
+        
+        return newResource(url);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Find a classpath resource.
+     */
+    public static Resource newClassPathResource(String resource)
+    {
+        return newClassPathResource(resource,true,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Find a classpath resource.
+     * The {@link java.lang.Class#getResource(String)} method is used to lookup the resource. If it is not
+     * found, then the {@link Loader#getResource(Class, String)} method is used.
+     * If it is still not found, then {@link ClassLoader#getSystemResource(String)} is used.
+     * Unlike {@link ClassLoader#getSystemResource(String)} this method does not check for normal resources.
+     * @param name The relative name of the resource
+     * @param useCaches True if URL caches are to be used.
+     * @param checkParents True if forced searching of parent Classloaders is performed to work around 
+     * loaders with inverted priorities
+     * @return Resource or null
+     */
+    public static Resource newClassPathResource(String name,boolean useCaches,boolean checkParents)
+    {
+        URL url=Resource.class.getResource(name);
+        
+        if (url==null)
+            url=Loader.getResource(Resource.class,name);
+        if (url==null)
+            return null;
+        return newResource(url,useCaches);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static boolean isContainedIn (Resource r, Resource containingResource) throws MalformedURLException
+    {
+        return r.isContainedIn(containingResource);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void finalize()
+    {
+        close();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public abstract boolean isContainedIn (Resource r) throws MalformedURLException;
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Release any temporary resources held by the resource.
+     * @deprecated use {@link #close()}
+     */
+    public final void release()
+    {
+        close();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Release any temporary resources held by the resource.
+     */
+    @Override
+    public abstract void close();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the respresened resource exists.
+     */
+    public abstract boolean exists();
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the respresenetd resource is a container/directory.
+     * If the resource is not a file, resources ending with "/" are
+     * considered directories.
+     */
+    public abstract boolean isDirectory();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the last modified time
+     */
+    public abstract long lastModified();
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Return the length of the resource
+     */
+    public abstract long length();
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an URL representing the given resource
+     */
+    public abstract URL getURL();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an URI representing the given resource
+     */
+    public URI getURI()
+    {
+        try
+        {
+            return getURL().toURI();
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an File representing the given resource or NULL if this
+     * is not possible.
+     */
+    public abstract File getFile()
+        throws IOException;
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the name of the resource
+     */
+    public abstract String getName();
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an input stream to the resource
+     */
+    public abstract InputStream getInputStream()
+        throws java.io.IOException;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an readable bytechannel to the resource or null if one is not available.
+     */
+    public abstract ReadableByteChannel getReadableByteChannel()
+        throws java.io.IOException;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Deletes the given resource
+     */
+    public abstract boolean delete()
+        throws SecurityException;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Rename the given resource
+     */
+    public abstract boolean renameTo( Resource dest)
+        throws SecurityException;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns a list of resource names contained in the given resource
+     * The resource names are not URL encoded.
+     */
+    public abstract String[] list();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the resource contained inside the current resource with the
+     * given name.
+     * @param path The path segment to add, which is not encoded
+     */
+    public abstract Resource addPath(String path)
+        throws IOException,MalformedURLException;
+
+    /* ------------------------------------------------------------ */
+    /** Get a resource from within this resource.
+     * <p>
+     * This method is essentially an alias for {@link #addPath(String)}, but without checked exceptions.
+     * This method satisfied the {@link ResourceFactory} interface.
+     * @see org.eclipse.jetty.util.resource.ResourceFactory#getResource(java.lang.String)
+     */
+    @Override
+    public Resource getResource(String path)
+    {
+        try
+        {
+            return addPath(path);
+        }
+        catch(Exception e)
+        {
+            LOG.debug(e);
+            return null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @deprecated
+     */
+    public String encode(String uri)
+    {
+        return null;
+    }
+        
+    /* ------------------------------------------------------------ */
+    public Object getAssociate()
+    {
+        return _associate;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setAssociate(Object o)
+    {
+        _associate=o;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The canonical Alias of this resource or null if none.
+     */
+    public URI getAlias()
+    {
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the resource list as a HTML directory listing.
+     * @param base The base URL
+     * @param parent True if the parent directory should be included
+     * @return String of HTML
+     */
+    public String getListHTML(String base,boolean parent)
+        throws IOException
+    {
+        base=URIUtil.canonicalPath(base);
+        if (base==null || !isDirectory())
+            return null;
+        
+        String[] ls = list();
+        if (ls==null)
+            return null;
+        Arrays.sort(ls);
+        
+        String decodedBase = URIUtil.decodePath(base);
+        String title = "Directory: "+deTag(decodedBase);
+
+        StringBuilder buf=new StringBuilder(4096);
+        buf.append("<HTML><HEAD>");
+        buf.append("<LINK HREF=\"").append("jetty-dir.css").append("\" REL=\"stylesheet\" TYPE=\"text/css\"/><TITLE>");
+        buf.append(title);
+        buf.append("</TITLE></HEAD><BODY>\n<H1>");
+        buf.append(title);
+        buf.append("</H1>\n<TABLE BORDER=0>\n");
+        
+        if (parent)
+        {
+            buf.append("<TR><TD><A HREF=\"");
+            buf.append(URIUtil.addPaths(base,"../"));
+            buf.append("\">Parent Directory</A></TD><TD></TD><TD></TD></TR>\n");
+        }
+        
+        String encodedBase = hrefEncodeURI(base);
+        
+        DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
+                                                       DateFormat.MEDIUM);
+        for (int i=0 ; i< ls.length ; i++)
+        {
+            Resource item = addPath(ls[i]);
+            
+            buf.append("\n<TR><TD><A HREF=\"");
+            String path=URIUtil.addPaths(encodedBase,URIUtil.encodePath(ls[i]));
+            
+            buf.append(path);
+            
+            if (item.isDirectory() && !path.endsWith("/"))
+                buf.append(URIUtil.SLASH);
+            
+            // URIUtil.encodePath(buf,path);
+            buf.append("\">");
+            buf.append(deTag(ls[i]));
+            buf.append("&nbsp;");
+            buf.append("</A></TD><TD ALIGN=right>");
+            buf.append(item.length());
+            buf.append(" bytes&nbsp;</TD><TD>");
+            buf.append(dfmt.format(new Date(item.lastModified())));
+            buf.append("</TD></TR>");
+        }
+        buf.append("</TABLE>\n");
+       buf.append("</BODY></HTML>\n");
+        
+        return buf.toString();
+    }
+    
+    /**
+     * Encode any characters that could break the URI string in an HREF.
+     * Such as <a href="/path/to;<script>Window.alert("XSS"+'%20'+"here");</script>">Link</a>
+     * 
+     * The above example would parse incorrectly on various browsers as the "<" or '"' characters
+     * would end the href attribute value string prematurely.
+     * 
+     * @param raw the raw text to encode.
+     * @return the defanged text.
+     */
+    private static String hrefEncodeURI(String raw) 
+    {
+        StringBuffer buf = null;
+
+        loop:
+        for (int i=0;i<raw.length();i++)
+        {
+            char c=raw.charAt(i);
+            switch(c)
+            {
+                case '\'':
+                case '"':
+                case '<':
+                case '>':
+                    buf=new StringBuffer(raw.length()<<1);
+                    break loop;
+            }
+        }
+        if (buf==null)
+            return raw;
+
+        for (int i=0;i<raw.length();i++)
+        {
+            char c=raw.charAt(i);       
+            switch(c)
+            {
+              case '"':
+                  buf.append("%22");
+                  continue;
+              case '\'':
+                  buf.append("%27");
+                  continue;
+              case '<':
+                  buf.append("%3C");
+                  continue;
+              case '>':
+                  buf.append("%3E");
+                  continue;
+              default:
+                  buf.append(c);
+                  continue;
+            }
+        }
+
+        return buf.toString();
+    }
+    
+    private static String deTag(String raw) 
+    {
+        return StringUtil.replace( StringUtil.replace(raw,"<","&lt;"), ">", "&gt;");
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param out 
+     * @param start First byte to write
+     * @param count Bytes to write or -1 for all of them.
+     */
+    public void writeTo(OutputStream out,long start,long count)
+        throws IOException
+    {
+        try (InputStream in = getInputStream())
+        {
+            in.skip(start);
+            if (count<0)
+                IO.copy(in,out);
+            else
+                IO.copy(in,out,count);
+        }
+    }    
+    
+    /* ------------------------------------------------------------ */
+    public void copyTo(File destination)
+        throws IOException
+    {
+        if (destination.exists())
+            throw new IllegalArgumentException(destination+" exists");
+        try (OutputStream out = new FileOutputStream(destination))
+        {
+            writeTo(out,0,-1);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getWeakETag()
+    {
+        try
+        {
+            StringBuilder b = new StringBuilder(32);
+            b.append("W/\"");
+            
+            String name=getName();
+            int length=name.length();
+            long lhash=0;
+            for (int i=0; i<length;i++)
+                lhash=31*lhash+name.charAt(i);
+            
+            B64Code.encode(lastModified()^lhash,b);
+            B64Code.encode(length()^lhash,b);
+            b.append('"');
+            return b.toString();
+        } 
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Collection<Resource> getAllResources()
+    {
+        try
+        {
+            ArrayList<Resource> deep=new ArrayList<>();
+            {
+                String[] list=list();
+                if (list!=null)
+                {
+                    for (String i:list)
+                    {
+                        Resource r=addPath(i);
+                        if (r.isDirectory())
+                            deep.addAll(r.getAllResources());
+                        else
+                            deep.add(r);
+                    }
+                }
+            }
+            return deep;
+        }
+        catch(Exception e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Generate a properly encoded URL from a {@link File} instance.
+     * @param file Target file. 
+     * @return URL of the target file.
+     * @throws MalformedURLException 
+     */
+    public static URL toURL(File file) throws MalformedURLException
+    {
+        return file.toURI().toURL();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/ResourceCollection.java b/lib/jetty/org/eclipse/jetty/util/resource/ResourceCollection.java
new file mode 100644 (file)
index 0000000..8135e0b
--- /dev/null
@@ -0,0 +1,493 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.channels.ReadableByteChannel;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * A collection of resources (dirs).
+ * Allows webapps to have multiple (static) sources.
+ * The first resource in the collection is the main resource.
+ * If a resource is not found in the main resource, it looks it up in 
+ * the order the resources were constructed.
+ * 
+ * 
+ *
+ */
+public class ResourceCollection extends Resource
+{
+    private static final Logger LOG = Log.getLogger(ResourceCollection.class);
+    private Resource[] _resources;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Instantiates an empty resource collection.
+     * 
+     * This constructor is used when configuring jetty-maven-plugin.
+     */
+    public ResourceCollection()
+    {
+        _resources = new Resource[0];
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Instantiates a new resource collection.
+     *
+     * @param resources the resources to be added to collection
+     */
+    public ResourceCollection(Resource... resources)
+    {
+        List<Resource> list = new ArrayList<Resource>();
+        for (Resource r : resources)
+        {
+            if (r==null)
+                continue;
+            if (r instanceof ResourceCollection)
+            {
+                for (Resource r2 : ((ResourceCollection)r).getResources())
+                    list.add(r2);
+            }
+            else
+                list.add(r);
+        }
+        _resources = list.toArray(new Resource[list.size()]);
+        for(Resource r : _resources)
+        {
+            if(!r.exists() || !r.isDirectory())
+                throw new IllegalArgumentException(r + " is not an existing directory.");
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Instantiates a new resource collection.
+     *
+     * @param resources the resource strings to be added to collection
+     */
+    public ResourceCollection(String[] resources)
+    {
+        _resources = new Resource[resources.length];
+        try
+        {
+            for(int i=0; i<resources.length; i++)
+            {
+                _resources[i] = Resource.newResource(resources[i]);
+                if(!_resources[i].exists() || !_resources[i].isDirectory())
+                    throw new IllegalArgumentException(_resources[i] + " is not an existing directory.");
+            }
+        }
+        catch(IllegalArgumentException e)
+        {
+            throw e;
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Instantiates a new resource collection.
+     *
+     * @param csvResources the string containing comma-separated resource strings
+     */
+    public ResourceCollection(String csvResources)
+    {
+        setResourcesAsCSV(csvResources);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieves the resource collection's resources.
+     * 
+     * @return the resource array
+     */
+    public Resource[] getResources()
+    {
+        return _resources;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the resource collection's resources.
+     *
+     * @param resources the new resource array
+     */
+    public void setResources(Resource[] resources)
+    {
+        _resources = resources != null ? resources : new Resource[0];
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the resources as string of comma-separated values.
+     * This method should be used when configuring jetty-maven-plugin.
+     *
+     * @param csvResources the comma-separated string containing
+     *                     one or more resource strings.
+     */
+    public void setResourcesAsCSV(String csvResources)
+    {
+        StringTokenizer tokenizer = new StringTokenizer(csvResources, ",;");
+        int len = tokenizer.countTokens();
+        if(len==0)
+        {
+            throw new IllegalArgumentException("ResourceCollection@setResourcesAsCSV(String) " +
+                    " argument must be a string containing one or more comma-separated resource strings.");
+        }
+        
+        List<Resource> resources = new ArrayList<>();
+        
+        try
+        {            
+            while(tokenizer.hasMoreTokens())
+            {
+                Resource resource = Resource.newResource(tokenizer.nextToken().trim());
+                if(!resource.exists() || !resource.isDirectory())
+                    LOG.warn(" !exist "+resource);
+                else
+                    resources.add(resource);
+            }
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+
+        _resources = resources.toArray(new Resource[resources.size()]);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param path The path segment to add
+     * @return The contained resource (found first) in the collection of resources
+     */
+    @Override
+    public Resource addPath(String path) throws IOException, MalformedURLException
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        if(path==null)
+            throw new MalformedURLException();
+        
+        if(path.length()==0 || URIUtil.SLASH.equals(path))
+            return this;
+        
+        Resource resource=null;
+        ArrayList<Resource> resources = null;
+        int i=0;
+        for(; i<_resources.length; i++)
+        {
+            resource = _resources[i].addPath(path);  
+            if (resource.exists())
+            {
+                if (resource.isDirectory())
+                    break;       
+                return resource;
+            }
+        }  
+
+        for(i++; i<_resources.length; i++)
+        {
+            Resource r = _resources[i].addPath(path); 
+            if (r.exists() && r.isDirectory())
+            {
+                if (resources==null)
+                    resources = new ArrayList<Resource>();
+                    
+                if (resource!=null)
+                {
+                    resources.add(resource);
+                    resource=null;
+                }
+                
+                resources.add(r);
+            }
+        }
+
+        if (resource!=null)
+            return resource;
+        if (resources!=null)
+            return new ResourceCollection(resources.toArray(new Resource[resources.size()]));
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param path
+     * @return the resource(file) if found, returns a list of resource dirs if its a dir, else null.
+     * @throws IOException
+     * @throws MalformedURLException
+     */
+    protected Object findResource(String path) throws IOException, MalformedURLException
+    {        
+        Resource resource=null;
+        ArrayList<Resource> resources = null;
+        int i=0;
+        for(; i<_resources.length; i++)
+        {
+            resource = _resources[i].addPath(path);  
+            if (resource.exists())
+            {
+                if (resource.isDirectory())
+                    break;
+               
+                return resource;
+            }
+        }  
+
+        for(i++; i<_resources.length; i++)
+        {
+            Resource r = _resources[i].addPath(path); 
+            if (r.exists() && r.isDirectory())
+            {
+                if (resource!=null)
+                {
+                    resources = new ArrayList<Resource>();
+                    resources.add(resource);
+                }
+                resources.add(r);
+            }
+        }
+        
+        if (resource!=null)
+            return resource;
+        if (resources!=null)
+            return resources;
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean delete() throws SecurityException
+    {
+        throw new UnsupportedOperationException();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean exists()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public File getFile() throws IOException
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            File f = r.getFile();
+            if(f!=null)
+                return f;
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            InputStream is = r.getInputStream();
+            if(is!=null)
+                return is;
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override 
+    public ReadableByteChannel getReadableByteChannel() throws IOException
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            ReadableByteChannel channel = r.getReadableByteChannel();
+            if(channel!=null)
+                return channel;
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getName()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            String name = r.getName();
+            if(name!=null)
+                return name;
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public URL getURL()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            URL url = r.getURL();
+            if(url!=null)
+                return url;
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isDirectory()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public long lastModified()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            long lm = r.lastModified();
+            if (lm!=-1)
+                return lm;
+        }
+        return -1;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public long length()
+    {
+        return -1;
+    }    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The list of resource names(merged) contained in the collection of resources.
+     */    
+    @Override
+    public String[] list()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        HashSet<String> set = new HashSet<String>();
+        for(Resource r : _resources)
+        {
+            for(String s : r.list())
+                set.add(s);
+        }
+        String[] result=set.toArray(new String[set.size()]);
+        Arrays.sort(result);
+        return result;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void close()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+            r.close();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean renameTo(Resource dest) throws SecurityException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void copyTo(File destination)
+        throws IOException
+    {
+        for (int r=_resources.length;r-->0;)
+            _resources[r].copyTo(destination);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the list of resources separated by a path separator
+     */
+    @Override
+    public String toString()
+    {
+        if(_resources==null)
+            return "[]";
+        
+        return String.valueOf(Arrays.asList(_resources));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isContainedIn(Resource r) throws MalformedURLException
+    {
+        // TODO could look at implementing the semantic of is this collection a subset of the Resource r?
+        return false;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/ResourceFactory.java b/lib/jetty/org/eclipse/jetty/util/resource/ResourceFactory.java
new file mode 100644 (file)
index 0000000..707a672
--- /dev/null
@@ -0,0 +1,34 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+
+/* ------------------------------------------------------------ */
+/** ResourceFactory.
+ */
+public interface ResourceFactory
+{
+    
+    /* ------------------------------------------------------------ */
+    /** Get a resource for a path.
+     * @param path The path to the resource
+     * @return The resource or null 
+     */
+    Resource getResource(String path);
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/URLResource.java b/lib/jetty/org/eclipse/jetty/util/resource/URLResource.java
new file mode 100644 (file)
index 0000000..b696817
--- /dev/null
@@ -0,0 +1,316 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.channels.ReadableByteChannel;
+import java.security.Permission;
+
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Abstract resource class.
+ */
+public class URLResource extends Resource
+{
+    private static final Logger LOG = Log.getLogger(URLResource.class);
+    protected final URL _url;
+    protected final String _urlString;
+    
+    protected URLConnection _connection;
+    protected InputStream _in=null;
+    transient boolean _useCaches = Resource.__defaultUseCaches;
+    
+    /* ------------------------------------------------------------ */
+    protected URLResource(URL url, URLConnection connection)
+    {
+        _url = url;
+        _urlString=_url.toExternalForm();
+        _connection=connection;
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected URLResource (URL url, URLConnection connection, boolean useCaches)
+    {
+        this (url, connection);
+        _useCaches = useCaches;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected synchronized boolean checkConnection()
+    {
+        if (_connection==null)
+        {
+            try{
+                _connection=_url.openConnection();
+                _connection.setUseCaches(_useCaches);
+            }
+            catch(IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+        return _connection!=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Release any resources held by the resource.
+     */
+    @Override
+    public synchronized void close()
+    {
+        if (_in!=null)
+        {
+            try{_in.close();}catch(IOException e){LOG.ignore(e);}
+            _in=null;
+        }
+
+        if (_connection!=null)
+            _connection=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the represented resource exists.
+     */
+    @Override
+    public boolean exists()
+    {
+        try
+        {
+            synchronized(this)
+            {
+                if (checkConnection() && _in==null )
+                    _in = _connection.getInputStream();
+            }
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+        return _in!=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the represented resource is a container/directory.
+     * If the resource is not a file, resources ending with "/" are
+     * considered directories.
+     */
+    @Override
+    public boolean isDirectory()
+    {
+        return exists() && _urlString.endsWith("/");
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the last modified time
+     */
+    @Override
+    public long lastModified()
+    {
+        if (checkConnection())
+            return _connection.getLastModified();
+        return -1;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Return the length of the resource
+     */
+    @Override
+    public long length()
+    {
+        if (checkConnection())
+            return _connection.getContentLength();
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an URL representing the given resource
+     */
+    @Override
+    public URL getURL()
+    {
+        return _url;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an File representing the given resource or NULL if this
+     * is not possible.
+     */
+    @Override
+    public File getFile()
+        throws IOException
+    {
+        // Try the permission hack
+        if (checkConnection())
+        {
+            Permission perm = _connection.getPermission();
+            if (perm instanceof java.io.FilePermission)
+                return new File(perm.getName());
+        }
+
+        // Try the URL file arg
+        try {return new File(_url.getFile());}
+        catch(Exception e) {LOG.ignore(e);}
+
+        // Don't know the file
+        return null;    
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the name of the resource
+     */
+    @Override
+    public String getName()
+    {
+        return _url.toExternalForm();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an input stream to the resource
+     */
+    @Override
+    public synchronized InputStream getInputStream()
+        throws java.io.IOException
+    {
+        if (!checkConnection())
+            throw new IOException( "Invalid resource");
+
+        try
+        {    
+            if( _in != null)
+            {
+                InputStream in = _in;
+                _in=null;
+                return in;
+            }
+            return _connection.getInputStream();
+        }
+        finally
+        {
+            _connection=null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public ReadableByteChannel getReadableByteChannel() throws IOException
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Deletes the given resource
+     */
+    @Override
+    public boolean delete()
+        throws SecurityException
+    {
+        throw new SecurityException( "Delete not supported");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Rename the given resource
+     */
+    @Override
+    public boolean renameTo( Resource dest)
+        throws SecurityException
+    {
+        throw new SecurityException( "RenameTo not supported");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns a list of resource names contained in the given resource
+     */
+    @Override
+    public String[] list()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the resource contained inside the current resource with the
+     * given name
+     */
+    @Override
+    public Resource addPath(String path)
+        throws IOException,MalformedURLException
+    {
+        if (path==null)
+            return null;
+
+        path = URIUtil.canonicalPath(path);
+
+        return newResource(URIUtil.addPaths(_url.toExternalForm(),path));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _urlString;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int hashCode()
+    {
+        return _urlString.hashCode();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean equals( Object o)
+    {
+        return o instanceof URLResource && _urlString.equals(((URLResource)o)._urlString);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean getUseCaches ()
+    {
+        return _useCaches;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isContainedIn (Resource containingResource) throws MalformedURLException
+    {
+        return false;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/package-info.java b/lib/jetty/org/eclipse/jetty/util/resource/package-info.java
new file mode 100644 (file)
index 0000000..f8d2428
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Resource Utilities
+ */
+package org.eclipse.jetty.util.resource;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/security/CertificateUtils.java b/lib/jetty/org/eclipse/jetty/util/security/CertificateUtils.java
new file mode 100644 (file)
index 0000000..4ed9ae2
--- /dev/null
@@ -0,0 +1,94 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.cert.CRL;
+import java.security.cert.CertificateFactory;
+import java.util.Collection;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+public class CertificateUtils
+{
+    /* ------------------------------------------------------------ */
+    public static KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
+    {
+        KeyStore keystore = null;
+
+        if (storeStream != null || storePath != null)
+        {
+            InputStream inStream = storeStream;
+            try
+            {
+                if (inStream == null)
+                {
+                    inStream = Resource.newResource(storePath).getInputStream();
+                }
+                
+                if (storeProvider != null)
+                {
+                    keystore = KeyStore.getInstance(storeType, storeProvider);
+                }
+                else
+                {
+                    keystore = KeyStore.getInstance(storeType);
+                }
+    
+                keystore.load(inStream, storePassword == null ? null : storePassword.toCharArray());
+            }
+            finally
+            {
+                if (inStream != null)
+                {
+                    inStream.close();
+                }
+            }
+        }
+        
+        return keystore;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static Collection<? extends CRL> loadCRL(String crlPath) throws Exception
+    {
+        Collection<? extends CRL> crlList = null;
+
+        if (crlPath != null)
+        {
+            InputStream in = null;
+            try
+            {
+                in = Resource.newResource(crlPath).getInputStream();
+                crlList = CertificateFactory.getInstance("X.509").generateCRLs(in);
+            }
+            finally
+            {
+                if (in != null)
+                {
+                    in.close();
+                }
+            }
+        }
+
+        return crlList;
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/security/CertificateValidator.java b/lib/jetty/org/eclipse/jetty/util/security/CertificateValidator.java
new file mode 100644 (file)
index 0000000..2ead387
--- /dev/null
@@ -0,0 +1,343 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.Security;
+import java.security.cert.CRL;
+import java.security.cert.CertPathBuilder;
+import java.security.cert.CertPathBuilderResult;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertStore;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.X509CertSelector;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Convenience class to handle validation of certificates, aliases and keystores
+ *
+ * Allows specifying Certificate Revocation List (CRL), as well as enabling
+ * CRL Distribution Points Protocol (CRLDP) certificate extension support,
+ * and also enabling On-Line Certificate Status Protocol (OCSP) support.
+ * 
+ * IMPORTANT: at least one of the above mechanisms *MUST* be configured and
+ * operational, otherwise certificate validation *WILL FAIL* unconditionally.
+ */
+public class CertificateValidator
+{
+    private static final Logger LOG = Log.getLogger(CertificateValidator.class);
+    private static AtomicLong __aliasCount = new AtomicLong();
+    
+    private KeyStore _trustStore;
+    private Collection<? extends CRL> _crls;
+
+    /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
+    private int _maxCertPathLength = -1;
+    /** CRL Distribution Points (CRLDP) support */
+    private boolean _enableCRLDP = false;
+    /** On-Line Certificate Status Protocol (OCSP) support */
+    private boolean _enableOCSP = false;
+    /** Location of OCSP Responder */
+    private String _ocspResponderURL;
+    
+    /**
+     * creates an instance of the certificate validator 
+     *
+     * @param trustStore 
+     * @param crls
+     */
+    public CertificateValidator(KeyStore trustStore, Collection<? extends CRL> crls)
+    {
+        if (trustStore == null)
+        {
+            throw new InvalidParameterException("TrustStore must be specified for CertificateValidator.");
+        }
+        
+        _trustStore = trustStore;
+        _crls = crls;
+    }
+    
+    /**
+     * validates all aliases inside of a given keystore
+     * 
+     * @param keyStore
+     * @throws CertificateException
+     */
+    public void validate( KeyStore keyStore ) throws CertificateException
+    {
+        try
+        {
+            Enumeration<String> aliases = keyStore.aliases();
+            
+            for ( ; aliases.hasMoreElements(); )
+            {
+                String alias = aliases.nextElement();
+                
+                validate(keyStore,alias);
+            }
+            
+        }
+        catch ( KeyStoreException kse )
+        {
+            throw new CertificateException("Unable to retrieve aliases from keystore", kse);
+        }
+    }
+    
+
+    /**
+     * validates a specific alias inside of the keystore being passed in
+     * 
+     * @param keyStore
+     * @param keyAlias
+     * @return the keyAlias if valid
+     * @throws CertificateException
+     */
+    public String validate(KeyStore keyStore, String keyAlias) throws CertificateException
+    {
+        String result = null;
+
+        if (keyAlias != null)
+        {
+            try
+            {
+                validate(keyStore, keyStore.getCertificate(keyAlias));
+            }
+            catch (KeyStoreException kse)
+            {
+                LOG.debug(kse);
+                throw new CertificateException("Unable to validate certificate" +
+                        " for alias [" + keyAlias + "]: " + kse.getMessage(), kse);
+            }
+            result = keyAlias;            
+        }
+        
+        return result;
+    }
+    
+    /**
+     * validates a specific certificate inside of the keystore being passed in
+     * 
+     * @param keyStore
+     * @param cert
+     * @throws CertificateException
+     */
+    public void validate(KeyStore keyStore, Certificate cert) throws CertificateException
+    {
+        Certificate[] certChain = null;
+        
+        if (cert != null && cert instanceof X509Certificate)
+        {
+            ((X509Certificate)cert).checkValidity();
+            
+            String certAlias = null;
+            try
+            {
+                if (keyStore == null)
+                {
+                    throw new InvalidParameterException("Keystore cannot be null");
+                }
+
+                certAlias = keyStore.getCertificateAlias((X509Certificate)cert);
+                if (certAlias == null)
+                {
+                    certAlias = "JETTY" + String.format("%016X",__aliasCount.incrementAndGet());
+                    keyStore.setCertificateEntry(certAlias, cert);
+                }
+                
+                certChain = keyStore.getCertificateChain(certAlias);
+                if (certChain == null || certChain.length == 0)
+                {
+                    throw new IllegalStateException("Unable to retrieve certificate chain");
+                }
+            }
+            catch (KeyStoreException kse)
+            {
+                LOG.debug(kse);
+                throw new CertificateException("Unable to validate certificate" +
+                        (certAlias == null ? "":" for alias [" +certAlias + "]") + ": " + kse.getMessage(), kse);
+            }
+            
+            validate(certChain);
+        } 
+    }
+    
+    public void validate(Certificate[] certChain) throws CertificateException
+    {
+        try
+        {
+            ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
+            for (Certificate item : certChain)
+            {
+                if (item == null)
+                    continue;
+                
+                if (!(item instanceof X509Certificate))
+                {
+                    throw new IllegalStateException("Invalid certificate type in chain");
+                }
+                
+                certList.add((X509Certificate)item);
+            }
+    
+            if (certList.isEmpty())
+            {
+                throw new IllegalStateException("Invalid certificate chain");
+                
+            }
+    
+            X509CertSelector certSelect = new X509CertSelector();
+            certSelect.setCertificate(certList.get(0));
+            
+            // Configure certification path builder parameters
+            PKIXBuilderParameters pbParams = new PKIXBuilderParameters(_trustStore, certSelect);
+            pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList)));
+    
+            // Set maximum certification path length
+            pbParams.setMaxPathLength(_maxCertPathLength);
+    
+            // Enable revocation checking
+            pbParams.setRevocationEnabled(true);
+    
+            // Set static Certificate Revocation List
+            if (_crls != null && !_crls.isEmpty())
+            {
+                pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(_crls)));
+            }
+    
+            // Enable On-Line Certificate Status Protocol (OCSP) support
+            if (_enableOCSP)
+            {
+                Security.setProperty("ocsp.enable","true");
+            }
+            // Enable Certificate Revocation List Distribution Points (CRLDP) support
+            if (_enableCRLDP)
+            {
+                System.setProperty("com.sun.security.enableCRLDP","true");
+            }
+    
+            // Build certification path
+            CertPathBuilderResult buildResult = CertPathBuilder.getInstance("PKIX").build(pbParams);               
+            
+            // Validate certification path
+            CertPathValidator.getInstance("PKIX").validate(buildResult.getCertPath(),pbParams);
+        }
+        catch (GeneralSecurityException gse)
+        {
+            LOG.debug(gse);
+            throw new CertificateException("Unable to validate certificate: " + gse.getMessage(), gse);
+        }
+    }
+
+    public KeyStore getTrustStore()
+    {
+        return _trustStore;
+    }
+
+    public Collection<? extends CRL> getCrls()
+    {
+        return _crls;
+    }
+
+    /**
+     * @return Maximum number of intermediate certificates in
+     * the certification path (-1 for unlimited)
+     */
+    public int getMaxCertPathLength()
+    {
+        return _maxCertPathLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maxCertPathLength
+     *            maximum number of intermediate certificates in
+     *            the certification path (-1 for unlimited)
+     */
+    public void setMaxCertPathLength(int maxCertPathLength)
+    {
+        _maxCertPathLength = maxCertPathLength;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return true if CRL Distribution Points support is enabled
+     */
+    public boolean isEnableCRLDP()
+    {
+        return _enableCRLDP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Enables CRL Distribution Points Support
+     * @param enableCRLDP true - turn on, false - turns off
+     */
+    public void setEnableCRLDP(boolean enableCRLDP)
+    {
+        _enableCRLDP = enableCRLDP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return true if On-Line Certificate Status Protocol support is enabled
+     */
+    public boolean isEnableOCSP()
+    {
+        return _enableOCSP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Enables On-Line Certificate Status Protocol support
+     * @param enableOCSP true - turn on, false - turn off
+     */
+    public void setEnableOCSP(boolean enableOCSP)
+    {
+        _enableOCSP = enableOCSP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Location of the OCSP Responder
+     */
+    public String getOcspResponderURL()
+    {
+        return _ocspResponderURL;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the location of the OCSP Responder.
+     * @param ocspResponderURL location of the OCSP Responder
+     */
+    public void setOcspResponderURL(String ocspResponderURL)
+    {
+        _ocspResponderURL = ocspResponderURL;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/security/Constraint.java b/lib/jetty/org/eclipse/jetty/util/security/Constraint.java
new file mode 100644 (file)
index 0000000..28c003b
--- /dev/null
@@ -0,0 +1,254 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+/* ------------------------------------------------------------ */
+/**
+ * Constraint
+ * 
+ * Describe an auth and/or data constraint.
+ * 
+ * 
+ */
+public class Constraint implements Cloneable, Serializable
+{
+    /* ------------------------------------------------------------ */
+    public final static String __BASIC_AUTH = "BASIC";
+
+    public final static String __FORM_AUTH = "FORM";
+
+    public final static String __DIGEST_AUTH = "DIGEST";
+
+    public final static String __CERT_AUTH = "CLIENT_CERT";
+
+    public final static String __CERT_AUTH2 = "CLIENT-CERT";
+    
+    public final static String __SPNEGO_AUTH = "SPNEGO";
+    
+    public final static String __NEGOTIATE_AUTH = "NEGOTIATE";
+    
+    public static boolean validateMethod (String method)
+    {
+        if (method == null)
+            return false;
+        method = method.trim();
+        return (method.equals(__FORM_AUTH) 
+                || method.equals(__BASIC_AUTH) 
+                || method.equals (__DIGEST_AUTH) 
+                || method.equals (__CERT_AUTH) 
+                || method.equals(__CERT_AUTH2)
+                || method.equals(__SPNEGO_AUTH)
+                || method.equals(__NEGOTIATE_AUTH));
+    }
+
+    /* ------------------------------------------------------------ */
+    public final static int DC_UNSET = -1, DC_NONE = 0, DC_INTEGRAL = 1, DC_CONFIDENTIAL = 2, DC_FORBIDDEN = 3;
+
+    /* ------------------------------------------------------------ */
+    public final static String NONE = "NONE";
+
+    public final static String ANY_ROLE = "*";
+    
+    public final static String ANY_AUTH = "**"; //Servlet Spec 3.1 pg 140
+
+    /* ------------------------------------------------------------ */
+    private String _name;
+
+    private String[] _roles;
+
+    private int _dataConstraint = DC_UNSET;
+
+    private boolean _anyRole = false;
+    
+    private boolean _anyAuth = false;
+
+    private boolean _authenticate = false;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Constructor.
+     */
+    public Constraint()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Conveniance Constructor.
+     * 
+     * @param name
+     * @param role
+     */
+    public Constraint(String name, String role)
+    {
+        setName(name);
+        setRoles(new String[] { role });
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object clone() throws CloneNotSupportedException
+    {
+        return super.clone();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name
+     */
+    public void setName(String name)
+    {
+        _name = name;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getName()
+    {
+        return _name;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRoles(String[] roles)
+    {
+        _roles = roles;
+        _anyRole = false;
+        _anyAuth = false;
+        if (roles != null) 
+        {
+            for (int i = roles.length; i-- > 0;)
+            {
+                _anyRole |= ANY_ROLE.equals(roles[i]);
+                _anyAuth |= ANY_AUTH.equals(roles[i]);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if any user role is permitted.
+     */
+    public boolean isAnyRole()
+    {
+        return _anyRole;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Servlet Spec 3.1, pg 140
+     * @return True if any authenticated user is permitted (ie a role "**" was specified in the constraint).
+     */
+    public boolean isAnyAuth()
+    {
+        return _anyAuth;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return List of roles for this constraint.
+     */
+    public String[] getRoles()
+    {
+        return _roles;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param role
+     * @return True if the constraint contains the role.
+     */
+    public boolean hasRole(String role)
+    {
+        if (_anyRole) return true;
+        if (_roles != null) for (int i = _roles.length; i-- > 0;)
+            if (role.equals(_roles[i])) return true;
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param authenticate True if users must be authenticated
+     */
+    public void setAuthenticate(boolean authenticate)
+    {
+        _authenticate = authenticate;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if the constraint requires request authentication
+     */
+    public boolean getAuthenticate()
+    {
+        return _authenticate;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if authentication required but no roles set
+     */
+    public boolean isForbidden()
+    {
+        return _authenticate && !_anyRole && (_roles == null || _roles.length == 0);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param c Data constrain indicator: 0=DC+NONE, 1=DC_INTEGRAL &
+     *                2=DC_CONFIDENTIAL
+     */
+    public void setDataConstraint(int c)
+    {
+        if (c < 0 || c > DC_CONFIDENTIAL) throw new IllegalArgumentException("Constraint out of range");
+        _dataConstraint = c;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Data constrain indicator: 0=DC+NONE, 1=DC_INTEGRAL &
+     *         2=DC_CONFIDENTIAL
+     */
+    public int getDataConstraint()
+    {
+        return _dataConstraint;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if a data constraint has been set.
+     */
+    public boolean hasDataConstraint()
+    {
+        return _dataConstraint >= DC_NONE;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return "SC{" + _name
+               + ","
+               + (_anyRole ? "*" : (_roles == null ? "-" : Arrays.asList(_roles).toString()))
+               + ","
+               + (_dataConstraint == DC_UNSET ? "DC_UNSET}" : (_dataConstraint == DC_NONE ? "NONE}" : (_dataConstraint == DC_INTEGRAL ? "INTEGRAL}" : "CONFIDENTIAL}")));
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/security/Credential.java b/lib/jetty/org/eclipse/jetty/util/security/Credential.java
new file mode 100644 (file)
index 0000000..1feb604
--- /dev/null
@@ -0,0 +1,229 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Credentials. The Credential class represents an abstract mechanism for
+ * checking authentication credentials. A credential instance either represents
+ * a secret, or some data that could only be derived from knowing the secret.
+ * <p>
+ * Often a Credential is related to a Password via a one way algorithm, so while
+ * a Password itself is a Credential, a UnixCrypt or MD5 digest of a a password
+ * is only a credential that can be checked against the password.
+ * <p>
+ * This class includes an implementation for unix Crypt an MD5 digest.
+ * 
+ * @see Password
+ * 
+ */
+public abstract class Credential implements Serializable
+{
+    private static final Logger LOG = Log.getLogger(Credential.class);
+
+    private static final long serialVersionUID = -7760551052768181572L;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Check a credential
+     * 
+     * @param credentials The credential to check against. This may either be
+     *                another Credential object, a Password object or a String
+     *                which is interpreted by this credential.
+     * @return True if the credentials indicated that the shared secret is known
+     *         to both this Credential and the passed credential.
+     */
+    public abstract boolean check(Object credentials);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get a credential from a String. If the credential String starts with a
+     * known Credential type (eg "CRYPT:" or "MD5:" ) then a Credential of that
+     * type is returned. Else the credential is assumed to be a Password.
+     * 
+     * @param credential String representation of the credential
+     * @return A Credential or Password instance.
+     */
+    public static Credential getCredential(String credential)
+    {
+        if (credential.startsWith(Crypt.__TYPE)) return new Crypt(credential);
+        if (credential.startsWith(MD5.__TYPE)) return new MD5(credential);
+
+        return new Password(credential);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Unix Crypt Credentials
+     */
+    public static class Crypt extends Credential
+    {
+        private static final long serialVersionUID = -2027792997664744210L;
+
+        public static final String __TYPE = "CRYPT:";
+
+        private final String _cooked;
+
+        Crypt(String cooked)
+        {
+            _cooked = cooked.startsWith(Crypt.__TYPE) ? cooked.substring(__TYPE.length()) : cooked;
+        }
+
+        @Override
+        public boolean check(Object credentials)
+        {
+            if (credentials instanceof char[])
+                credentials=new String((char[])credentials);
+            if (!(credentials instanceof String) && !(credentials instanceof Password)) 
+                LOG.warn("Can't check " + credentials.getClass() + " against CRYPT");
+
+            String passwd = credentials.toString();
+            return _cooked.equals(UnixCrypt.crypt(passwd, _cooked));
+        }
+
+        public static String crypt(String user, String pw)
+        {
+            return "CRYPT:" + UnixCrypt.crypt(pw, user);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * MD5 Credentials
+     */
+    public static class MD5 extends Credential
+    {
+        private static final long serialVersionUID = 5533846540822684240L;
+
+        public static final String __TYPE = "MD5:";
+
+        public static final Object __md5Lock = new Object();
+
+        private static MessageDigest __md;
+
+        private final byte[] _digest;
+
+        /* ------------------------------------------------------------ */
+        MD5(String digest)
+        {
+            digest = digest.startsWith(__TYPE) ? digest.substring(__TYPE.length()) : digest;
+            _digest = TypeUtil.parseBytes(digest, 16);
+        }
+
+        /* ------------------------------------------------------------ */
+        public byte[] getDigest()
+        {
+            return _digest;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public boolean check(Object credentials)
+        {
+            try
+            {
+                byte[] digest = null;
+
+                if (credentials instanceof char[])
+                    credentials=new String((char[])credentials);
+                if (credentials instanceof Password || credentials instanceof String)
+                {
+                    synchronized (__md5Lock)
+                    {
+                        if (__md == null) __md = MessageDigest.getInstance("MD5");
+                        __md.reset();
+                        __md.update(credentials.toString().getBytes(StandardCharsets.ISO_8859_1));
+                        digest = __md.digest();
+                    }
+                    if (digest == null || digest.length != _digest.length) return false;
+                    for (int i = 0; i < digest.length; i++)
+                        if (digest[i] != _digest[i]) return false;
+                    return true;
+                }
+                else if (credentials instanceof MD5)
+                {
+                    MD5 md5 = (MD5) credentials;
+                    if (_digest.length != md5._digest.length) return false;
+                    for (int i = 0; i < _digest.length; i++)
+                        if (_digest[i] != md5._digest[i]) return false;
+                    return true;
+                }
+                else if (credentials instanceof Credential)
+                {
+                    // Allow credential to attempt check - i.e. this'll work
+                    // for DigestAuthModule$Digest credentials
+                    return ((Credential) credentials).check(this);
+                }
+                else
+                {
+                    LOG.warn("Can't check " + credentials.getClass() + " against MD5");
+                    return false;
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+                return false;
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        public static String digest(String password)
+        {
+            try
+            {
+                byte[] digest;
+                synchronized (__md5Lock)
+                {
+                    if (__md == null)
+                    {
+                        try
+                        {
+                            __md = MessageDigest.getInstance("MD5");
+                        }
+                        catch (Exception e)
+                        {
+                            LOG.warn(e);
+                            return null;
+                        }
+                    }
+
+                    __md.reset();
+                    __md.update(password.getBytes(StandardCharsets.ISO_8859_1));
+                    digest = __md.digest();
+                }
+
+                return __TYPE + TypeUtil.toString(digest, 16);
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+                return null;
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/security/Password.java b/lib/jetty/org/eclipse/jetty/util/security/Password.java
new file mode 100644 (file)
index 0000000..13160c1
--- /dev/null
@@ -0,0 +1,267 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Password utility class.
+ * 
+ * This utility class gets a password or pass phrase either by:
+ * 
+ * <PRE>
+ *  + Password is set as a system property.
+ *  + The password is prompted for and read from standard input
+ *  + A program is run to get the password.
+ * </pre>
+ * 
+ * Passwords that begin with OBF: are de obfuscated. Passwords can be obfuscated
+ * by run org.eclipse.util.Password as a main class. Obfuscated password are
+ * required if a system needs to recover the full password (eg. so that it may
+ * be passed to another system). They are not secure, but prevent casual
+ * observation.
+ * <p>
+ * Passwords that begin with CRYPT: are oneway encrypted with UnixCrypt. The
+ * real password cannot be retrieved, but comparisons can be made to other
+ * passwords. A Crypt can be generated by running org.eclipse.util.UnixCrypt as
+ * a main class, passing password and then the username. Checksum passwords are
+ * a secure(ish) way to store passwords that only need to be checked rather than
+ * recovered. Note that it is not strong security - specially if simple
+ * passwords are used.
+ * 
+ * 
+ */
+public class Password extends Credential
+{
+    private static final Logger LOG = Log.getLogger(Password.class);
+
+    private static final long serialVersionUID = 5062906681431569445L;
+
+    public static final String __OBFUSCATE = "OBF:";
+
+    private String _pw;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Constructor.
+     * 
+     * @param password The String password.
+     */
+    public Password(String password)
+    {
+        _pw = password;
+
+        // expand password
+        while (_pw != null && _pw.startsWith(__OBFUSCATE))
+            _pw = deobfuscate(_pw);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _pw;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toStarString()
+    {
+        return "*****************************************************".substring(0, _pw.length());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean check(Object credentials)
+    {
+        if (this == credentials) return true;
+
+        if (credentials instanceof Password) return credentials.equals(_pw);
+
+        if (credentials instanceof String) return credentials.equals(_pw);
+
+        if (credentials instanceof char[]) return Arrays.equals(_pw.toCharArray(), (char[]) credentials);
+
+        if (credentials instanceof Credential) return ((Credential) credentials).check(_pw);
+
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o) 
+            return true;
+
+        if (null == o) 
+            return false;
+
+        if (o instanceof Password)
+        {
+            Password p = (Password) o;
+            //noinspection StringEquality
+            return p._pw == _pw || (null != _pw && _pw.equals(p._pw));
+        }
+
+        if (o instanceof String) 
+            return o.equals(_pw);
+
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int hashCode()
+    {
+        return null == _pw ? super.hashCode() : _pw.hashCode();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String obfuscate(String s)
+    {
+        StringBuilder buf = new StringBuilder();
+        byte[] b = s.getBytes(StandardCharsets.UTF_8);
+
+        buf.append(__OBFUSCATE);
+        for (int i = 0; i < b.length; i++)
+        {
+            byte b1 = b[i];
+            byte b2 = b[b.length - (i + 1)];
+            if (b1<0 || b2<0)
+            {
+                int i0 = (0xff&b1)*256 + (0xff&b2); 
+                String x = Integer.toString(i0, 36).toLowerCase();
+                buf.append("U0000",0,5-x.length());
+                buf.append(x);
+            }
+            else
+            {
+                int i1 = 127 + b1 + b2;
+                int i2 = 127 + b1 - b2;
+                int i0 = i1 * 256 + i2;
+                String x = Integer.toString(i0, 36).toLowerCase();
+
+                int j0 = Integer.parseInt(x, 36);
+                int j1 = (i0 / 256);
+                int j2 = (i0 % 256);
+                byte bx = (byte) ((j1 + j2 - 254) / 2);
+                
+                buf.append("000",0,4-x.length());
+                buf.append(x);
+            }
+
+        }
+        return buf.toString();
+
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String deobfuscate(String s)
+    {
+        if (s.startsWith(__OBFUSCATE)) s = s.substring(4);
+
+        byte[] b = new byte[s.length() / 2];
+        int l = 0;
+        for (int i = 0; i < s.length(); i += 4)
+        {
+            if (s.charAt(i)=='U')
+            {
+                i++;
+                String x = s.substring(i, i + 4);
+                int i0 = Integer.parseInt(x, 36);
+                byte bx = (byte)(i0>>8);
+                b[l++] = bx;
+            }
+            else
+            {
+                String x = s.substring(i, i + 4);
+                int i0 = Integer.parseInt(x, 36);
+                int i1 = (i0 / 256);
+                int i2 = (i0 % 256);
+                byte bx = (byte) ((i1 + i2 - 254) / 2);
+                b[l++] = bx;
+            }
+        }
+
+        return new String(b, 0, l,StandardCharsets.UTF_8);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get a password. A password is obtained by trying
+     * <UL>
+     * <LI>Calling <Code>System.getProperty(realm,dft)</Code>
+     * <LI>Prompting for a password
+     * <LI>Using promptDft if nothing was entered.
+     * </UL>
+     * 
+     * @param realm The realm name for the password, used as a SystemProperty
+     *                name.
+     * @param dft The default password.
+     * @param promptDft The default to use if prompting for the password.
+     * @return Password
+     */
+    public static Password getPassword(String realm, String dft, String promptDft)
+    {
+        String passwd = System.getProperty(realm, dft);
+        if (passwd == null || passwd.length() == 0)
+        {
+            try
+            {
+                System.out.print(realm + ((promptDft != null && promptDft.length() > 0) ? " [dft]" : "") + " : ");
+                System.out.flush();
+                byte[] buf = new byte[512];
+                int len = System.in.read(buf);
+                if (len > 0) passwd = new String(buf, 0, len).trim();
+            }
+            catch (IOException e)
+            {
+                LOG.warn(Log.EXCEPTION, e);
+            }
+            if (passwd == null || passwd.length() == 0) passwd = promptDft;
+        }
+        return new Password(passwd);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param arg
+     */
+    public static void main(String[] arg)
+    {
+        if (arg.length != 1 && arg.length != 2)
+        {
+            System.err.println("Usage - java org.eclipse.jetty.security.Password [<user>] <password>");
+            System.err.println("If the password is ?, the user will be prompted for the password");
+            System.exit(1);
+        }
+        String p = arg[arg.length == 1 ? 0 : 1];
+        Password pw = new Password(p);
+        System.err.println(pw.toString());
+        System.err.println(obfuscate(pw.toString()));
+        System.err.println(Credential.MD5.digest(p));
+        if (arg.length == 2) System.err.println(Credential.Crypt.crypt(arg[0], pw.toString()));
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/security/UnixCrypt.java b/lib/jetty/org/eclipse/jetty/util/security/UnixCrypt.java
new file mode 100644 (file)
index 0000000..e3f98e8
--- /dev/null
@@ -0,0 +1,461 @@
+/*
+ * @(#)UnixCrypt.java  0.9 96/11/25
+ *
+ * Copyright (c) 1996 Aki Yoshida. All rights reserved.
+ *
+ * Permission to use, copy, modify and distribute this software
+ * for non-commercial or commercial purposes and without fee is
+ * hereby granted provided that this copyright notice appears in
+ * all copies.
+ */
+
+/**
+ * Unix crypt(3C) utility
+ *
+ * @version    0.9, 11/25/96
+ * @author     Aki Yoshida
+ */
+
+/**
+ * modified April 2001
+ * by Iris Van den Broeke, Daniel Deville
+ */
+
+package org.eclipse.jetty.util.security;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Unix Crypt. Implements the one way cryptography used by Unix systems for
+ * simple password protection.
+ * 
+ * @version $Id: UnixCrypt.java,v 1.1 2005/10/05 14:09:14 janb Exp $
+ * @author Greg Wilkins (gregw)
+ */
+public class UnixCrypt
+{
+
+    /* (mostly) Standard DES Tables from Tom Truscott */
+    private static final byte[] IP = { /* initial permutation */
+    58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1,
+            59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 };
+
+    /* The final permutation is the inverse of IP - no table is necessary */
+    private static final byte[] ExpandTr = { /* expansion operation */
+    32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29,
+            28, 29, 30, 31, 32, 1 };
+
+    private static final byte[] PC1 = { /* permuted choice table 1 */
+    57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36,
+
+    63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4 };
+
+    private static final byte[] Rotates = { /* PC1 rotation schedule */
+    1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
+
+    private static final byte[] PC2 = { /* permuted choice table 2 */
+    9, 18, 14, 17, 11, 24, 1, 5, 22, 25, 3, 28, 15, 6, 21, 10, 35, 38, 23, 19, 12, 4, 26, 8, 43, 54, 16, 7, 27, 20, 13, 2,
+
+    0, 0, 41, 52, 31, 37, 47, 55, 0, 0, 30, 40, 51, 45, 33, 48, 0, 0, 44, 49, 39, 56, 34, 53, 0, 0, 46, 42, 50, 36, 29, 32 };
+
+    private static final byte[][] S = { /* 48->32 bit substitution tables */
+            /* S[1] */
+            { 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9,
+             7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 },
+            /* S[2] */
+            { 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12,
+             6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 },
+            /* S[3] */
+            { 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2,
+             12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 },
+            /* S[4] */
+            { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3,
+             14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 },
+            /* S[5] */
+            { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12,
+             5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 },
+            /* S[6] */
+            { 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4,
+             10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 },
+            /* S[7] */
+            { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15,
+             6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 },
+            /* S[8] */
+            { 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10,
+             13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 } };
+
+    private static final byte[] P32Tr = { /* 32-bit permutation function */
+    16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 };
+
+    private static final byte[] CIFP = { /*
+                                             * compressed/interleaved
+                                             * permutation
+                                             */
+    1, 2, 3, 4, 17, 18, 19, 20, 5, 6, 7, 8, 21, 22, 23, 24, 9, 10, 11, 12, 25, 26, 27, 28, 13, 14, 15, 16, 29, 30, 31, 32,
+
+    33, 34, 35, 36, 49, 50, 51, 52, 37, 38, 39, 40, 53, 54, 55, 56, 41, 42, 43, 44, 57, 58, 59, 60, 45, 46, 47, 48, 61, 62, 63, 64 };
+
+    private static final byte[] ITOA64 = { /* 0..63 => ascii-64 */
+    (byte) '.', (byte) '/', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A',
+            (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M',
+            (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y',
+            (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k',
+            (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w',
+            (byte) 'x', (byte) 'y', (byte) 'z' };
+
+    /* ===== Tables that are initialized at run time ==================== */
+
+    private static final byte[] A64TOI = new byte[128]; /* ascii-64 => 0..63 */
+
+    /* Initial key schedule permutation */
+    private static final long[][] PC1ROT = new long[16][16];
+
+    /* Subsequent key schedule rotation permutations */
+    private static final long[][][] PC2ROT = new long[2][16][16];
+
+    /* Initial permutation/expansion table */
+    private static final long[][] IE3264 = new long[8][16];
+
+    /* Table that combines the S, P, and E operations. */
+    private static final long[][] SPE = new long[8][64];
+
+    /* compressed/interleaved => final permutation table */
+    private static final long[][] CF6464 = new long[16][16];
+
+    /* ==================================== */
+
+    static
+    {
+        byte[] perm = new byte[64];
+        byte[] temp = new byte[64];
+
+        // inverse table.
+        for (int i = 0; i < 64; i++)
+            A64TOI[ITOA64[i]] = (byte) i;
+
+        // PC1ROT - bit reverse, then PC1, then Rotate, then PC2
+        for (int i = 0; i < 64; i++)
+            perm[i] = (byte) 0;
+        
+        for (int i = 0; i < 64; i++)
+        {
+            int k;
+            if ((k = PC2[i]) == 0) continue;
+            k += Rotates[0] - 1;
+            if ((k % 28) < Rotates[0]) k -= 28;
+            k = PC1[k];
+            if (k > 0)
+            {
+                k--;
+                k = (k | 0x07) - (k & 0x07);
+                k++;
+            }
+            perm[i] = (byte) k;
+        }
+        init_perm(PC1ROT, perm, 8);
+
+        // PC2ROT - PC2 inverse, then Rotate, then PC2
+        for (int j = 0; j < 2; j++)
+        {
+            int k;
+            for (int i = 0; i < 64; i++)
+                perm[i] = temp[i] = 0;
+            for (int i = 0; i < 64; i++)
+            {
+                if ((k = PC2[i]) == 0) continue;
+                temp[k - 1] = (byte) (i + 1);
+            }
+            for (int i = 0; i < 64; i++)
+            {
+                if ((k = PC2[i]) == 0) continue;
+                k += j;
+                if ((k % 28) <= j) k -= 28;
+                perm[i] = temp[k];
+            }
+
+            init_perm(PC2ROT[j], perm, 8);
+        }
+
+        // Bit reverse, intial permupation, expantion
+        for (int i = 0; i < 8; i++)
+        {
+            for (int j = 0; j < 8; j++)
+            {
+                int k = (j < 2) ? 0 : IP[ExpandTr[i * 6 + j - 2] - 1];
+                if (k > 32)
+                    k -= 32;
+                else if (k > 0) k--;
+                if (k > 0)
+                {
+                    k--;
+                    k = (k | 0x07) - (k & 0x07);
+                    k++;
+                }
+                perm[i * 8 + j] = (byte) k;
+            }
+        }
+
+        init_perm(IE3264, perm, 8);
+
+        // Compression, final permutation, bit reverse
+        for (int i = 0; i < 64; i++)
+        {
+            int k = IP[CIFP[i] - 1];
+            if (k > 0)
+            {
+                k--;
+                k = (k | 0x07) - (k & 0x07);
+                k++;
+            }
+            perm[k - 1] = (byte) (i + 1);
+        }
+
+        init_perm(CF6464, perm, 8);
+
+        // SPE table
+        for (int i = 0; i < 48; i++)
+            perm[i] = P32Tr[ExpandTr[i] - 1];
+        for (int t = 0; t < 8; t++)
+        {
+            for (int j = 0; j < 64; j++)
+            {
+                int k = (((j >> 0) & 0x01) << 5) | (((j >> 1) & 0x01) << 3)
+                        | (((j >> 2) & 0x01) << 2)
+                        | (((j >> 3) & 0x01) << 1)
+                        | (((j >> 4) & 0x01) << 0)
+                        | (((j >> 5) & 0x01) << 4);
+                k = S[t][k];
+                k = (((k >> 3) & 0x01) << 0) | (((k >> 2) & 0x01) << 1) | (((k >> 1) & 0x01) << 2) | (((k >> 0) & 0x01) << 3);
+                for (int i = 0; i < 32; i++)
+                    temp[i] = 0;
+                for (int i = 0; i < 4; i++)
+                    temp[4 * t + i] = (byte) ((k >> i) & 0x01);
+                long kk = 0;
+                for (int i = 24; --i >= 0;)
+                    kk = ((kk << 1) | ((long) temp[perm[i] - 1]) << 32 | (temp[perm[i + 24] - 1]));
+
+                SPE[t][j] = to_six_bit(kk);
+            }
+        }
+    }
+
+    /**
+     * You can't call the constructer.
+     */
+    private UnixCrypt()
+    {
+    }
+
+    /**
+     * Returns the transposed and split code of a 24-bit code into a 4-byte
+     * code, each having 6 bits.
+     */
+    private static int to_six_bit(int num)
+    {
+        return (((num << 26) & 0xfc000000) | ((num << 12) & 0xfc0000) | ((num >> 2) & 0xfc00) | ((num >> 16) & 0xfc));
+    }
+
+    /**
+     * Returns the transposed and split code of two 24-bit code into two 4-byte
+     * code, each having 6 bits.
+     */
+    private static long to_six_bit(long num)
+    {
+        return (((num << 26) & 0xfc000000fc000000L) | ((num << 12) & 0xfc000000fc0000L) | ((num >> 2) & 0xfc000000fc00L) | ((num >> 16) & 0xfc000000fcL));
+    }
+
+    /**
+     * Returns the permutation of the given 64-bit code with the specified
+     * permutataion table.
+     */
+    private static long perm6464(long c, long[][] p)
+    {
+        long out = 0L;
+        for (int i = 8; --i >= 0;)
+        {
+            int t = (int) (0x00ff & c);
+            c >>= 8;
+            long tp = p[i << 1][t & 0x0f];
+            out |= tp;
+            tp = p[(i << 1) + 1][t >> 4];
+            out |= tp;
+        }
+        return out;
+    }
+
+    /**
+     * Returns the permutation of the given 32-bit code with the specified
+     * permutataion table.
+     */
+    private static long perm3264(int c, long[][] p)
+    {
+        long out = 0L;
+        for (int i = 4; --i >= 0;)
+        {
+            int t = (0x00ff & c);
+            c >>= 8;
+            long tp = p[i << 1][t & 0x0f];
+            out |= tp;
+            tp = p[(i << 1) + 1][t >> 4];
+            out |= tp;
+        }
+        return out;
+    }
+
+    /**
+     * Returns the key schedule for the given key.
+     */
+    private static long[] des_setkey(long keyword)
+    {
+        long K = perm6464(keyword, PC1ROT);
+        long[] KS = new long[16];
+        KS[0] = K & ~0x0303030300000000L;
+
+        for (int i = 1; i < 16; i++)
+        {
+            KS[i] = K;
+            K = perm6464(K, PC2ROT[Rotates[i] - 1]);
+
+            KS[i] = K & ~0x0303030300000000L;
+        }
+        return KS;
+    }
+
+    /**
+     * Returns the DES encrypted code of the given word with the specified
+     * environment.
+     */
+    private static long des_cipher(long in, int salt, int num_iter, long[] KS)
+    {
+        salt = to_six_bit(salt);
+        long L = in;
+        long R = L;
+        L &= 0x5555555555555555L;
+        R = (R & 0xaaaaaaaa00000000L) | ((R >> 1) & 0x0000000055555555L);
+        L = ((((L << 1) | (L << 32)) & 0xffffffff00000000L) | ((R | (R >> 32)) & 0x00000000ffffffffL));
+
+        L = perm3264((int) (L >> 32), IE3264);
+        R = perm3264((int) (L & 0xffffffff), IE3264);
+
+        while (--num_iter >= 0)
+        {
+            for (int loop_count = 0; loop_count < 8; loop_count++)
+            {
+                long kp;
+                long B;
+                long k;
+
+                kp = KS[(loop_count << 1)];
+                k = ((R >> 32) ^ R) & salt & 0xffffffffL;
+                k |= (k << 32);
+                B = (k ^ R ^ kp);
+
+                L ^= (SPE[0][(int) ((B >> 58) & 0x3f)] ^ SPE[1][(int) ((B >> 50) & 0x3f)]
+                      ^ SPE[2][(int) ((B >> 42) & 0x3f)]
+                      ^ SPE[3][(int) ((B >> 34) & 0x3f)]
+                      ^ SPE[4][(int) ((B >> 26) & 0x3f)]
+                      ^ SPE[5][(int) ((B >> 18) & 0x3f)]
+                      ^ SPE[6][(int) ((B >> 10) & 0x3f)] ^ SPE[7][(int) ((B >> 2) & 0x3f)]);
+
+                kp = KS[(loop_count << 1) + 1];
+                k = ((L >> 32) ^ L) & salt & 0xffffffffL;
+                k |= (k << 32);
+                B = (k ^ L ^ kp);
+
+                R ^= (SPE[0][(int) ((B >> 58) & 0x3f)] ^ SPE[1][(int) ((B >> 50) & 0x3f)]
+                      ^ SPE[2][(int) ((B >> 42) & 0x3f)]
+                      ^ SPE[3][(int) ((B >> 34) & 0x3f)]
+                      ^ SPE[4][(int) ((B >> 26) & 0x3f)]
+                      ^ SPE[5][(int) ((B >> 18) & 0x3f)]
+                      ^ SPE[6][(int) ((B >> 10) & 0x3f)] ^ SPE[7][(int) ((B >> 2) & 0x3f)]);
+            }
+            // swap L and R
+            L ^= R;
+            R ^= L;
+            L ^= R;
+        }
+        L = ((((L >> 35) & 0x0f0f0f0fL) | (((L & 0xffffffff) << 1) & 0xf0f0f0f0L)) << 32 | (((R >> 35) & 0x0f0f0f0fL) | (((R & 0xffffffff) << 1) & 0xf0f0f0f0L)));
+
+        L = perm6464(L, CF6464);
+
+        return L;
+    }
+
+    /**
+     * Initializes the given permutation table with the mapping table.
+     */
+    private static void init_perm(long[][] perm, byte[] p, int chars_out)
+    {
+        for (int k = 0; k < chars_out * 8; k++)
+        {
+
+            int l = p[k] - 1;
+            if (l < 0) continue;
+            int i = l >> 2;
+            l = 1 << (l & 0x03);
+            for (int j = 0; j < 16; j++)
+            {
+                int s = ((k & 0x07) + ((7 - (k >> 3)) << 3));
+                if ((j & l) != 0x00) perm[i][j] |= (1L << s);
+            }
+        }
+    }
+
+    /**
+     * Encrypts String into crypt (Unix) code.
+     * 
+     * @param key the key to be encrypted
+     * @param setting the salt to be used
+     * @return the encrypted String
+     */
+    public static String crypt(String key, String setting)
+    {
+        long constdatablock = 0L; /* encryption constant */
+        byte[] cryptresult = new byte[13]; /* encrypted result */
+        long keyword = 0L;
+        /* invalid parameters! */
+        if (key == null || setting == null) return "*"; // will NOT match under
+        // ANY circumstances!
+
+        int keylen = key.length();
+
+        for (int i = 0; i < 8; i++)
+        {
+            keyword = (keyword << 8) | ((i < keylen) ? 2 * key.charAt(i) : 0);
+        }
+
+        long[] KS = des_setkey(keyword);
+
+        int salt = 0;
+        for (int i = 2; --i >= 0;)
+        {
+            char c = (i < setting.length()) ? setting.charAt(i) : '.';
+            cryptresult[i] = (byte) c;
+            salt = (salt << 6) | (0x00ff & A64TOI[c]);
+        }
+
+        long rsltblock = des_cipher(constdatablock, salt, 25, KS);
+
+        cryptresult[12] = ITOA64[(((int) rsltblock) << 2) & 0x3f];
+        rsltblock >>= 4;
+        for (int i = 12; --i >= 2;)
+        {
+            cryptresult[i] = ITOA64[((int) rsltblock) & 0x3f];
+            rsltblock >>= 6;
+        }
+
+        return new String(cryptresult, 0, 13);
+    }
+
+    public static void main(String[] arg)
+    {
+        if (arg.length != 2)
+        {
+            System.err.println("Usage - java org.eclipse.util.UnixCrypt <key> <salt>");
+            System.exit(1);
+        }
+
+        System.err.println("Crypt=" + crypt(arg[0], arg[1]));
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/security/package-info.java b/lib/jetty/org/eclipse/jetty/util/security/package-info.java
new file mode 100644 (file)
index 0000000..a9271a5
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Security Utilities
+ */
+package org.eclipse.jetty.util.security;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java b/lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java
new file mode 100644 (file)
index 0000000..b97aa34
--- /dev/null
@@ -0,0 +1,130 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.ssl;
+
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.X509ExtendedKeyManager;
+import javax.net.ssl.X509KeyManager;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * KeyManager to select a key with desired alias
+ * while delegating processing to specified KeyManager
+ * Can be used both with server and client sockets
+ */
+public class AliasedX509ExtendedKeyManager extends X509ExtendedKeyManager
+{
+    private String _keyAlias;
+    private X509KeyManager _keyManager;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Construct KeyManager instance
+     * @param keyAlias Alias of the key to be selected
+     * @param keyManager Instance of KeyManager to be wrapped
+     * @throws Exception
+     */
+    public AliasedX509ExtendedKeyManager(String keyAlias, X509KeyManager keyManager) throws Exception
+    {
+        _keyAlias = keyAlias;
+        _keyManager = keyManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#chooseClientAlias(java.lang.String[], java.security.Principal[], java.net.Socket)
+     */
+    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket)
+    {
+        return _keyAlias == null ? _keyManager.chooseClientAlias(keyType, issuers, socket) : _keyAlias;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#chooseServerAlias(java.lang.String, java.security.Principal[], java.net.Socket)
+     */
+    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
+    {
+        return _keyAlias == null ? _keyManager.chooseServerAlias(keyType, issuers, socket) : _keyAlias;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getClientAliases(java.lang.String, java.security.Principal[])
+     */
+    public String[] getClientAliases(String keyType, Principal[] issuers)
+    {
+        return _keyManager.getClientAliases(keyType, issuers);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getServerAliases(java.lang.String, java.security.Principal[])
+     */
+    public String[] getServerAliases(String keyType, Principal[] issuers)
+    {
+        return _keyManager.getServerAliases(keyType, issuers);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getCertificateChain(java.lang.String)
+     */
+    public X509Certificate[] getCertificateChain(String alias)
+    {
+        return _keyManager.getCertificateChain(alias);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getPrivateKey(java.lang.String)
+     */
+    public PrivateKey getPrivateKey(String alias)
+    {
+        return _keyManager.getPrivateKey(alias);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509ExtendedKeyManager#chooseEngineServerAlias(java.lang.String, java.security.Principal[], javax.net.ssl.SSLEngine)
+     */
+    @Override
+    public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine)
+    {
+        return _keyAlias == null ? super.chooseEngineServerAlias(keyType,issuers,engine) : _keyAlias;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509ExtendedKeyManager#chooseEngineClientAlias(String[], Principal[], SSLEngine)
+     */
+    @Override
+    public String chooseEngineClientAlias(String keyType[], Principal[] issuers, SSLEngine engine)
+    {
+        return _keyAlias == null ? super.chooseEngineClientAlias(keyType,issuers,engine) : _keyAlias;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java b/lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java
new file mode 100644 (file)
index 0000000..29d33bb
--- /dev/null
@@ -0,0 +1,107 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.ssl;
+
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509KeyManager;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * KeyManager to select a key with desired alias
+ * while delegating processing to specified KeyManager
+ * Can be used both with server and client sockets
+ */
+public class AliasedX509KeyManager implements X509KeyManager
+{
+    private String _keyAlias;
+    private X509KeyManager _keyManager;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Construct KeyManager instance
+     * @param keyAlias Alias of the key to be selected
+     * @param keyManager Instance of KeyManager to be wrapped
+     * @throws Exception
+     */
+    public AliasedX509KeyManager(String keyAlias, X509KeyManager keyManager) throws Exception
+    {
+        _keyAlias = keyAlias;
+        _keyManager = keyManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#chooseClientAlias(java.lang.String[], java.security.Principal[], java.net.Socket)
+     */
+    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket)
+    {
+        return _keyAlias == null ? _keyManager.chooseClientAlias(keyType, issuers, socket) : _keyAlias;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#chooseServerAlias(java.lang.String, java.security.Principal[], java.net.Socket)
+     */
+    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
+    {
+        return _keyAlias == null ?_keyManager.chooseServerAlias(keyType, issuers, socket) : _keyAlias;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getClientAliases(java.lang.String, java.security.Principal[])
+     */
+    public String[] getClientAliases(String keyType, Principal[] issuers)
+    {
+        return _keyManager.getClientAliases(keyType, issuers);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getServerAliases(java.lang.String, java.security.Principal[])
+     */
+    public String[] getServerAliases(String keyType, Principal[] issuers)
+    {
+        return _keyManager.getServerAliases(keyType, issuers);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getCertificateChain(java.lang.String)
+     */
+    public X509Certificate[] getCertificateChain(String alias)
+    {
+        return _keyManager.getCertificateChain(alias);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getPrivateKey(java.lang.String)
+     */
+    public PrivateKey getPrivateKey(String alias)
+    {
+        return _keyManager.getPrivateKey(alias);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ssl/SslContextFactory.java b/lib/jetty/org/eclipse/jetty/util/ssl/SslContextFactory.java
new file mode 100644 (file)
index 0000000..016c812
--- /dev/null
@@ -0,0 +1,1484 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.ssl;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.CRL;
+import java.security.cert.CertStore;
+import java.security.cert.Certificate;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.X509CertSelector;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.net.ssl.CertPathTrustManagerParameters;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.CertificateUtils;
+import org.eclipse.jetty.util.security.CertificateValidator;
+import org.eclipse.jetty.util.security.Password;
+
+
+/**
+ * SslContextFactory is used to configure SSL connectors
+ * as well as HttpClient. It holds all SSL parameters and
+ * creates SSL context based on these parameters to be
+ * used by the SSL connectors.
+ */
+public class SslContextFactory extends AbstractLifeCycle
+{
+    public final static TrustManager[] TRUST_ALL_CERTS = new X509TrustManager[]{new X509TrustManager()
+    {
+        public java.security.cert.X509Certificate[] getAcceptedIssuers()
+        {
+            return new java.security.cert.X509Certificate[]{};
+        }
+
+        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
+        {
+        }
+
+        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
+        {
+        }
+    }};
+
+    static final Logger LOG = Log.getLogger(SslContextFactory.class);
+
+    public static final String DEFAULT_KEYMANAGERFACTORY_ALGORITHM =
+        (Security.getProperty("ssl.KeyManagerFactory.algorithm") == null ?
+                KeyManagerFactory.getDefaultAlgorithm() : Security.getProperty("ssl.KeyManagerFactory.algorithm"));
+
+    public static final String DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM =
+        (Security.getProperty("ssl.TrustManagerFactory.algorithm") == null ?
+                TrustManagerFactory.getDefaultAlgorithm() : Security.getProperty("ssl.TrustManagerFactory.algorithm"));
+
+    /** String name of key password property. */
+    public static final String KEYPASSWORD_PROPERTY = "org.eclipse.jetty.ssl.keypassword";
+
+    /** String name of keystore password property. */
+    public static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";
+
+    /** Excluded protocols. */
+    private final Set<String> _excludeProtocols = new LinkedHashSet<>();
+
+    /** Included protocols. */
+    private Set<String> _includeProtocols = null;
+
+    /** Excluded cipher suites. */
+    private final Set<String> _excludeCipherSuites = new LinkedHashSet<>();
+    /** Included cipher suites. */
+    private Set<String> _includeCipherSuites = null;
+
+    /** Keystore path. */
+    private String _keyStorePath;
+    /** Keystore provider name */
+    private String _keyStoreProvider;
+    /** Keystore type */
+    private String _keyStoreType = "JKS";
+    /** Keystore input stream */
+    private InputStream _keyStoreInputStream;
+
+    /** SSL certificate alias */
+    private String _certAlias;
+
+    /** Truststore path */
+    private String _trustStorePath;
+    /** Truststore provider name */
+    private String _trustStoreProvider;
+    /** Truststore type */
+    private String _trustStoreType = "JKS";
+    /** Truststore input stream */
+    private InputStream _trustStoreInputStream;
+
+    /** Set to true if client certificate authentication is required */
+    private boolean _needClientAuth = false;
+    /** Set to true if client certificate authentication is desired */
+    private boolean _wantClientAuth = false;
+
+    /** Keystore password */
+    private transient Password _keyStorePassword;
+    /** Key manager password */
+    private transient Password _keyManagerPassword;
+    /** Truststore password */
+    private transient Password _trustStorePassword;
+
+    /** SSL provider name */
+    private String _sslProvider;
+    /** SSL protocol name */
+    private String _sslProtocol = "TLS";
+
+    /** SecureRandom algorithm */
+    private String _secureRandomAlgorithm;
+    /** KeyManager factory algorithm */
+    private String _keyManagerFactoryAlgorithm = DEFAULT_KEYMANAGERFACTORY_ALGORITHM;
+    /** TrustManager factory algorithm */
+    private String _trustManagerFactoryAlgorithm = DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM;
+
+    /** Set to true if SSL certificate validation is required */
+    private boolean _validateCerts;
+    /** Set to true if SSL certificate of the peer validation is required */
+    private boolean _validatePeerCerts;
+    /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
+    private int _maxCertPathLength = -1;
+    /** Path to file that contains Certificate Revocation List */
+    private String _crlPath;
+    /** Set to true to enable CRL Distribution Points (CRLDP) support */
+    private boolean _enableCRLDP = false;
+    /** Set to true to enable On-Line Certificate Status Protocol (OCSP) support */
+    private boolean _enableOCSP = false;
+    /** Location of OCSP Responder */
+    private String _ocspResponderURL;
+
+    /** SSL keystore */
+    private KeyStore _keyStore;
+    /** SSL truststore */
+    private KeyStore _trustStore;
+    /** Set to true to enable SSL Session caching */
+    private boolean _sessionCachingEnabled = true;
+    /** SSL session cache size */
+    private int _sslSessionCacheSize;
+    /** SSL session timeout */
+    private int _sslSessionTimeout;
+
+    /** SSL context */
+    private SSLContext _context;
+
+    /** EndpointIdentificationAlgorithm - when set to "HTTPS" hostname verification will be enabled */
+    private String _endpointIdentificationAlgorithm = null;
+
+    /** Whether to blindly trust certificates */
+    private boolean _trustAll;
+
+    /** Whether TLS renegotiation is allowed */
+    private boolean _renegotiationAllowed = true;
+
+    /**
+     * Construct an instance of SslContextFactory
+     * Default constructor for use in XmlConfiguration files
+     */
+    public SslContextFactory()
+    {
+        this(false);
+    }
+
+    /**
+     * Construct an instance of SslContextFactory
+     * Default constructor for use in XmlConfiguration files
+     * @param trustAll whether to blindly trust all certificates
+     * @see #setTrustAll(boolean)
+     */
+    public SslContextFactory(boolean trustAll)
+    {
+        setTrustAll(trustAll);
+    }
+
+    /**
+     * Construct an instance of SslContextFactory
+     * @param keyStorePath default keystore location
+     */
+    public SslContextFactory(String keyStorePath)
+    {
+        _keyStorePath = keyStorePath;
+    }
+
+    /**
+     * Create the SSLContext object and start the lifecycle
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (_context == null)
+        {
+            if (_keyStore==null && _keyStoreInputStream == null && _keyStorePath == null &&
+                _trustStore==null && _trustStoreInputStream == null && _trustStorePath == null )
+            {
+                TrustManager[] trust_managers=null;
+
+                if (_trustAll)
+                {
+                    LOG.debug("No keystore or trust store configured.  ACCEPTING UNTRUSTED CERTIFICATES!!!!!");
+                    // Create a trust manager that does not validate certificate chains
+                    trust_managers = TRUST_ALL_CERTS;
+                }
+
+                SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm);
+                SSLContext context = SSLContext.getInstance(_sslProtocol);
+                context.init(null, trust_managers, secureRandom);
+                _context = context;
+            }
+            else
+            {
+                // verify that keystore and truststore
+                // parameters are set up correctly
+                checkKeyStore();
+
+                KeyStore keyStore = loadKeyStore();
+                KeyStore trustStore = loadTrustStore();
+
+                Collection<? extends CRL> crls = loadCRL(_crlPath);
+
+                if (_validateCerts && keyStore != null)
+                {
+                    if (_certAlias == null)
+                    {
+                        List<String> aliases = Collections.list(keyStore.aliases());
+                        _certAlias = aliases.size() == 1 ? aliases.get(0) : null;
+                    }
+
+                    Certificate cert = _certAlias == null?null:keyStore.getCertificate(_certAlias);
+                    if (cert == null)
+                    {
+                        throw new Exception("No certificate found in the keystore" + (_certAlias==null ? "":" for alias " + _certAlias));
+                    }
+
+                    CertificateValidator validator = new CertificateValidator(trustStore, crls);
+                    validator.setMaxCertPathLength(_maxCertPathLength);
+                    validator.setEnableCRLDP(_enableCRLDP);
+                    validator.setEnableOCSP(_enableOCSP);
+                    validator.setOcspResponderURL(_ocspResponderURL);
+                    validator.validate(keyStore, cert);
+                }
+
+                KeyManager[] keyManagers = getKeyManagers(keyStore);
+                TrustManager[] trustManagers = getTrustManagers(trustStore,crls);
+
+                SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm);
+                SSLContext context = _sslProvider == null ? SSLContext.getInstance(_sslProtocol) : SSLContext.getInstance(_sslProtocol,_sslProvider);
+                context.init(keyManagers,trustManagers,secureRandom);
+                _context = context;
+            }
+
+            SSLEngine engine = newSSLEngine();
+            LOG.debug("Enabled Protocols {} of {}",Arrays.asList(engine.getEnabledProtocols()),Arrays.asList(engine.getSupportedProtocols()));
+            if (LOG.isDebugEnabled())
+                LOG.debug("Enabled Ciphers   {} of {}",Arrays.asList(engine.getEnabledCipherSuites()),Arrays.asList(engine.getSupportedCipherSuites()));
+        }
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        _context = null;
+        super.doStop();
+    }
+
+    /**
+     * @return The array of protocol names to exclude from
+     * {@link SSLEngine#setEnabledProtocols(String[])}
+     */
+    public String[] getExcludeProtocols()
+    {
+        return _excludeProtocols.toArray(new String[_excludeProtocols.size()]);
+    }
+
+    /**
+     * @param protocols
+     *            The array of protocol names to exclude from
+     *            {@link SSLEngine#setEnabledProtocols(String[])}
+     */
+    public void setExcludeProtocols(String... protocols)
+    {
+        checkNotStarted();
+        _excludeProtocols.clear();
+        _excludeProtocols.addAll(Arrays.asList(protocols));
+    }
+
+    /**
+     * @param protocol Protocol names to add to {@link SSLEngine#setEnabledProtocols(String[])}
+     */
+    public void addExcludeProtocols(String... protocol)
+    {
+        checkNotStarted();
+        _excludeProtocols.addAll(Arrays.asList(protocol));
+    }
+
+    /**
+     * @return The array of protocol names to include in
+     * {@link SSLEngine#setEnabledProtocols(String[])}
+     */
+    public String[] getIncludeProtocols()
+    {
+        return _includeProtocols.toArray(new String[_includeProtocols.size()]);
+    }
+
+    /**
+     * @param protocols
+     *            The array of protocol names to include in
+     *            {@link SSLEngine#setEnabledProtocols(String[])}
+     */
+    public void setIncludeProtocols(String... protocols)
+    {
+        checkNotStarted();
+        _includeProtocols = new LinkedHashSet<>(Arrays.asList(protocols));
+    }
+
+    /**
+     * @return The array of cipher suite names to exclude from
+     * {@link SSLEngine#setEnabledCipherSuites(String[])}
+     */
+    public String[] getExcludeCipherSuites()
+    {
+        return _excludeCipherSuites.toArray(new String[_excludeCipherSuites.size()]);
+    }
+
+    /**
+     * You can either use the exact cipher suite name or a a regular expression.
+     * @param cipherSuites
+     *            The array of cipher suite names to exclude from
+     *            {@link SSLEngine#setEnabledCipherSuites(String[])}
+     */
+    public void setExcludeCipherSuites(String... cipherSuites)
+    {
+        checkNotStarted();
+        _excludeCipherSuites.clear();
+        _excludeCipherSuites.addAll(Arrays.asList(cipherSuites));
+    }
+
+    /**
+     * @param cipher Cipher names to add to {@link SSLEngine#setEnabledCipherSuites(String[])}
+     */
+    public void addExcludeCipherSuites(String... cipher)
+    {
+        checkNotStarted();
+        _excludeCipherSuites.addAll(Arrays.asList(cipher));
+    }
+
+    /**
+     * @return The array of cipher suite names to include in
+     * {@link SSLEngine#setEnabledCipherSuites(String[])}
+     */
+    public String[] getIncludeCipherSuites()
+    {
+        return _includeCipherSuites.toArray(new String[_includeCipherSuites.size()]);
+    }
+
+    /**
+     * You can either use the exact cipher suite name or a a regular expression.
+     * @param cipherSuites
+     *            The array of cipher suite names to include in
+     *            {@link SSLEngine#setEnabledCipherSuites(String[])}
+     */
+    public void setIncludeCipherSuites(String... cipherSuites)
+    {
+        checkNotStarted();
+        _includeCipherSuites = new LinkedHashSet<>(Arrays.asList(cipherSuites));
+    }
+
+    /**
+     * @return The file or URL of the SSL Key store.
+     */
+    public String getKeyStorePath()
+    {
+        return _keyStorePath;
+    }
+
+    /**
+     * @param keyStorePath
+     *            The file or URL of the SSL Key store.
+     */
+    public void setKeyStorePath(String keyStorePath)
+    {
+        checkNotStarted();
+        _keyStorePath = keyStorePath;
+    }
+
+    /**
+     * @return The provider of the key store
+     */
+    public String getKeyStoreProvider()
+    {
+        return _keyStoreProvider;
+    }
+
+    /**
+     * @param keyStoreProvider
+     *            The provider of the key store
+     */
+    public void setKeyStoreProvider(String keyStoreProvider)
+    {
+        checkNotStarted();
+        _keyStoreProvider = keyStoreProvider;
+    }
+
+    /**
+     * @return The type of the key store (default "JKS")
+     */
+    public String getKeyStoreType()
+    {
+        return (_keyStoreType);
+    }
+
+    /**
+     * @param keyStoreType
+     *            The type of the key store (default "JKS")
+     */
+    public void setKeyStoreType(String keyStoreType)
+    {
+        checkNotStarted();
+        _keyStoreType = keyStoreType;
+    }
+
+    /**
+     * @return Alias of SSL certificate for the connector
+     */
+    public String getCertAlias()
+    {
+        return _certAlias;
+    }
+
+    /**
+     * @param certAlias
+     *            Alias of SSL certificate for the connector
+     */
+    public void setCertAlias(String certAlias)
+    {
+        checkNotStarted();
+        _certAlias = certAlias;
+    }
+
+    /**
+     * @return The file name or URL of the trust store location
+     */
+    public String getTrustStore()
+    {
+        return _trustStorePath;
+    }
+
+    /**
+     * @param trustStorePath
+     *            The file name or URL of the trust store location
+     */
+    public void setTrustStorePath(String trustStorePath)
+    {
+        checkNotStarted();
+        _trustStorePath = trustStorePath;
+    }
+
+    /**
+     * @return The provider of the trust store
+     */
+    public String getTrustStoreProvider()
+    {
+        return _trustStoreProvider;
+    }
+
+    /**
+     * @param trustStoreProvider
+     *            The provider of the trust store
+     */
+    public void setTrustStoreProvider(String trustStoreProvider)
+    {
+        checkNotStarted();
+        _trustStoreProvider = trustStoreProvider;
+    }
+
+    /**
+     * @return The type of the trust store (default "JKS")
+     */
+    public String getTrustStoreType()
+    {
+        return _trustStoreType;
+    }
+
+    /**
+     * @param trustStoreType
+     *            The type of the trust store (default "JKS")
+     */
+    public void setTrustStoreType(String trustStoreType)
+    {
+        checkNotStarted();
+        _trustStoreType = trustStoreType;
+    }
+
+    /**
+     * @return True if SSL needs client authentication.
+     * @see SSLEngine#getNeedClientAuth()
+     */
+    public boolean getNeedClientAuth()
+    {
+        return _needClientAuth;
+    }
+
+    /**
+     * @param needClientAuth
+     *            True if SSL needs client authentication.
+     * @see SSLEngine#getNeedClientAuth()
+     */
+    public void setNeedClientAuth(boolean needClientAuth)
+    {
+        checkNotStarted();
+        _needClientAuth = needClientAuth;
+    }
+
+    /**
+     * @return True if SSL wants client authentication.
+     * @see SSLEngine#getWantClientAuth()
+     */
+    public boolean getWantClientAuth()
+    {
+        return _wantClientAuth;
+    }
+
+    /**
+     * @param wantClientAuth
+     *            True if SSL wants client authentication.
+     * @see SSLEngine#getWantClientAuth()
+     */
+    public void setWantClientAuth(boolean wantClientAuth)
+    {
+        checkNotStarted();
+        _wantClientAuth = wantClientAuth;
+    }
+
+    /**
+     * @return true if SSL certificate has to be validated
+     */
+    public boolean isValidateCerts()
+    {
+        return _validateCerts;
+    }
+
+    /**
+     * @param validateCerts
+     *            true if SSL certificates have to be validated
+     */
+    public void setValidateCerts(boolean validateCerts)
+    {
+        checkNotStarted();
+        _validateCerts = validateCerts;
+    }
+
+    /**
+     * @return true if SSL certificates of the peer have to be validated
+     */
+    public boolean isValidatePeerCerts()
+    {
+        return _validatePeerCerts;
+    }
+
+    /**
+     * @param validatePeerCerts
+     *            true if SSL certificates of the peer have to be validated
+     */
+    public void setValidatePeerCerts(boolean validatePeerCerts)
+    {
+        checkNotStarted();
+        _validatePeerCerts = validatePeerCerts;
+    }
+
+
+    /**
+     * @param password
+     *            The password for the key store
+     */
+    public void setKeyStorePassword(String password)
+    {
+        checkNotStarted();
+        _keyStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
+    }
+
+    /**
+     * @param password
+     *            The password (if any) for the specific key within the key store
+     */
+    public void setKeyManagerPassword(String password)
+    {
+        checkNotStarted();
+        _keyManagerPassword = Password.getPassword(KEYPASSWORD_PROPERTY,password,null);
+    }
+
+    /**
+     * @param password
+     *            The password for the trust store
+     */
+    public void setTrustStorePassword(String password)
+    {
+        checkNotStarted();
+        _trustStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
+    }
+
+    /**
+     * @return The SSL provider name, which if set is passed to
+     * {@link SSLContext#getInstance(String, String)}
+     */
+    public String getProvider()
+    {
+        return _sslProvider;
+    }
+
+    /**
+     * @param provider
+     *            The SSL provider name, which if set is passed to
+     *            {@link SSLContext#getInstance(String, String)}
+     */
+    public void setProvider(String provider)
+    {
+        checkNotStarted();
+        _sslProvider = provider;
+    }
+
+    /**
+     * @return The SSL protocol (default "TLS") passed to
+     * {@link SSLContext#getInstance(String, String)}
+     */
+    public String getProtocol()
+    {
+        return _sslProtocol;
+    }
+
+    /**
+     * @param protocol
+     *            The SSL protocol (default "TLS") passed to
+     *            {@link SSLContext#getInstance(String, String)}
+     */
+    public void setProtocol(String protocol)
+    {
+        checkNotStarted();
+        _sslProtocol = protocol;
+    }
+
+    /**
+     * @return The algorithm name, which if set is passed to
+     * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to
+     * {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
+     */
+    public String getSecureRandomAlgorithm()
+    {
+        return _secureRandomAlgorithm;
+    }
+
+    /**
+     * @param algorithm
+     *            The algorithm name, which if set is passed to
+     *            {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to
+     *            {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
+     */
+    public void setSecureRandomAlgorithm(String algorithm)
+    {
+        checkNotStarted();
+        _secureRandomAlgorithm = algorithm;
+    }
+
+    /**
+     * @return The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
+     */
+    public String getSslKeyManagerFactoryAlgorithm()
+    {
+        return (_keyManagerFactoryAlgorithm);
+    }
+
+    /**
+     * @param algorithm
+     *            The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
+     */
+    public void setSslKeyManagerFactoryAlgorithm(String algorithm)
+    {
+        checkNotStarted();
+        _keyManagerFactoryAlgorithm = algorithm;
+    }
+
+    /**
+     * @return The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
+     */
+    public String getTrustManagerFactoryAlgorithm()
+    {
+        return (_trustManagerFactoryAlgorithm);
+    }
+
+    /**
+     * @return True if all certificates should be trusted if there is no KeyStore or TrustStore
+     */
+    public boolean isTrustAll()
+    {
+        return _trustAll;
+    }
+
+    /**
+     * @param trustAll True if all certificates should be trusted if there is no KeyStore or TrustStore
+     */
+    public void setTrustAll(boolean trustAll)
+    {
+        _trustAll = trustAll;
+        if(trustAll)
+            setEndpointIdentificationAlgorithm(null);
+    }
+
+    /**
+     * @param algorithm
+     *            The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
+     *            Use the string "TrustAll" to install a trust manager that trusts all.
+     */
+    public void setTrustManagerFactoryAlgorithm(String algorithm)
+    {
+        checkNotStarted();
+        _trustManagerFactoryAlgorithm = algorithm;
+    }
+
+    /**
+     * @return whether TLS renegotiation is allowed (true by default)
+     */
+    public boolean isRenegotiationAllowed()
+    {
+        return _renegotiationAllowed;
+    }
+
+    /**
+     * @param renegotiationAllowed whether TLS renegotiation is allowed
+     */
+    public void setRenegotiationAllowed(boolean renegotiationAllowed)
+    {
+        _renegotiationAllowed = renegotiationAllowed;
+    }
+
+    /**
+     * @return Path to file that contains Certificate Revocation List
+     */
+    public String getCrlPath()
+    {
+        return _crlPath;
+    }
+
+    /**
+     * @param crlPath
+     *            Path to file that contains Certificate Revocation List
+     */
+    public void setCrlPath(String crlPath)
+    {
+        checkNotStarted();
+        _crlPath = crlPath;
+    }
+
+    /**
+     * @return Maximum number of intermediate certificates in
+     * the certification path (-1 for unlimited)
+     */
+    public int getMaxCertPathLength()
+    {
+        return _maxCertPathLength;
+    }
+
+    /**
+     * @param maxCertPathLength
+     *            maximum number of intermediate certificates in
+     *            the certification path (-1 for unlimited)
+     */
+    public void setMaxCertPathLength(int maxCertPathLength)
+    {
+        checkNotStarted();
+        _maxCertPathLength = maxCertPathLength;
+    }
+
+    /**
+     * @return The SSLContext
+     */
+    public SSLContext getSslContext()
+    {
+        if (!isStarted())
+            throw new IllegalStateException(getState());
+        return _context;
+    }
+
+    /**
+     * @param sslContext
+     *            Set a preconfigured SSLContext
+     */
+    public void setSslContext(SSLContext sslContext)
+    {
+        checkNotStarted();
+        _context = sslContext;
+    }
+
+    /**
+     * When set to "HTTPS" hostname verification will be enabled
+     *
+     * @param endpointIdentificationAlgorithm Set the endpointIdentificationAlgorithm
+     */
+    public void setEndpointIdentificationAlgorithm(String endpointIdentificationAlgorithm)
+    {
+        this._endpointIdentificationAlgorithm = endpointIdentificationAlgorithm;
+    }
+
+    /**
+     * Override this method to provide alternate way to load a keystore.
+     *
+     * @return the key store instance
+     * @throws Exception if the keystore cannot be loaded
+     */
+    protected KeyStore loadKeyStore() throws Exception
+    {
+        return _keyStore != null ? _keyStore : CertificateUtils.getKeyStore(_keyStoreInputStream,
+                _keyStorePath, _keyStoreType, _keyStoreProvider,
+                _keyStorePassword==null? null: _keyStorePassword.toString());
+    }
+
+    /**
+     * Override this method to provide alternate way to load a truststore.
+     *
+     * @return the key store instance
+     * @throws Exception if the truststore cannot be loaded
+     */
+    protected KeyStore loadTrustStore() throws Exception
+    {
+        return _trustStore != null ? _trustStore : CertificateUtils.getKeyStore(_trustStoreInputStream,
+                _trustStorePath, _trustStoreType,  _trustStoreProvider,
+                _trustStorePassword==null? null: _trustStorePassword.toString());
+    }
+
+    /**
+     * Loads certificate revocation list (CRL) from a file.
+     *
+     * Required for integrations to be able to override the mechanism used to
+     * load CRL in order to provide their own implementation.
+     *
+     * @param crlPath path of certificate revocation list file
+     * @return Collection of CRL's
+     * @throws Exception if the certificate revocation list cannot be loaded
+     */
+    protected Collection<? extends CRL> loadCRL(String crlPath) throws Exception
+    {
+        return CertificateUtils.loadCRL(crlPath);
+    }
+
+    protected KeyManager[] getKeyManagers(KeyStore keyStore) throws Exception
+    {
+        KeyManager[] managers = null;
+
+        if (keyStore != null)
+        {
+            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_keyManagerFactoryAlgorithm);
+            keyManagerFactory.init(keyStore,_keyManagerPassword == null?(_keyStorePassword == null?null:_keyStorePassword.toString().toCharArray()):_keyManagerPassword.toString().toCharArray());
+            managers = keyManagerFactory.getKeyManagers();
+
+            if (_certAlias != null)
+            {
+                for (int idx = 0; idx < managers.length; idx++)
+                {
+                    if (managers[idx] instanceof X509KeyManager)
+                    {
+                        managers[idx] = new AliasedX509ExtendedKeyManager(_certAlias,(X509KeyManager)managers[idx]);
+                    }
+                }
+            }
+        }
+
+        return managers;
+    }
+
+    protected TrustManager[] getTrustManagers(KeyStore trustStore, Collection<? extends CRL> crls) throws Exception
+    {
+        TrustManager[] managers = null;
+        if (trustStore != null)
+        {
+            // Revocation checking is only supported for PKIX algorithm
+            if (_validatePeerCerts && _trustManagerFactoryAlgorithm.equalsIgnoreCase("PKIX"))
+            {
+                PKIXBuilderParameters pbParams = new PKIXBuilderParameters(trustStore,new X509CertSelector());
+
+                // Set maximum certification path length
+                pbParams.setMaxPathLength(_maxCertPathLength);
+
+                // Make sure revocation checking is enabled
+                pbParams.setRevocationEnabled(true);
+
+                if (crls != null && !crls.isEmpty())
+                {
+                    pbParams.addCertStore(CertStore.getInstance("Collection",new CollectionCertStoreParameters(crls)));
+                }
+
+                if (_enableCRLDP)
+                {
+                    // Enable Certificate Revocation List Distribution Points (CRLDP) support
+                    System.setProperty("com.sun.security.enableCRLDP","true");
+                }
+
+                if (_enableOCSP)
+                {
+                    // Enable On-Line Certificate Status Protocol (OCSP) support
+                    Security.setProperty("ocsp.enable","true");
+
+                    if (_ocspResponderURL != null)
+                    {
+                        // Override location of OCSP Responder
+                        Security.setProperty("ocsp.responderURL", _ocspResponderURL);
+                    }
+                }
+
+                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm);
+                trustManagerFactory.init(new CertPathTrustManagerParameters(pbParams));
+
+                managers = trustManagerFactory.getTrustManagers();
+            }
+            else
+            {
+                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm);
+                trustManagerFactory.init(trustStore);
+
+                managers = trustManagerFactory.getTrustManagers();
+            }
+        }
+
+        return managers;
+    }
+
+    /**
+     * Check KeyStore Configuration. Ensures that if keystore has been
+     * configured but there's no truststore, that keystore is
+     * used as truststore.
+     * @throws IllegalStateException if SslContextFactory configuration can't be used.
+     */
+    public void checkKeyStore()
+    {
+        if (_context != null)
+            return;
+
+        if (_keyStore == null && _keyStoreInputStream == null && _keyStorePath == null)
+            throw new IllegalStateException("SSL doesn't have a valid keystore");
+
+        // if the keystore has been configured but there is no
+        // truststore configured, use the keystore as the truststore
+        if (_trustStore == null && _trustStoreInputStream == null && _trustStorePath == null)
+        {
+            _trustStore = _keyStore;
+            _trustStorePath = _keyStorePath;
+            _trustStoreInputStream = _keyStoreInputStream;
+            _trustStoreType = _keyStoreType;
+            _trustStoreProvider = _keyStoreProvider;
+            _trustStorePassword = _keyStorePassword;
+            _trustManagerFactoryAlgorithm = _keyManagerFactoryAlgorithm;
+        }
+
+        // It's the same stream we cannot read it twice, so read it once in memory
+        if (_keyStoreInputStream != null && _keyStoreInputStream == _trustStoreInputStream)
+        {
+            try
+            {
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                IO.copy(_keyStoreInputStream, baos);
+                _keyStoreInputStream.close();
+
+                _keyStoreInputStream = new ByteArrayInputStream(baos.toByteArray());
+                _trustStoreInputStream = new ByteArrayInputStream(baos.toByteArray());
+            }
+            catch (Exception ex)
+            {
+                throw new IllegalStateException(ex);
+            }
+        }
+    }
+
+    /**
+     * Select protocols to be used by the connector
+     * based on configured inclusion and exclusion lists
+     * as well as enabled and supported protocols.
+     * @param enabledProtocols Array of enabled protocols
+     * @param supportedProtocols Array of supported protocols
+     * @return Array of protocols to enable
+     */
+    public String[] selectProtocols(String[] enabledProtocols, String[] supportedProtocols)
+    {
+        Set<String> selected_protocols = new LinkedHashSet<>();
+
+        // Set the starting protocols - either from the included or enabled list
+        if (_includeProtocols!=null)
+        {
+            // Use only the supported included protocols
+            for (String protocol : _includeProtocols)
+                if(Arrays.asList(supportedProtocols).contains(protocol))
+                    selected_protocols.add(protocol);
+        }
+        else
+            selected_protocols.addAll(Arrays.asList(enabledProtocols));
+
+
+        // Remove any excluded protocols
+        selected_protocols.removeAll(_excludeProtocols);
+
+        return selected_protocols.toArray(new String[selected_protocols.size()]);
+    }
+
+    /**
+     * Select cipher suites to be used by the connector
+     * based on configured inclusion and exclusion lists
+     * as well as enabled and supported cipher suite lists.
+     * @param enabledCipherSuites Array of enabled cipher suites
+     * @param supportedCipherSuites Array of supported cipher suites
+     * @return Array of cipher suites to enable
+     */
+    public String[] selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites)
+    {
+        Set<String> selected_ciphers = new CopyOnWriteArraySet<>();
+
+        // Set the starting ciphers - either from the included or enabled list
+        if (_includeCipherSuites!=null)
+            processIncludeCipherSuites(supportedCipherSuites, selected_ciphers);
+        else
+            selected_ciphers.addAll(Arrays.asList(enabledCipherSuites));
+
+        removeExcludedCipherSuites(selected_ciphers);
+
+        return selected_ciphers.toArray(new String[selected_ciphers.size()]);
+    }
+
+    private void processIncludeCipherSuites(String[] supportedCipherSuites, Set<String> selected_ciphers)
+    {
+        for (String cipherSuite : _includeCipherSuites)
+        {
+            Pattern p = Pattern.compile(cipherSuite);
+            for (String supportedCipherSuite : supportedCipherSuites)
+            {
+                Matcher m = p.matcher(supportedCipherSuite);
+                if (m.matches())
+                    selected_ciphers.add(supportedCipherSuite);
+            }
+        }
+    }
+
+    private void removeExcludedCipherSuites(Set<String> selected_ciphers)
+    {
+        for (String excludeCipherSuite : _excludeCipherSuites)
+        {
+            Pattern excludeCipherPattern = Pattern.compile(excludeCipherSuite);
+            for (String selectedCipherSuite : selected_ciphers)
+            {
+                Matcher m = excludeCipherPattern.matcher(selectedCipherSuite);
+                if (m.matches())
+                    selected_ciphers.remove(selectedCipherSuite);
+            }
+        }
+    }
+
+    /**
+     * Check if the lifecycle has been started and throw runtime exception
+     */
+    protected void checkNotStarted()
+    {
+        if (isStarted())
+            throw new IllegalStateException("Cannot modify configuration when "+getState());
+    }
+
+    /**
+     * @return true if CRL Distribution Points support is enabled
+     */
+    public boolean isEnableCRLDP()
+    {
+        return _enableCRLDP;
+    }
+
+    /** Enables CRL Distribution Points Support
+     * @param enableCRLDP true - turn on, false - turns off
+     */
+    public void setEnableCRLDP(boolean enableCRLDP)
+    {
+        checkNotStarted();
+        _enableCRLDP = enableCRLDP;
+    }
+
+    /**
+     * @return true if On-Line Certificate Status Protocol support is enabled
+     */
+    public boolean isEnableOCSP()
+    {
+        return _enableOCSP;
+    }
+
+    /** Enables On-Line Certificate Status Protocol support
+     * @param enableOCSP true - turn on, false - turn off
+     */
+    public void setEnableOCSP(boolean enableOCSP)
+    {
+        checkNotStarted();
+        _enableOCSP = enableOCSP;
+    }
+
+    /**
+     * @return Location of the OCSP Responder
+     */
+    public String getOcspResponderURL()
+    {
+        return _ocspResponderURL;
+    }
+
+    /** Set the location of the OCSP Responder.
+     * @param ocspResponderURL location of the OCSP Responder
+     */
+    public void setOcspResponderURL(String ocspResponderURL)
+    {
+        checkNotStarted();
+        _ocspResponderURL = ocspResponderURL;
+    }
+
+    /** Set the key store.
+     * @param keyStore the key store to set
+     */
+    public void setKeyStore(KeyStore keyStore)
+    {
+        checkNotStarted();
+        _keyStore = keyStore;
+    }
+
+    /** Set the trust store.
+     * @param trustStore the trust store to set
+     */
+    public void setTrustStore(KeyStore trustStore)
+    {
+        checkNotStarted();
+        _trustStore = trustStore;
+    }
+
+    /** Set the key store resource.
+     * @param resource the key store resource to set
+     */
+    public void setKeyStoreResource(Resource resource)
+    {
+        checkNotStarted();
+        try
+        {
+            _keyStoreInputStream = resource.getInputStream();
+        }
+        catch (IOException e)
+        {
+             throw new InvalidParameterException("Unable to get resource "+
+                     "input stream for resource "+resource.toString());
+        }
+    }
+
+    /** Set the trust store resource.
+     * @param resource the trust store resource to set
+     */
+    public void setTrustStoreResource(Resource resource)
+    {
+        checkNotStarted();
+        try
+        {
+            _trustStoreInputStream = resource.getInputStream();
+        }
+        catch (IOException e)
+        {
+             throw new InvalidParameterException("Unable to get resource "+
+                     "input stream for resource "+resource.toString());
+        }
+    }
+
+    /**
+    * @return true if SSL Session caching is enabled
+    */
+    public boolean isSessionCachingEnabled()
+    {
+        return _sessionCachingEnabled;
+    }
+
+    /** Set the flag to enable SSL Session caching.
+    * @param enableSessionCaching the value of the flag
+    */
+    public void setSessionCachingEnabled(boolean enableSessionCaching)
+    {
+        _sessionCachingEnabled = enableSessionCaching;
+    }
+
+    /** Get SSL session cache size.
+     * @return SSL session cache size
+     */
+    public int getSslSessionCacheSize()
+    {
+        return _sslSessionCacheSize;
+    }
+
+    /** SEt SSL session cache size.
+     * @param sslSessionCacheSize SSL session cache size to set
+     */
+    public void setSslSessionCacheSize(int sslSessionCacheSize)
+    {
+        _sslSessionCacheSize = sslSessionCacheSize;
+    }
+
+    /** Get SSL session timeout.
+     * @return SSL session timeout
+     */
+    public int getSslSessionTimeout()
+    {
+        return _sslSessionTimeout;
+    }
+
+    /** Set SSL session timeout.
+     * @param sslSessionTimeout SSL session timeout to set
+     */
+    public void setSslSessionTimeout(int sslSessionTimeout)
+    {
+        _sslSessionTimeout = sslSessionTimeout;
+    }
+
+
+    public SSLServerSocket newSslServerSocket(String host,int port,int backlog) throws IOException
+    {
+        SSLServerSocketFactory factory = _context.getServerSocketFactory();
+
+        SSLServerSocket socket =
+            (SSLServerSocket) (host==null ?
+                        factory.createServerSocket(port,backlog):
+                        factory.createServerSocket(port,backlog,InetAddress.getByName(host)));
+
+        if (getWantClientAuth())
+            socket.setWantClientAuth(getWantClientAuth());
+        if (getNeedClientAuth())
+            socket.setNeedClientAuth(getNeedClientAuth());
+
+        socket.setEnabledCipherSuites(selectCipherSuites(
+                                            socket.getEnabledCipherSuites(),
+                                            socket.getSupportedCipherSuites()));
+        socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(),socket.getSupportedProtocols()));
+
+        return socket;
+    }
+
+    public SSLSocket newSslSocket() throws IOException
+    {
+        SSLSocketFactory factory = _context.getSocketFactory();
+
+        SSLSocket socket = (SSLSocket)factory.createSocket();
+
+        if (getWantClientAuth())
+            socket.setWantClientAuth(getWantClientAuth());
+        if (getNeedClientAuth())
+            socket.setNeedClientAuth(getNeedClientAuth());
+
+        socket.setEnabledCipherSuites(selectCipherSuites(
+                                            socket.getEnabledCipherSuites(),
+                                            socket.getSupportedCipherSuites()));
+        socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(),socket.getSupportedProtocols()));
+
+        return socket;
+    }
+
+    /**
+     * Factory method for "scratch" {@link SSLEngine}s, usually only used for retrieving configuration
+     * information such as the application buffer size or the list of protocols/ciphers.
+     * <p />
+     * This method should not be used for creating {@link SSLEngine}s that are used in actual socket
+     * communication.
+     *
+     * @return a new, "scratch" {@link SSLEngine}
+     */
+    public SSLEngine newSSLEngine()
+    {
+        if (!isRunning())
+            throw new IllegalStateException("!STARTED");
+        SSLEngine sslEngine=_context.createSSLEngine();
+        customize(sslEngine);
+        return sslEngine;
+    }
+
+    /**
+     * General purpose factory method for creating {@link SSLEngine}s, although creation of
+     * {@link SSLEngine}s on the server-side should prefer {@link #newSSLEngine(InetSocketAddress)}.
+     *
+     * @param host the remote host
+     * @param port the remote port
+     * @return a new {@link SSLEngine}
+     */
+    public SSLEngine newSSLEngine(String host, int port)
+    {
+        if (!isRunning())
+            throw new IllegalStateException("!STARTED");
+        SSLEngine sslEngine=isSessionCachingEnabled()
+            ? _context.createSSLEngine(host, port)
+            : _context.createSSLEngine();
+        customize(sslEngine);
+        return sslEngine;
+    }
+
+    /**
+     * Server-side only factory method for creating {@link SSLEngine}s.
+     * <p />
+     * If the given {@code address} is null, it is equivalent to {@link #newSSLEngine()}, otherwise
+     * {@link #newSSLEngine(String, int)} is called.
+     * <p />
+     * If {@link #getNeedClientAuth()} is {@code true}, then the host name is passed to
+     * {@link #newSSLEngine(String, int)}, possibly incurring in a reverse DNS lookup, which takes time
+     * and may hang the selector (since this method is usually called by the selector thread).
+     * <p />
+     * Otherwise, the host address is passed to {@link #newSSLEngine(String, int)} without DNS lookup
+     * penalties.
+     * <p />
+     * Clients that wish to create {@link SSLEngine} instances must use {@link #newSSLEngine(String, int)}.
+     *
+     * @param address the remote peer address
+     * @return a new {@link SSLEngine}
+     */
+    public SSLEngine newSSLEngine(InetSocketAddress address)
+    {
+        if (address == null)
+            return newSSLEngine();
+
+        boolean useHostName = getNeedClientAuth();
+        String hostName = useHostName ? address.getHostName() : address.getAddress().getHostAddress();
+        return newSSLEngine(hostName, address.getPort());
+    }
+
+    public void customize(SSLEngine sslEngine)
+    {
+        SSLParameters sslParams = sslEngine.getSSLParameters();
+        sslParams.setEndpointIdentificationAlgorithm(_endpointIdentificationAlgorithm);
+        sslEngine.setSSLParameters(sslParams);
+
+        if (getWantClientAuth())
+            sslEngine.setWantClientAuth(getWantClientAuth());
+        if (getNeedClientAuth())
+            sslEngine.setNeedClientAuth(getNeedClientAuth());
+
+        sslEngine.setEnabledCipherSuites(selectCipherSuites(
+                sslEngine.getEnabledCipherSuites(),
+                sslEngine.getSupportedCipherSuites()));
+
+        sslEngine.setEnabledProtocols(selectProtocols(sslEngine.getEnabledProtocols(),sslEngine.getSupportedProtocols()));
+    }
+
+    public static X509Certificate[] getCertChain(SSLSession sslSession)
+    {
+        try
+        {
+            Certificate[] javaxCerts=sslSession.getPeerCertificates();
+            if (javaxCerts==null||javaxCerts.length==0)
+                return null;
+
+            int length=javaxCerts.length;
+            X509Certificate[] javaCerts=new X509Certificate[length];
+
+            java.security.cert.CertificateFactory cf=java.security.cert.CertificateFactory.getInstance("X.509");
+            for (int i=0; i<length; i++)
+            {
+                byte bytes[]=javaxCerts[i].getEncoded();
+                ByteArrayInputStream stream=new ByteArrayInputStream(bytes);
+                javaCerts[i]=(X509Certificate)cf.generateCertificate(stream);
+            }
+
+            return javaCerts;
+        }
+        catch (SSLPeerUnverifiedException pue)
+        {
+            return null;
+        }
+        catch (Exception e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+            return null;
+        }
+    }
+
+    /**
+     * Given the name of a TLS/SSL cipher suite, return an int representing it effective stream
+     * cipher key strength. i.e. How much entropy material is in the key material being fed into the
+     * encryption routines.
+     *
+     * <p>
+     * This is based on the information on effective key lengths in RFC 2246 - The TLS Protocol
+     * Version 1.0, Appendix C. CipherSuite definitions:
+     *
+     * <pre>
+     *                         Effective
+     *     Cipher       Type    Key Bits
+     *
+     *     NULL       * Stream     0
+     *     IDEA_CBC     Block    128
+     *     RC2_CBC_40 * Block     40
+     *     RC4_40     * Stream    40
+     *     RC4_128      Stream   128
+     *     DES40_CBC  * Block     40
+     *     DES_CBC      Block     56
+     *     3DES_EDE_CBC Block    168
+     * </pre>
+     *
+     * @param cipherSuite String name of the TLS cipher suite.
+     * @return int indicating the effective key entropy bit-length.
+     */
+    public static int deduceKeyLength(String cipherSuite)
+    {
+        // Roughly ordered from most common to least common.
+        if (cipherSuite == null)
+            return 0;
+        else if (cipherSuite.contains("WITH_AES_256_"))
+            return 256;
+        else if (cipherSuite.contains("WITH_RC4_128_"))
+            return 128;
+        else if (cipherSuite.contains("WITH_AES_128_"))
+            return 128;
+        else if (cipherSuite.contains("WITH_RC4_40_"))
+            return 40;
+        else if (cipherSuite.contains("WITH_3DES_EDE_CBC_"))
+            return 168;
+        else if (cipherSuite.contains("WITH_IDEA_CBC_"))
+            return 128;
+        else if (cipherSuite.contains("WITH_RC2_CBC_40_"))
+            return 40;
+        else if (cipherSuite.contains("WITH_DES40_CBC_"))
+            return 40;
+        else if (cipherSuite.contains("WITH_DES_CBC_"))
+            return 56;
+        else
+            return 0;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x(%s,%s)",
+                getClass().getSimpleName(),
+                hashCode(),
+                _keyStorePath,
+                _trustStorePath);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ssl/package-info.java b/lib/jetty/org/eclipse/jetty/util/ssl/package-info.java
new file mode 100644 (file)
index 0000000..26ffa87
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common SSL Utility Classes
+ */
+package org.eclipse.jetty.util.ssl;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/statistic/CounterStatistic.java b/lib/jetty/org/eclipse/jetty/util/statistic/CounterStatistic.java
new file mode 100644 (file)
index 0000000..027dfe3
--- /dev/null
@@ -0,0 +1,118 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.statistic;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.Atomics;
+
+
+/* ------------------------------------------------------------ */
+/** Statistics on a counter value.
+ * <p>
+ * Keep total, current and maximum values of a counter that
+ * can be incremented and decremented. The total refers only
+ * to increments.
+ *
+ */
+public class CounterStatistic
+{
+    protected final AtomicLong _max = new AtomicLong();
+    protected final AtomicLong _curr = new AtomicLong();
+    protected final AtomicLong _total = new AtomicLong();
+
+    /* ------------------------------------------------------------ */
+    public void reset()
+    {
+        reset(0);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void reset(final long value)
+    {
+        _max.set(value);
+        _curr.set(value);
+        _total.set(0); // total always set to 0 to properly calculate cumulative total
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param delta the amount to add to the count
+     */
+    public long add(final long delta)
+    {
+        long value=_curr.addAndGet(delta);
+        if (delta > 0)
+        {
+            _total.addAndGet(delta);
+            Atomics.updateMax(_max,value);
+        }
+        return value;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public long increment()
+    {
+        return add(1);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public long decrement()
+    {
+        return add(-1);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return max value
+     */
+    public long getMax()
+    {
+        return _max.get();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return current value
+     */
+    public long getCurrent()
+    {
+        return _curr.get();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return total value
+     */
+    public long getTotal()
+    {
+        return _total.get();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{c=%d,m=%d,t=%d}",this.getClass().getSimpleName(),hashCode(),_curr.get(),_max.get(),_total.get());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/statistic/SampleStatistic.java b/lib/jetty/org/eclipse/jetty/util/statistic/SampleStatistic.java
new file mode 100644 (file)
index 0000000..eae7d47
--- /dev/null
@@ -0,0 +1,116 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.statistic;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.Atomics;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * SampledStatistics
+ * <p>
+ * Provides max, total, mean, count, variance, and standard
+ * deviation of continuous sequence of samples.
+ * <p>
+ * Calculates estimates of mean, variance, and standard deviation
+ * characteristics of a sample using a non synchronized
+ * approximation of the on-line algorithm presented
+ * in Donald Knuth's Art of Computer Programming, Volume 2,
+ * Seminumerical Algorithms, 3rd edition, page 232,
+ * Boston: Addison-Wesley. that cites a 1962 paper by B.P. Welford
+ * that can be found by following the link http://www.jstor.org/pss/1266577
+ * <p>
+ * This algorithm is also described in Wikipedia at
+ * http://en.wikipedia.org/w/index.php?title=Algorithms_for_calculating_variance&section=4#On-line_algorithm
+ */
+public class SampleStatistic
+{
+    protected final AtomicLong _max = new AtomicLong();
+    protected final AtomicLong _total = new AtomicLong();
+    protected final AtomicLong _count = new AtomicLong();
+    protected final AtomicLong _totalVariance100 = new AtomicLong();
+
+    public void reset()
+    {
+        _max.set(0);
+        _total.set(0);
+        _count.set(0);
+        _totalVariance100.set(0);
+    }
+
+    public void set(final long sample)
+    {
+        long total = _total.addAndGet(sample);
+        long count = _count.incrementAndGet();
+
+        if (count>1)
+        {
+            long mean10 = total*10/count;
+            long delta10 = sample*10 - mean10;
+            _totalVariance100.addAndGet(delta10*delta10);
+        }
+
+        Atomics.updateMax(_max, sample);
+    }
+
+    /**
+     * @return the max value
+     */
+    public long getMax()
+    {
+        return _max.get();
+    }
+
+    public long getTotal()
+    {
+        return _total.get();
+    }
+
+    public long getCount()
+    {
+        return _count.get();
+    }
+
+    public double getMean()
+    {
+        return (double)_total.get()/_count.get();
+    }
+
+    public double getVariance()
+    {
+        final long variance100 = _totalVariance100.get();
+        final long count = _count.get();
+
+        return count>1?((double)variance100)/100.0/(count-1):0.0;
+    }
+
+    public double getStdDev()
+    {
+        return Math.sqrt(getVariance());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{c=%d,m=%d,t=%d,v100=%d}",this.getClass().getSimpleName(),hashCode(),_count.get(),_max.get(),_total.get(),_totalVariance100.get());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/statistic/package-info.java b/lib/jetty/org/eclipse/jetty/util/statistic/package-info.java
new file mode 100644 (file)
index 0000000..fac39e4
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Statistics Utility classes
+ */
+package org.eclipse.jetty.util.statistic;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/ExecutorThreadPool.java b/lib/jetty/org/eclipse/jetty/util/thread/ExecutorThreadPool.java
new file mode 100644 (file)
index 0000000..4f6b5fe
--- /dev/null
@@ -0,0 +1,192 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Jetty ThreadPool using java 5 ThreadPoolExecutor
+ * This class wraps a {@link ExecutorService} as a {@link ThreadPool} and
+ * {@link LifeCycle} interfaces so that it may be used by the Jetty <code>org.eclipse.jetty.server.Server</code>
+ */
+public class ExecutorThreadPool extends AbstractLifeCycle implements ThreadPool, LifeCycle
+{
+    private static final Logger LOG = Log.getLogger(ExecutorThreadPool.class);
+    private final ExecutorService _executor;
+
+    /* ------------------------------------------------------------ */
+    public ExecutorThreadPool(ExecutorService executor)
+    {
+        _executor = executor;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Wraps an {@link ThreadPoolExecutor}.
+     * Max pool size is 256, pool thread timeout after 60 seconds and
+     * an unbounded {@link LinkedBlockingQueue} is used for the job queue;
+     */
+    public ExecutorThreadPool()
+    {
+        // Using an unbounded queue makes the maxThreads parameter useless
+        // Refer to ThreadPoolExecutor javadocs for details
+        this(new ThreadPoolExecutor(256, 256, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Wraps an {@link ThreadPoolExecutor}.
+     * Max pool size is 256, pool thread timeout after 60 seconds, and core pool size is 32 when queueSize >= 0.
+     * @param queueSize can be -1 for using an unbounded {@link LinkedBlockingQueue}, 0 for using a
+     * {@link SynchronousQueue}, greater than 0 for using a {@link ArrayBlockingQueue} of the given size.
+     */
+    public ExecutorThreadPool(int queueSize)
+    {
+        this(queueSize < 0 ? new ThreadPoolExecutor(256, 256, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()) :
+                queueSize == 0 ? new ThreadPoolExecutor(32, 256, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()) :
+                        new ThreadPoolExecutor(32, 256, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize)));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Wraps an {@link ThreadPoolExecutor} using
+     * an unbounded {@link LinkedBlockingQueue} is used for the jobs queue;
+     * @param corePoolSize must be equal to maximumPoolSize
+     * @param maximumPoolSize the maximum number of threads to allow in the pool
+     * @param keepAliveTime the max time a thread can remain idle, in milliseconds
+     */
+    public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime)
+    {
+        this(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Wraps an {@link ThreadPoolExecutor} using
+     * an unbounded {@link LinkedBlockingQueue} is used for the jobs queue.
+     * @param corePoolSize must be equal to maximumPoolSize
+     * @param maximumPoolSize the maximum number of threads to allow in the pool
+     * @param keepAliveTime the max time a thread can remain idle
+     * @param unit the unit for the keepAliveTime
+     */
+    public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit)
+    {
+        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, new LinkedBlockingQueue<Runnable>());
+    }
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Wraps an {@link ThreadPoolExecutor}
+     * @param corePoolSize the number of threads to keep in the pool, even if they are idle
+     * @param maximumPoolSize the maximum number of threads to allow in the pool
+     * @param keepAliveTime the max time a thread can remain idle
+     * @param unit the unit for the keepAliveTime
+     * @param workQueue the queue to use for holding tasks before they are executed
+     */
+    public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
+    {
+        this(new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue));
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void execute(Runnable job)
+    {
+        _executor.execute(job);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean dispatch(Runnable job)
+    {
+        try
+        {
+            _executor.execute(job);
+            return true;
+        }
+        catch(RejectedExecutionException e)
+        {
+            LOG.warn(e);
+            return false;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getIdleThreads()
+    {
+        if (_executor instanceof ThreadPoolExecutor)
+        {
+            final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor;
+            return tpe.getPoolSize() - tpe.getActiveCount();
+        }
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getThreads()
+    {
+        if (_executor instanceof ThreadPoolExecutor)
+        {
+            final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor;
+            return tpe.getPoolSize();
+        }
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isLowOnThreads()
+    {
+        if (_executor instanceof ThreadPoolExecutor)
+        {
+            final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor;
+            // getActiveCount() locks the thread pool, so execute it last
+            return tpe.getPoolSize() == tpe.getMaximumPoolSize() &&
+                    tpe.getQueue().size() >= tpe.getPoolSize() - tpe.getActiveCount();
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void join() throws InterruptedException
+    {
+        _executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        _executor.shutdownNow();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/NonBlockingThread.java b/lib/jetty/org/eclipse/jetty/util/thread/NonBlockingThread.java
new file mode 100644 (file)
index 0000000..4fb75c7
--- /dev/null
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+/**
+ * Marker that wraps a Runnable, indicating that it is running in a thread that must not be blocked.
+ * <p />
+ * Client code can use the thread-local {@link #isNonBlockingThread()} to detect whether they are
+ * in the context of a non-blocking thread, and perform different actions if that's the case.
+ */
+public class NonBlockingThread implements Runnable
+{
+    private final static ThreadLocal<Boolean> __nonBlockingThread = new ThreadLocal<>();
+
+    /**
+     * @return whether the current thread is a thread that must not block.
+     */
+    public static boolean isNonBlockingThread()
+    {
+        return Boolean.TRUE.equals(__nonBlockingThread.get());
+    }
+
+    private final Runnable delegate;
+
+    public NonBlockingThread(Runnable delegate)
+    {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public void run()
+    {
+        try
+        {
+            __nonBlockingThread.set(Boolean.TRUE);
+            delegate.run();
+        }
+        finally
+        {
+            __nonBlockingThread.remove();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/lib/jetty/org/eclipse/jetty/util/thread/QueuedThreadPool.java
new file mode 100644 (file)
index 0000000..5cc7512
--- /dev/null
@@ -0,0 +1,666 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util.thread;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool;
+
+@ManagedObject("A thread pool with no max bound by default")
+public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPool, Dumpable
+{
+    private static final Logger LOG = Log.getLogger(QueuedThreadPool.class);
+
+    private final AtomicInteger _threadsStarted = new AtomicInteger();
+    private final AtomicInteger _threadsIdle = new AtomicInteger();
+    private final AtomicLong _lastShrink = new AtomicLong();
+    private final ConcurrentLinkedQueue<Thread> _threads = new ConcurrentLinkedQueue<>();
+    private final Object _joinLock = new Object();
+    private final BlockingQueue<Runnable> _jobs;
+    private String _name = "qtp" + hashCode();
+    private int _idleTimeout;
+    private int _maxThreads;
+    private int _minThreads;
+    private int _priority = Thread.NORM_PRIORITY;
+    private boolean _daemon = false;
+    private boolean _detailedDump = false;
+
+    public QueuedThreadPool()
+    {
+        this(200);
+    }
+
+    public QueuedThreadPool(@Name("maxThreads") int maxThreads)
+    {
+        this(maxThreads, 8);
+    }
+
+    public QueuedThreadPool(@Name("maxThreads") int maxThreads,  @Name("minThreads") int minThreads)
+    {
+        this(maxThreads, minThreads, 60000);
+    }
+
+    public QueuedThreadPool(@Name("maxThreads") int maxThreads,  @Name("minThreads") int minThreads, @Name("idleTimeout")int idleTimeout)
+    {
+        this(maxThreads, minThreads, idleTimeout, null);
+    }
+
+    public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout") int idleTimeout, @Name("queue") BlockingQueue<Runnable> queue)
+    {
+        setMinThreads(minThreads);
+        setMaxThreads(maxThreads);
+        setIdleTimeout(idleTimeout);
+        setStopTimeout(5000);
+
+        if (queue==null)
+            queue=new BlockingArrayQueue<>(_minThreads, _minThreads);
+        _jobs=queue;
+
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        _threadsStarted.set(0);
+
+        startThreads(_minThreads);
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+
+        long timeout = getStopTimeout();
+        BlockingQueue<Runnable> jobs = getQueue();
+
+        // If no stop timeout, clear job queue
+        if (timeout <= 0)
+            jobs.clear();
+
+        // Fill job Q with noop jobs to wakeup idle
+        Runnable noop = new Runnable()
+        {
+            @Override
+            public void run()
+            {
+            }
+        };
+        for (int i = _threadsStarted.get(); i-- > 0; )
+            jobs.offer(noop);
+
+        // try to jobs complete naturally for half our stop time
+        long stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2;
+        for (Thread thread : _threads)
+        {
+            long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime());
+            if (canwait > 0)
+                thread.join(canwait);
+        }
+
+        // If we still have threads running, get a bit more aggressive
+
+        // interrupt remaining threads
+        if (_threadsStarted.get() > 0)
+            for (Thread thread : _threads)
+                thread.interrupt();
+
+        // wait again for the other half of our stop time
+        stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2;
+        for (Thread thread : _threads)
+        {
+            long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime());
+            if (canwait > 0)
+                thread.join(canwait);
+        }
+
+        Thread.yield();
+        int size = _threads.size();
+        if (size > 0)
+        {
+            Thread.yield();
+            
+            if (LOG.isDebugEnabled())
+            {
+                for (Thread unstopped : _threads)
+                {
+                    StringBuilder dmp = new StringBuilder();
+                    for (StackTraceElement element : unstopped.getStackTrace())
+                    {
+                        dmp.append(StringUtil.__LINE_SEPARATOR).append("\tat ").append(element);
+                    }
+                    LOG.warn("Couldn't stop {}{}", unstopped, dmp.toString());
+                }
+            }
+            else
+            {
+                for (Thread unstopped : _threads)
+                    LOG.warn("{} Couldn't stop {}",this,unstopped);
+            }
+        }
+
+        synchronized (_joinLock)
+        {
+            _joinLock.notifyAll();
+        }
+    }
+
+    /**
+     * Delegated to the named or anonymous Pool.
+     */
+    public void setDaemon(boolean daemon)
+    {
+        _daemon = daemon;
+    }
+
+    /**
+     * Set the maximum thread idle time.
+     * Threads that are idle for longer than this period may be
+     * stopped.
+     * Delegated to the named or anonymous Pool.
+     *
+     * @param idleTimeout Max idle time in ms.
+     * @see #getIdleTimeout
+     */
+    public void setIdleTimeout(int idleTimeout)
+    {
+        _idleTimeout = idleTimeout;
+    }
+
+    /**
+     * Set the maximum number of threads.
+     * Delegated to the named or anonymous Pool.
+     *
+     * @param maxThreads maximum number of threads.
+     * @see #getMaxThreads
+     */
+    @Override
+    public void setMaxThreads(int maxThreads)
+    {
+        _maxThreads = maxThreads;
+        if (_minThreads > _maxThreads)
+            _minThreads = _maxThreads;
+    }
+
+    /**
+     * Set the minimum number of threads.
+     * Delegated to the named or anonymous Pool.
+     *
+     * @param minThreads minimum number of threads
+     * @see #getMinThreads
+     */
+    @Override
+    public void setMinThreads(int minThreads)
+    {
+        _minThreads = minThreads;
+
+        if (_minThreads > _maxThreads)
+            _maxThreads = _minThreads;
+
+        int threads = _threadsStarted.get();
+        if (isStarted() && threads < _minThreads)
+            startThreads(_minThreads - threads);
+    }
+
+    /**
+     * @param name Name of this thread pool to use when naming threads.
+     */
+    public void setName(String name)
+    {
+        if (isRunning())
+            throw new IllegalStateException("started");
+        _name = name;
+    }
+
+    /**
+     * Set the priority of the pool threads.
+     *
+     * @param priority the new thread priority.
+     */
+    public void setThreadsPriority(int priority)
+    {
+        _priority = priority;
+    }
+
+    /**
+     * Get the maximum thread idle time.
+     * Delegated to the named or anonymous Pool.
+     *
+     * @return Max idle time in ms.
+     * @see #setIdleTimeout
+     */
+    @ManagedAttribute("maximum time a thread may be idle in ms")
+    public int getIdleTimeout()
+    {
+        return _idleTimeout;
+    }
+
+    /**
+     * Set the maximum number of threads.
+     * Delegated to the named or anonymous Pool.
+     *
+     * @return maximum number of threads.
+     * @see #setMaxThreads
+     */
+    @Override
+    @ManagedAttribute("maximum number of threads in the pool")
+    public int getMaxThreads()
+    {
+        return _maxThreads;
+    }
+
+    /**
+     * Get the minimum number of threads.
+     * Delegated to the named or anonymous Pool.
+     *
+     * @return minimum number of threads.
+     * @see #setMinThreads
+     */
+    @Override
+    @ManagedAttribute("minimum number of threads in the pool")
+    public int getMinThreads()
+    {
+        return _minThreads;
+    }
+
+    /**
+     * @return The name of the this thread pool
+     */
+    @ManagedAttribute("name of the thread pool")
+    public String getName()
+    {
+        return _name;
+    }
+
+    /**
+     * Get the priority of the pool threads.
+     *
+     * @return the priority of the pool threads.
+     */
+    @ManagedAttribute("priority of threads in the pool")
+    public int getThreadsPriority()
+    {
+        return _priority;
+    }
+    
+    /**
+     * Get the size of the job queue.
+     * 
+     * @return Number of jobs queued waiting for a thread
+     */
+    @ManagedAttribute("Size of the job queue")
+    public int getQueueSize()
+    {
+        return _jobs.size();
+    }
+
+    /**
+     * Delegated to the named or anonymous Pool.
+     */
+    @ManagedAttribute("thead pool using a daemon thread")
+    public boolean isDaemon()
+    {
+        return _daemon;
+    }
+
+    public boolean isDetailedDump()
+    {
+        return _detailedDump;
+    }
+
+    public void setDetailedDump(boolean detailedDump)
+    {
+        _detailedDump = detailedDump;
+    }
+    
+    @Override
+    public void execute(Runnable job)
+    {
+        if (!isRunning() || !_jobs.offer(job))
+        {
+            LOG.warn("{} rejected {}", this, job);
+            throw new RejectedExecutionException(job.toString());
+        }
+    }
+
+    /**
+     * Blocks until the thread pool is {@link LifeCycle#stop stopped}.
+     */
+    @Override
+    public void join() throws InterruptedException
+    {
+        synchronized (_joinLock)
+        {
+            while (isRunning())
+                _joinLock.wait();
+        }
+
+        while (isStopping())
+            Thread.sleep(1);
+    }
+
+    /**
+     * @return The total number of threads currently in the pool
+     */
+    @Override
+    @ManagedAttribute("total number of threads currently in the pool")
+    public int getThreads()
+    {
+        return _threadsStarted.get();
+    }
+
+    /**
+     * @return The number of idle threads in the pool
+     */
+    @Override
+    @ManagedAttribute("total number of idle threads in the pool")
+    public int getIdleThreads()
+    {
+        return _threadsIdle.get();
+    }
+
+    /**
+     * @return True if the pool is at maxThreads and there are not more idle threads than queued jobs
+     */
+    @Override
+    @ManagedAttribute("True if the pools is at maxThreads and there are not idle threads than queued jobs")
+    public boolean isLowOnThreads()
+    {
+        return _threadsStarted.get() == _maxThreads && _jobs.size() >= _threadsIdle.get();
+    }
+
+    private boolean startThreads(int threadsToStart)
+    {
+        while (threadsToStart > 0)
+        {
+            int threads = _threadsStarted.get();
+            if (threads >= _maxThreads)
+                return false;
+
+            if (!_threadsStarted.compareAndSet(threads, threads + 1))
+                continue;
+
+            boolean started = false;
+            try
+            {
+                Thread thread = newThread(_runnable);
+                thread.setDaemon(isDaemon());
+                thread.setPriority(getThreadsPriority());
+                thread.setName(_name + "-" + thread.getId());
+                _threads.add(thread);
+
+                thread.start();
+                started = true;
+            }
+            finally
+            {
+                if (!started)
+                    _threadsStarted.decrementAndGet();
+            }
+            if (started)
+                threadsToStart--;
+        }
+        return true;
+    }
+
+    protected Thread newThread(Runnable runnable)
+    {
+        return new Thread(runnable);
+    }
+
+
+    @Override
+    @ManagedOperation("dump thread state")
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        List<Object> dump = new ArrayList<>(getMaxThreads());
+        for (final Thread thread : _threads)
+        {
+            final StackTraceElement[] trace = thread.getStackTrace();
+            boolean inIdleJobPoll = false;
+            for (StackTraceElement t : trace)
+            {
+                if ("idleJobPoll".equals(t.getMethodName()))
+                {
+                    inIdleJobPoll = true;
+                    break;
+                }
+            }
+            final boolean idle = inIdleJobPoll;
+
+            if (isDetailedDump())
+            {
+                dump.add(new Dumpable()
+                {
+                    @Override
+                    public void dump(Appendable out, String indent) throws IOException
+                    {
+                        out.append(String.valueOf(thread.getId())).append(' ').append(thread.getName()).append(' ').append(thread.getState().toString()).append(idle ? " IDLE" : "").append('\n');
+                        if (!idle)
+                            ContainerLifeCycle.dump(out, indent, Arrays.asList(trace));
+                    }
+
+                    @Override
+                    public String dump()
+                    {
+                        return null;
+                    }
+                });
+            }
+            else
+            {
+                dump.add(thread.getId() + " " + thread.getName() + " " + thread.getState() + " @ " + (trace.length > 0 ? trace[0] : "???") + (idle ? " IDLE" : ""));
+            }
+        }
+
+        ContainerLifeCycle.dumpObject(out, this);
+        ContainerLifeCycle.dump(out, indent, dump);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s{%s,%d<=%d<=%d,i=%d,q=%d}", _name, getState(), getMinThreads(), getThreads(), getMaxThreads(), getIdleThreads(), (_jobs == null ? -1 : _jobs.size()));
+    }
+
+    private Runnable idleJobPoll() throws InterruptedException
+    {
+        return _jobs.poll(_idleTimeout, TimeUnit.MILLISECONDS);
+    }
+
+    private Runnable _runnable = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            boolean shrink = false;
+            try
+            {
+                Runnable job = _jobs.poll();
+
+                if (job != null && _threadsIdle.get() == 0)
+                {
+                    startThreads(1);
+                }
+
+                loop: while (isRunning())
+                {
+                    // Job loop
+                    while (job != null && isRunning())
+                    {
+                        runJob(job);
+                        if (Thread.interrupted())
+                            break loop;
+                        job = _jobs.poll();
+                    }
+
+                    // Idle loop
+                    try
+                    {
+                        _threadsIdle.incrementAndGet();
+
+                        while (isRunning() && job == null)
+                        {
+                            if (_idleTimeout <= 0)
+                                job = _jobs.take();
+                            else
+                            {
+                                // maybe we should shrink?
+                                final int size = _threadsStarted.get();
+                                if (size > _minThreads)
+                                {
+                                    long last = _lastShrink.get();
+                                    long now = System.nanoTime();
+                                    if (last == 0 || (now - last) > TimeUnit.MILLISECONDS.toNanos(_idleTimeout))
+                                    {
+                                        shrink = _lastShrink.compareAndSet(last, now) &&
+                                                _threadsStarted.compareAndSet(size, size - 1);
+                                        if (shrink)
+                                        {
+                                            return;
+                                        }
+                                    }
+                                }
+                                job = idleJobPoll();
+                            }
+                        }
+                    }
+                    finally
+                    {
+                        if (_threadsIdle.decrementAndGet() == 0)
+                        {
+                            startThreads(1);
+                        }
+                    }
+                }
+            }
+            catch (InterruptedException e)
+            {
+                LOG.ignore(e);
+            }
+            catch (Throwable e)
+            {
+                LOG.warn(e);
+            }
+            finally
+            {
+                if (!shrink)
+                    _threadsStarted.decrementAndGet();
+                _threads.remove(Thread.currentThread());
+            }
+        }
+    };
+
+    /**
+     * <p>Runs the given job in the {@link Thread#currentThread() current thread}.</p>
+     * <p>Subclasses may override to perform pre/post actions before/after the job is run.</p>
+     *
+     * @param job the job to run
+     */
+    protected void runJob(Runnable job)
+    {
+        job.run();
+    }
+
+    /**
+     * @return the job queue
+     */
+    protected BlockingQueue<Runnable> getQueue()
+    {
+        return _jobs;
+    }
+
+    /**
+     * @param queue the job queue
+     */
+    public void setQueue(BlockingQueue<Runnable> queue)
+    {
+        throw new UnsupportedOperationException("Use constructor injection");
+    }
+
+    /**
+     * @param id The thread ID to interrupt.
+     * @return true if the thread was found and interrupted.
+     */
+    @ManagedOperation("interrupt a pool thread")
+    public boolean interruptThread(@Name("id") long id)
+    {
+        for (Thread thread : _threads)
+        {
+            if (thread.getId() == id)
+            {
+                thread.interrupt();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param id The thread ID to interrupt.
+     * @return true if the thread was found and interrupted.
+     */
+    @ManagedOperation("dump a pool thread stack")
+    public String dumpThread(@Name("id") long id)
+    {
+        for (Thread thread : _threads)
+        {
+            if (thread.getId() == id)
+            {
+                StringBuilder buf = new StringBuilder();
+                buf.append(thread.getId()).append(" ").append(thread.getName()).append(" ").append(thread.getState()).append(":\n");
+                for (StackTraceElement element : thread.getStackTrace())
+                    buf.append("  at ").append(element.toString()).append('\n');
+                return buf.toString();
+            }
+        }
+        return null;
+    }
+    
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java b/lib/jetty/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java
new file mode 100644 (file)
index 0000000..5f8d62a
--- /dev/null
@@ -0,0 +1,100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+
+/**
+ * Implementation of {@link Scheduler} based on JDK's {@link ScheduledThreadPoolExecutor}.
+ * <p />
+ * While use of {@link ScheduledThreadPoolExecutor} creates futures that will not be used,
+ * it has the advantage of allowing to set a property to remove cancelled tasks from its
+ * queue even if the task did not fire, which provides a huge benefit in the performance
+ * of garbage collection in young generation.
+ */
+public class ScheduledExecutorScheduler extends AbstractLifeCycle implements Scheduler
+{
+    private final String name;
+    private final boolean daemon;
+    private volatile ScheduledThreadPoolExecutor scheduler;
+
+    public ScheduledExecutorScheduler()
+    {
+        this(null, false);
+    }
+
+    public ScheduledExecutorScheduler(String name, boolean daemon)
+    {
+        this.name = name == null ? "Scheduler-" + hashCode() : name;
+        this.daemon = daemon;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        scheduler = new ScheduledThreadPoolExecutor(1, new ThreadFactory()
+        {
+            @Override
+            public Thread newThread(Runnable r)
+            {
+                Thread thread = new Thread(r, name);
+                thread.setDaemon(daemon);
+                return thread;
+            }
+        });
+        scheduler.setRemoveOnCancelPolicy(true);
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        scheduler.shutdownNow();
+        super.doStop();
+        scheduler = null;
+    }
+
+    @Override
+    public Task schedule(Runnable task, long delay, TimeUnit unit)
+    {
+        ScheduledFuture<?> result = scheduler.schedule(task, delay, unit);
+        return new ScheduledFutureTask(result);
+    }
+
+    private class ScheduledFutureTask implements Task
+    {
+        private final ScheduledFuture<?> scheduledFuture;
+
+        public ScheduledFutureTask(ScheduledFuture<?> scheduledFuture)
+        {
+            this.scheduledFuture = scheduledFuture;
+        }
+
+        @Override
+        public boolean cancel()
+        {
+            return scheduledFuture.cancel(false);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/Scheduler.java b/lib/jetty/org/eclipse/jetty/util/thread/Scheduler.java
new file mode 100644 (file)
index 0000000..f519184
--- /dev/null
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+public interface Scheduler extends LifeCycle
+{
+    interface Task
+    {
+        boolean cancel();
+    }
+
+    Task schedule(Runnable task, long delay, TimeUnit units);
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/ShutdownThread.java b/lib/jetty/org/eclipse/jetty/util/thread/ShutdownThread.java
new file mode 100644 (file)
index 0000000..168b444
--- /dev/null
@@ -0,0 +1,148 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.component.Destroyable;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * ShutdownThread is a shutdown hook thread implemented as 
+ * singleton that maintains a list of lifecycle instances
+ * that are registered with it and provides ability to stop
+ * these lifecycles upon shutdown of the Java Virtual Machine 
+ */
+public class ShutdownThread extends Thread
+{
+    private static final Logger LOG = Log.getLogger(ShutdownThread.class);
+    private static final ShutdownThread _thread = new ShutdownThread();
+
+    private boolean _hooked;
+    private final List<LifeCycle> _lifeCycles = new CopyOnWriteArrayList<LifeCycle>();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Default constructor for the singleton
+     * 
+     * Registers the instance as shutdown hook with the Java Runtime
+     */
+    private ShutdownThread()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    private synchronized void hook()
+    {
+        try
+        {
+            if (!_hooked)
+                Runtime.getRuntime().addShutdownHook(this);
+            _hooked=true;
+        }
+        catch(Exception e)
+        {
+            LOG.ignore(e);
+            LOG.info("shutdown already commenced");
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    private synchronized void unhook()
+    {
+        try
+        {
+            _hooked=false;
+            Runtime.getRuntime().removeShutdownHook(this);
+        }
+        catch(Exception e)
+        {
+            LOG.ignore(e);
+            LOG.debug("shutdown already commenced");
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the instance of the singleton
+     * 
+     * @return the singleton instance of the {@link ShutdownThread}
+     */
+    public static ShutdownThread getInstance()
+    {
+        return _thread;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static synchronized void register(LifeCycle... lifeCycles)
+    {
+        _thread._lifeCycles.addAll(Arrays.asList(lifeCycles));
+        if (_thread._lifeCycles.size()>0)
+            _thread.hook();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static synchronized void register(int index, LifeCycle... lifeCycles)
+    {
+        _thread._lifeCycles.addAll(index,Arrays.asList(lifeCycles));
+        if (_thread._lifeCycles.size()>0)
+            _thread.hook();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static synchronized void deregister(LifeCycle lifeCycle)
+    {
+        _thread._lifeCycles.remove(lifeCycle);
+        if (_thread._lifeCycles.size()==0)
+            _thread.unhook();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void run()
+    {
+        for (LifeCycle lifeCycle : _thread._lifeCycles)
+        {
+            try
+            {
+                if (lifeCycle.isStarted())
+                {
+                    lifeCycle.stop();
+                    LOG.debug("Stopped {}",lifeCycle);
+                }
+                
+                if (lifeCycle instanceof Destroyable)
+                {
+                    ((Destroyable)lifeCycle).destroy();
+                    LOG.debug("Destroyed {}",lifeCycle);
+                }
+            }
+            catch (Exception ex)
+            {
+                LOG.debug(ex);
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/ThreadPool.java b/lib/jetty/org/eclipse/jetty/util/thread/ThreadPool.java
new file mode 100644 (file)
index 0000000..c2abcfc
--- /dev/null
@@ -0,0 +1,74 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* ------------------------------------------------------------ */
+/** ThreadPool.
+ * 
+ * A specialization of Executor interface that provides reporting methods (eg {@link #getThreads()})
+ * and the option of configuration methods (e.g. @link {@link SizedThreadPool#setMaxThreads(int)}). 
+ *
+ */
+@ManagedObject("Pool of Threads")
+public interface ThreadPool extends Executor
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Blocks until the thread pool is {@link LifeCycle#stop stopped}.
+     */
+    public void join() throws InterruptedException;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The total number of threads currently in the pool
+     */
+    @ManagedAttribute("number of threads in pool")
+    public int getThreads();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The number of idle threads in the pool
+     */
+    @ManagedAttribute("number of idle threads in pool")
+    public int getIdleThreads();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if the pool is low on threads
+     */
+    @ManagedAttribute("indicates the pool is low on available threads")
+    public boolean isLowOnThreads();
+    
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public interface SizedThreadPool extends ThreadPool
+    {
+        public int getMinThreads();
+        public int getMaxThreads();
+        public void setMinThreads(int threads);
+        public void setMaxThreads(int threads);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/TimerScheduler.java b/lib/jetty/org/eclipse/jetty/util/thread/TimerScheduler.java
new file mode 100644 (file)
index 0000000..c07390a
--- /dev/null
@@ -0,0 +1,129 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** A scheduler based on the the JVM Timer class
+ */
+public class TimerScheduler extends AbstractLifeCycle implements Scheduler, Runnable
+{
+    private static final Logger LOG = Log.getLogger(TimerScheduler.class);
+
+    /*
+     * This class uses the Timer class rather than an ScheduledExecutionService because
+     * it uses the same algorithm internally and the signature is cheaper to use as there are no
+     * Futures involved (which we do not need).
+     * However, Timer is still locking and a concurrent queue would be better.
+     */
+
+    private final String _name;
+    private final boolean _daemon;
+    private Timer _timer;
+
+    public TimerScheduler()
+    {
+        this(null, false);
+    }
+
+    public TimerScheduler(String name, boolean daemon)
+    {
+        _name = name;
+        _daemon = daemon;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        _timer = _name == null ? new Timer() : new Timer(_name, _daemon);
+        run();
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        _timer.cancel();
+        super.doStop();
+        _timer = null;
+    }
+
+    @Override
+    public Task schedule(final Runnable task, final long delay, final TimeUnit units)
+    {
+        Timer timer = _timer;
+        if (timer == null)
+            throw new RejectedExecutionException("STOPPED: " + this);
+        SimpleTask t = new SimpleTask(task);
+        timer.schedule(t, units.toMillis(delay));
+        return t;
+    }
+
+    @Override
+    public void run()
+    {
+        Timer timer = _timer;
+        if (timer != null)
+        {
+            timer.purge();
+            schedule(this, 1, TimeUnit.SECONDS);
+        }
+    }
+
+    private static class SimpleTask extends TimerTask implements Task
+    {
+        private final Runnable _task;
+
+        private SimpleTask(Runnable runnable)
+        {
+            _task = runnable;
+        }
+
+        @Override
+        public void run()
+        {
+            try
+            {
+                _task.run();
+            }
+            catch (Throwable x)
+            {
+                LOG.debug("Exception while executing task " + _task, x);
+            }
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s.%s@%x",
+                    TimerScheduler.class.getSimpleName(),
+                    SimpleTask.class.getSimpleName(),
+                    hashCode());
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/package-info.java b/lib/jetty/org/eclipse/jetty/util/thread/package-info.java
new file mode 100644 (file)
index 0000000..2637e49
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common ThreadPool Utilities
+ */
+package org.eclipse.jetty.util.thread;
+