[tomcat7] 01/01: Imported Upstream version 7.0.45

Gianfranco Costamagna locutusofborg-guest at alioth.debian.org
Fri Oct 11 12:51:16 UTC 2013


This is an automated email from the git hooks/post-receive script.

locutusofborg-guest pushed a commit to annotated tag upstream/7.0.45
in repository tomcat7.

commit 1326f29a17cf5f0f5feca28f97f6488051506afb
Author: Gianfranco Costamagna <costamagnagianfranco at yahoo.it>
Date:   Fri Oct 11 13:48:33 2013 +0200

    Imported Upstream version 7.0.45
---
 BUILDING.txt                                       |   32 +-
 KEYS                                               |   59 +
 bin/catalina.sh                                    |   79 +-
 bin/daemon.sh                                      |   19 +-
 build.properties.default                           |    9 +-
 build.xml                                          |  173 ++-
 conf/catalina.policy                               |    3 +-
 conf/catalina.properties                           |    8 +-
 java/javax/annotation/Generated.java               |   12 +-
 .../{Resources.java => ManagedBean.java}           |   14 +-
 java/javax/annotation/PostConstruct.java           |    6 +-
 java/javax/annotation/PreDestroy.java              |    6 +-
 java/javax/annotation/Resource.java                |   10 +-
 java/javax/annotation/Resources.java               |    6 +-
 java/javax/annotation/security/DeclareRoles.java   |    6 +-
 java/javax/annotation/security/DenyAll.java        |    8 +-
 java/javax/annotation/security/PermitAll.java      |    6 +-
 java/javax/annotation/security/RolesAllowed.java   |    6 +-
 java/javax/annotation/security/RunAs.java          |    6 +-
 .../DataSourceDefinition.java}                     |   33 +-
 .../DataSourceDefinitions.java}                    |   16 +-
 java/javax/el/ArrayELResolver.java                 |   31 +-
 java/javax/el/BeanELResolver.java                  |    8 -
 java/javax/el/ELContext.java                       |    7 +
 java/javax/el/ListELResolver.java                  |   28 +-
 java/javax/el/MapELResolver.java                   |    3 +-
 java/javax/el/ResourceBundleELResolver.java        |    4 +-
 java/javax/servlet/ServletContext.java             |    4 +-
 java/javax/servlet/http/HttpSession.java           |   28 +-
 .../ClientEndpoint.java}                           |   23 +-
 java/javax/websocket/ClientEndpointConfig.java     |  138 ++
 java/javax/websocket/CloseReason.java              |  122 ++
 java/javax/websocket/ContainerProvider.java        |   64 +
 java/javax/websocket/DecodeException.java          |   56 +
 .../websocket/Decoder.java}                        |   43 +-
 .../websocket/DefaultClientEndpointConfig.java     |   80 ++
 .../DeploymentException.java}                      |   25 +-
 .../EncodeException.java}                          |   31 +-
 .../websocket/Encoder.java}                        |   39 +-
 java/javax/websocket/Endpoint.java                 |   47 +
 .../EndpointConfig.java}                           |   22 +-
 .../websocket/Extension.java}                      |   18 +-
 .../HandshakeResponse.java}                        |   25 +-
 .../websocket/MessageHandler.java}                 |   44 +-
 .../PreDestroy.java => websocket/OnClose.java}     |   16 +-
 .../PreDestroy.java => websocket/OnError.java}     |   16 +-
 .../DenyAll.java => websocket/OnMessage.java}      |   17 +-
 .../PreDestroy.java => websocket/OnOpen.java}      |   16 +-
 .../PongMessage.java}                              |   29 +-
 java/javax/websocket/RemoteEndpoint.java           |  173 +++
 .../PreDestroy.java => websocket/SendHandler.java} |   21 +-
 .../SendResult.java}                               |   32 +-
 java/javax/websocket/Session.java                  |  129 ++
 .../SessionException.java}                         |   28 +-
 java/javax/websocket/WebSocketContainer.java       |  118 ++
 .../server/DefaultServerEndpointConfig.java        |   95 ++
 java/javax/websocket/server/HandshakeRequest.java  |   51 +
 .../server/PathParam.java}                         |   22 +-
 .../websocket/server/ServerApplicationConfig.java  |   51 +
 .../server/ServerContainer.java}                   |   27 +-
 .../server/ServerEndpoint.java}                    |   31 +-
 .../websocket/server/ServerEndpointConfig.java     |  214 ++++
 java/org/apache/catalina/Context.java              |   13 +-
 .../authenticator/SpnegoAuthenticator.java         |    5 +-
 .../apache/catalina/connector/CoyoteAdapter.java   |   10 +-
 .../apache/catalina/connector/OutputBuffer.java    |    9 +-
 java/org/apache/catalina/connector/Request.java    |   45 +-
 .../apache/catalina/connector/RequestFacade.java   |    8 +-
 .../apache/catalina/core/ApplicationContext.java   |  126 +-
 .../catalina/core/ApplicationContextFacade.java    |   32 +-
 .../apache/catalina/core/AprLifecycleListener.java |    6 +-
 .../org/apache/catalina/core/AsyncContextImpl.java |   41 +-
 .../catalina/core/DefaultInstanceManager.java      |    7 +-
 java/org/apache/catalina/core/StandardContext.java |    4 +-
 .../catalina/filters/CsrfPreventionFilter.java     |    1 +
 .../catalina/ha/context/ReplicatedContext.java     |   35 +-
 .../catalina/ha/deploy/FileMessageFactory.java     |    4 +
 .../apache/catalina/ha/session/BackupManager.java  |   42 +-
 .../apache/catalina/ha/session/DeltaManager.java   |    4 +-
 .../apache/catalina/ha/session/DeltaSession.java   |   67 +-
 .../catalina/ha/session/SessionIDMessage.java      |   11 +-
 .../catalina/ha/session/mbeans-descriptors.xml     |    5 +
 .../apache/catalina/ha/tcp/SimpleTcpCluster.java   |    3 +-
 .../catalina/manager/HTMLManagerServlet.java       |   10 +-
 .../apache/catalina/manager/ManagerServlet.java    |   13 +-
 .../manager/host/HTMLHostManagerServlet.java       |    8 +-
 .../catalina/manager/host/HostManagerServlet.java  |   10 +-
 java/org/apache/catalina/mbeans/MBeanFactory.java  |    4 +-
 java/org/apache/catalina/realm/JAASRealm.java      |   51 +-
 java/org/apache/catalina/realm/JDBCRealm.java      |   48 +-
 java/org/apache/catalina/realm/JNDIRealm.java      |   54 +-
 .../apache/catalina/realm/LocalStrings.properties  |    3 +-
 java/org/apache/catalina/session/FileStore.java    |   36 +-
 java/org/apache/catalina/session/JDBCStore.java    |    5 +-
 .../apache/catalina/session/StandardSession.java   |   26 +-
 .../apache/catalina/session/mbeans-descriptors.xml |   14 +-
 .../org/apache/catalina/startup/ContextConfig.java |  102 +-
 .../org/apache/catalina/startup/FailedContext.java |    7 +
 java/org/apache/catalina/startup/HostConfig.java   |    9 +-
 .../catalina/startup/LocalStrings.properties       |    6 +-
 .../catalina/startup/LocalStrings_es.properties    |    6 +-
 .../catalina/startup/LocalStrings_fr.properties    |    6 +-
 .../catalina/startup/LocalStrings_ja.properties    |    6 +-
 java/org/apache/catalina/startup/Tomcat.java       |   89 +-
 .../apache/catalina/startup/WebAnnotationSet.java  |    5 +-
 .../apache/catalina/tribes/MembershipService.java  |    3 +-
 .../apache/catalina/tribes/group/GroupChannel.java |    9 +-
 .../interceptors/MessageDispatch15Interceptor.java |    3 +-
 .../group/interceptors/TcpFailureDetector.java     |   62 +-
 .../interceptors/TwoPhaseCommitInterceptor.java    |    7 +-
 .../catalina/tribes/membership/McastService.java   |    4 +-
 .../tribes/tipis/AbstractReplicatedMap.java        |   31 +-
 .../catalina/tribes/tipis/LazyReplicatedMap.java   |   70 +-
 .../catalina/tribes/tipis/ReplicatedMap.java       |   18 +-
 .../catalina/tribes/transport/PooledSender.java    |    7 +-
 .../catalina/tribes/util/TcclThreadFactory.java    |    6 +-
 java/org/apache/catalina/util/ParameterMap.java    |   11 +-
 .../apache/catalina/valves/ErrorReportValve.java   |   30 +-
 java/org/apache/catalina/websocket/Constants.java  |    3 +
 .../apache/catalina/websocket/MessageInbound.java  |    3 +
 .../apache/catalina/websocket/StreamInbound.java   |    3 +
 .../catalina/websocket/WebSocketServlet.java       |    3 +
 java/org/apache/catalina/websocket/WsFrame.java    |    3 +
 .../websocket/WsHttpServletRequestWrapper.java     |    3 +
 .../apache/catalina/websocket/WsInputStream.java   |    3 +
 java/org/apache/catalina/websocket/WsOutbound.java |  396 +++---
 java/org/apache/coyote/AbstractProcessor.java      |    7 +-
 java/org/apache/coyote/AbstractProtocol.java       |  119 +-
 java/org/apache/coyote/ActionCode.java             |    7 +-
 java/org/apache/coyote/AsyncStateMachine.java      |    4 +
 java/org/apache/coyote/Processor.java              |   15 +-
 java/org/apache/coyote/Request.java                |    3 +-
 .../apache/coyote/ajp/AbstractAjpProcessor.java    |  121 +-
 .../org/apache/coyote/ajp/AbstractAjpProtocol.java |   16 +-
 java/org/apache/coyote/ajp/AjpAprProcessor.java    |   12 +-
 java/org/apache/coyote/ajp/AjpAprProtocol.java     |    2 +-
 java/org/apache/coyote/ajp/AjpNioProcessor.java    |   33 +-
 java/org/apache/coyote/ajp/AjpProcessor.java       |   13 +-
 .../coyote/http11/AbstractHttp11Processor.java     |   94 +-
 .../coyote/http11/AbstractHttp11Protocol.java      |   10 +
 .../apache/coyote/http11/AbstractOutputBuffer.java |    3 +-
 .../apache/coyote/http11/Http11AprProcessor.java   |   10 +-
 .../apache/coyote/http11/Http11AprProtocol.java    |   53 +-
 .../apache/coyote/http11/Http11NioProcessor.java   |    8 +-
 .../apache/coyote/http11/Http11NioProtocol.java    |   26 +-
 java/org/apache/coyote/http11/Http11Processor.java |    8 +-
 java/org/apache/coyote/http11/Http11Protocol.java  |   26 +-
 .../coyote/http11/InternalAprOutputBuffer.java     |    2 +-
 .../coyote/http11/InternalNioOutputBuffer.java     |    4 +-
 .../coyote/http11/filters/ChunkedInputFilter.java  |   30 +-
 .../coyote/http11/upgrade/AbstractProcessor.java   |  187 +++
 .../http11/upgrade/AbstractServletInputStream.java |  198 +++
 .../upgrade/AbstractServletOutputStream.java       |  177 +++
 .../apache/coyote/http11/upgrade/AprProcessor.java |   42 +
 .../http11/upgrade/AprServletInputStream.java      |  114 ++
 .../http11/upgrade/AprServletOutputStream.java     |  162 +++
 .../apache/coyote/http11/upgrade/BioProcessor.java |   42 +
 .../http11/upgrade/BioServletInputStream.java      |   50 +
 .../http11/upgrade/BioServletOutputStream.java}    |   39 +-
 .../coyote/http11/upgrade/LocalStrings.properties  |   17 +-
 .../apache/coyote/http11/upgrade/NioProcessor.java |   42 +
 .../http11/upgrade/NioServletInputStream.java      |  140 ++
 .../http11/upgrade/NioServletOutputStream.java     |  138 ++
 .../coyote/http11/upgrade/UpgradeAprProcessor.java |    4 +
 .../coyote/http11/upgrade/UpgradeBioProcessor.java |    4 +
 .../coyote/http11/upgrade/UpgradeInbound.java      |    3 +
 .../coyote/http11/upgrade/UpgradeNioProcessor.java |    4 +
 .../coyote/http11/upgrade/UpgradeOutbound.java     |    3 +
 .../coyote/http11/upgrade/UpgradeProcessor.java    |   27 +-
 .../HttpUpgradeHandler.java}                       |   36 +-
 .../http11/upgrade/servlet31/ReadListener.java     |   50 +
 .../http11/upgrade/servlet31/WebConnection.java    |   47 +
 .../http11/upgrade/servlet31/WriteListener.java    |   43 +
 java/org/apache/jasper/JspC.java                   |    2 -
 java/org/apache/jasper/compiler/Generator.java     |   64 +-
 .../apache/jasper/compiler/JspDocumentParser.java  |   27 +-
 .../apache/jasper/compiler/TagFileProcessor.java   |    2 +-
 .../apache/jasper/compiler/TagPluginManager.java   |   45 +-
 java/org/apache/juli/ClassLoaderLogManager.java    |   27 +-
 java/org/apache/juli/JdkLoggerFormatter.java       |   11 +-
 .../naming/resources/DirContextURLConnection.java  |  168 +--
 .../org/apache/naming/resources/WARDirContext.java |   47 +-
 java/org/apache/tomcat/InstanceManager.java        |    6 +-
 .../tomcat/util/bcel/classfile/Constant.java       |    4 +-
 .../tomcat/util/bcel/classfile/ConstantUtf8.java   |   54 +-
 java/org/apache/tomcat/util/bcel/package.html      |    4 +-
 java/org/apache/tomcat/util/buf/Utf8Encoder.java   |  234 ++++
 java/org/apache/tomcat/util/http/HttpMessages.java |   64 +-
 java/org/apache/tomcat/util/http/Parameters.java   |    6 +-
 .../util/http/fileupload/MultipartStream.java      |    4 +-
 .../util/http/fileupload/disk/DiskFileItem.java    |   16 +-
 .../http/fileupload/disk/DiskFileItemFactory.java  |   15 +-
 .../tomcat/util/http/fileupload/util/Streams.java  |    4 +-
 .../apache/tomcat/util/http/parser/HttpParser.java |    8 +-
 .../apache/tomcat/util/modeler/BaseModelMBean.java |    4 +-
 .../apache/tomcat/util/modeler/ManagedBean.java    |    6 +-
 .../modules/MbeansDescriptorsDOMSource.java        |    5 +-
 .../apache/tomcat/util/net/AbstractEndpoint.java   |   15 +-
 java/org/apache/tomcat/util/net/AprEndpoint.java   | 1343 +++++++++++++-------
 java/org/apache/tomcat/util/net/JIoEndpoint.java   |    7 +-
 java/org/apache/tomcat/util/net/NioEndpoint.java   |  265 ++--
 java/org/apache/tomcat/util/net/SocketStatus.java  |    2 +-
 java/org/apache/tomcat/util/net/SocketWrapper.java |   52 +
 .../tomcat/util/net/res/LocalStrings.properties    |   36 +-
 java/org/apache/tomcat/util/res/StringManager.java |   54 +-
 .../tomcat/websocket/AsyncChannelWrapper.java      |   47 +
 .../websocket/AsyncChannelWrapperNonSecure.java    |  112 ++
 .../websocket/AsyncChannelWrapperSecure.java       |  558 ++++++++
 .../BackgroundProcess.java}                        |   15 +-
 .../tomcat/websocket/BackgroundProcessManager.java |  128 ++
 java/org/apache/tomcat/websocket/Constants.java    |   67 +
 .../apache/tomcat/websocket/DecoderEntry.java}     |   34 +-
 .../tomcat/websocket/FutureToSendHandler.java      |  109 ++
 .../tomcat/websocket/LocalStrings.properties       |  101 ++
 .../tomcat/websocket/MessageHandlerResult.java}    |   33 +-
 .../websocket/MessageHandlerResultType.java}       |   22 +-
 .../websocket/ReadBufferOverflowException.java}    |   27 +-
 .../SendHandlerToCompletionHandler.java}           |   31 +-
 java/org/apache/tomcat/websocket/Util.java         |  483 +++++++
 .../tomcat/websocket/WrappedMessageHandler.java}   |   22 +-
 .../tomcat/websocket/WsContainerProvider.java}     |   25 +-
 java/org/apache/tomcat/websocket/WsFrameBase.java  |  679 ++++++++++
 .../org/apache/tomcat/websocket/WsFrameClient.java |  126 ++
 .../websocket/WsHandshakeResponse.java}            |   35 +-
 .../WsIOException.java}                            |   26 +-
 .../apache/tomcat/websocket/WsPongMessage.java}    |   32 +-
 .../tomcat/websocket/WsRemoteEndpointAsync.java    |   79 ++
 .../tomcat/websocket/WsRemoteEndpointBase.java     |   64 +
 .../tomcat/websocket/WsRemoteEndpointBasic.java    |   76 ++
 .../tomcat/websocket/WsRemoteEndpointImplBase.java |  949 ++++++++++++++
 .../websocket/WsRemoteEndpointImplClient.java      |   56 +
 java/org/apache/tomcat/websocket/WsSession.java    |  644 ++++++++++
 .../tomcat/websocket/WsWebSocketContainer.java     |  848 ++++++++++++
 .../apache/tomcat/websocket/pojo/Constants.java}   |   25 +-
 .../tomcat/websocket/pojo/LocalStrings.properties  |   39 +
 .../tomcat/websocket/pojo/PojoEndpointBase.java    |  157 +++
 .../tomcat/websocket/pojo/PojoEndpointClient.java  |   46 +
 .../tomcat/websocket/pojo/PojoEndpointServer.java  |   72 ++
 .../websocket/pojo/PojoMessageHandlerBase.java     |  104 ++
 .../pojo/PojoMessageHandlerPartialBase.java        |   79 ++
 .../pojo/PojoMessageHandlerPartialBinary.java}     |   40 +-
 .../pojo/PojoMessageHandlerPartialText.java}       |   34 +-
 .../pojo/PojoMessageHandlerWholeBase.java          |   96 ++
 .../pojo/PojoMessageHandlerWholeBinary.java        |  124 ++
 .../pojo/PojoMessageHandlerWholePong.java}         |   40 +-
 .../pojo/PojoMessageHandlerWholeText.java          |  129 ++
 .../tomcat/websocket/pojo/PojoMethodMapping.java   |  606 +++++++++
 .../pojo/PojoPathParam.java}                       |   34 +-
 .../tomcat/websocket/pojo/package-info.java}       |   24 +-
 .../websocket/server/Constants.java}               |   37 +-
 .../server/DefaultServerEndpointConfigurator.java  |   81 ++
 .../websocket/server/LocalStrings.properties       |   36 +
 .../tomcat/websocket/server/UpgradeUtil.java       |  247 ++++
 .../tomcat/websocket/server/UriTemplate.java       |  178 +++
 .../tomcat/websocket/server/WsContextListener.java |   51 +
 .../apache/tomcat/websocket/server/WsFilter.java   |   87 ++
 .../tomcat/websocket/server/WsFrameServer.java     |   65 +
 .../websocket/server/WsHandshakeRequest.java       |  143 +++
 .../websocket/server/WsHttpUpgradeHandler.java     |  243 ++++
 .../tomcat/websocket/server/WsMappingResult.java}  |   37 +-
 .../server/WsRemoteEndpointImplServer.java         |  163 +++
 java/org/apache/tomcat/websocket/server/WsSci.java |  178 +++
 .../tomcat/websocket/server/WsServerContainer.java |  452 +++++++
 .../websocket/server/WsSessionListener.java}       |   34 +-
 .../tomcat/websocket/server/WsWriteTimeout.java    |  129 ++
 .../tomcat/websocket/server/package-info.java}     |   24 +-
 modules/jdbc-pool/doc/jdbc-pool.xml                |   16 +
 .../apache/tomcat/jdbc/pool/ConnectionPool.java    |   16 +-
 .../apache/tomcat/jdbc/pool/DataSourceFactory.java |   17 +-
 .../apache/tomcat/jdbc/pool/DataSourceProxy.java   |   34 +
 .../jdbc/pool/DisposableConnectionFacade.java      |    4 +-
 .../apache/tomcat/jdbc/pool/PoolConfiguration.java |   25 +
 .../apache/tomcat/jdbc/pool/PoolProperties.java    |   36 +
 .../apache/tomcat/jdbc/pool/PooledConnection.java  |    6 +
 .../jdbc/pool/interceptor/SlowQueryReport.java     |    2 +
 .../jdbc/pool/interceptor/StatementCache.java      |    4 +
 .../tomcat/jdbc/pool/jmx/ConnectionPool.java       |   26 +
 .../apache/tomcat/jdbc/pool/mbeans-descriptors.xml |  117 ++
 .../jdbc/test/TestValidationQueryTimeout.java      |  266 ++++
 .../META-INF/default/.gitignore                    |   16 +-
 .../javax.servlet.ServletContainerInitializer      |    1 +
 .../services/javax.websocket.ContainerProvider     |    1 +
 ...socket.server.ServerEndpointConfig.Configurator |    1 +
 .../tomcat7-websocket.jar/web-fragment.xml         |   24 +-
 res/META-INF/websocket-api.jar.manifest            |   11 +
 res/checkstyle/javax-import-control.xml            |    3 +
 res/checkstyle/org-import-control.xml              |   20 +
 .../eclipse/java-compiler-errors-warnings.txt      |    2 +-
 res/maven/mvn-pub.xml                              |    5 +
 res/maven/mvn.properties.default                   |    2 +-
 res/maven/tomcat-coyote.pom                        |    6 +
 ...{tomcat-coyote.pom => tomcat-websocket-api.pom} |   18 +-
 .../{tomcat-coyote.pom => tomcat7-websocket.pom}   |   22 +-
 test/deployment/context.war                        |  Bin 0 -> 565 bytes
 .../deployment/dirContext/META-INF/context.xml     |   15 +-
 .../deployment/dirContext}/index.html              |   20 +-
 .../deployment/dirNoContext}/index.html            |   20 +-
 test/deployment/noContext.war                      |  Bin 0 -> 240 bytes
 test/javax/el/TestArrayELResolver.java             |  362 ++++++
 test/javax/el/TestBeanELResolver.java              |  446 ++++++-
 .../javax/el/TestELContext.java                    |   25 +-
 test/javax/el/TestListELResolver.java              |  375 ++++++
 test/javax/el/TestMapELResolver.java               |  311 +++++
 test/javax/el/TestResourceBundleELResolver.java    |  257 +++-
 .../javax/el/TesterBean.java                       |   34 +-
 .../javax/el/TesterELContext.java                  |   27 +-
 .../catalina/connector/TestCoyoteAdapter.java      |    9 +-
 .../catalina/connector/TestMaxConnections.java     |   57 +-
 .../apache/catalina/core/TestAsyncContextImpl.java |   93 +-
 .../core/TestStandardContextResources.java         |   34 +
 .../catalina/core/TestSwallowAbortedUploads.java   |    4 +-
 test/org/apache/catalina/core/TesterContext.java   |   11 +
 .../catalina/filters/TestCsrfPreventionFilter.java |    1 +
 .../catalina/filters/TesterServletContext.java     |   10 +-
 .../apache/catalina/startup/TestContextConfig.java |   13 +
 test/org/apache/catalina/startup/TestTomcat.java   |   39 +
 .../TesterServletContainerInitializer1.java        |   33 +-
 .../TesterServletContainerInitializer2.java        |   33 +-
 .../apache/catalina/websocket/TestWebSocket.java   |   84 +-
 test/org/apache/coyote/ajp/SimpleAjpClient.java    |   41 +-
 .../coyote/ajp/TestAbstractAjpProcessor.java       |  170 ++-
 test/org/apache/coyote/ajp/TesterAjpMessage.java   |   62 +
 .../coyote/http11/TestAbstractHttp11Processor.java |   48 +
 .../http11/filters/TestChunkedInputFilter.java     |   73 +-
 test/org/apache/jasper/compiler/TestParser.java    |   22 +
 .../apache/tomcat/util/http/TestParameters.java    |    4 +-
 .../tomcat/util/http/parser/TestMediaType.java     |   19 +
 test/org/apache/tomcat/util/net/TesterSupport.java |   47 +-
 test/org/apache/tomcat/websocket/TestUtil.java     |  284 +++++
 .../tomcat/websocket/TestWebSocketFrameClient.java |   95 ++
 test/org/apache/tomcat/websocket/TestWsFrame.java  |   49 +
 .../tomcat/websocket/TestWsPingPongMessages.java   |  100 ++
 .../tomcat/websocket/TestWsRemoteEndpoint.java     |  139 ++
 .../org/apache/tomcat/websocket/TestWsSession.java |  107 ++
 .../tomcat/websocket/TestWsSubprotocols.java       |  121 ++
 .../tomcat/websocket/TestWsWebSocketContainer.java |  880 +++++++++++++
 .../apache/tomcat/websocket/TesterEchoServer.java  |  152 +++
 .../tomcat/websocket/TesterFirehoseServer.java     |  106 ++
 .../tomcat/websocket/TesterMessageCountClient.java |  226 ++++
 .../tomcat/websocket/TesterWsClientAutobahn.java   |  188 +++
 .../websocket/pojo/TestEncodingDecoding.java       |  477 +++++++
 .../websocket/pojo/TestPojoEndpointBase.java       |  155 +++
 .../websocket/pojo/TestPojoMethodMapping.java      |  152 +++
 .../apache/tomcat/websocket/pojo/TesterUtil.java   |   74 ++
 .../tomcat/websocket/server/TestUriTemplate.java   |  213 ++++
 .../websocket/server/TestWsServerContainer.java    |  178 +++
 test/webapp-2.3/WEB-INF/tags12.tld                 |    2 +-
 test/webapp-2.3/WEB-INF/tags20.tld                 |    8 +-
 test/webapp-2.3/WEB-INF/tags21.tld                 |    2 +-
 test/webapp-2.4/WEB-INF/tags20.tld                 |    8 +-
 test/webapp-2.4/WEB-INF/tags21.tld                 |    2 +-
 test/webapp-2.4/WEB-INF/web.xml                    |    6 +-
 test/webapp-2.5/WEB-INF/tags20.tld                 |    8 +-
 test/webapp-2.5/WEB-INF/tags21.tld                 |    2 +-
 .../webapp-3.0-fragments/WEB-INF/lib/resources.jar |  Bin 19244 -> 20061 bytes
 .../webapp-3.0/WEB-INF/tags/bug55198.tagx          |   23 +-
 test/webapp-3.0/WEB-INF/tags20.tld                 |    8 +-
 test/webapp-3.0/WEB-INF/tags21.tld                 |    2 +-
 test/webapp-3.0/WEB-INF/web.xml                    |    6 +-
 .../webapp-3.0/bug5nnnn/bug55198.jsp               |   28 +-
 webapps/docs/changelog.xml                         |  407 +++++-
 webapps/docs/config/ajp.xml                        |    3 -
 webapps/docs/config/cluster-channel.xml            |   24 +
 webapps/docs/config/cluster-manager.xml            |    7 +
 webapps/docs/config/http.xml                       |   20 +-
 webapps/docs/realm-howto.xml                       |    2 +-
 webapps/docs/web-socket-howto.xml                  |   81 +-
 .../classes/websocket/chat/ChatAnnotation.java     |   92 ++
 .../classes/websocket/echo/EchoAnnotation.java     |   70 +
 .../classes/websocket/echo/EchoEndpoint.java       |   56 +
 .../classes/websocket/echo/WsConfigListener.java   |   46 +
 .../WEB-INF/classes/websocket/snake/Location.java  |    8 +-
 .../WEB-INF/classes/websocket/snake/Snake.java     |   40 +-
 .../classes/websocket/snake/SnakeAnnotation.java   |  113 ++
 .../classes/websocket/snake/SnakeTimer.java        |  107 ++
 .../{ => tc7}/chat/ChatWebSocketServlet.java       |    4 +-
 .../websocket/{ => tc7}/echo/EchoMessage.java      |    8 +-
 .../websocket/{ => tc7}/echo/EchoStream.java       |    7 +-
 .../classes/websocket/tc7/snake/Direction.java     |   24 +-
 .../websocket/{ => tc7}/snake/Location.java        |    6 +-
 .../classes/websocket/{ => tc7}/snake/Snake.java   |    6 +-
 .../{ => tc7}/snake/SnakeWebSocketServlet.java     |    6 +-
 webapps/examples/WEB-INF/web.xml                   |   21 +-
 webapps/examples/index.html                        |    4 +-
 webapps/examples/websocket-deprecated/chat.html    |  125 ++
 .../{websocket => websocket-deprecated}/echo.html  |    4 +-
 .../examples/{ => websocket-deprecated}/index.html |   25 +-
 webapps/examples/websocket-deprecated/snake.html   |  258 ++++
 webapps/examples/websocket/echo.html               |   14 +-
 389 files changed, 24175 insertions(+), 2939 deletions(-)

diff --git a/BUILDING.txt b/BUILDING.txt
index 88c165b..1800bff 100644
--- a/BUILDING.txt
+++ b/BUILDING.txt
@@ -15,7 +15,7 @@
   limitations under the License.
 ================================================================================
 
-$Id: BUILDING.txt 1457383 2013-03-17 06:22:36Z kkolinko $
+$Id: BUILDING.txt 1515813 2013-08-20 13:10:50Z markt $
 
             ====================================================
             Building The Apache Tomcat @VERSION_MAJOR_MINOR@ Servlet/JSP Container
@@ -33,11 +33,11 @@ In order to build a binary distribution version of Apache Tomcat from a
 source distribution, do the following:
 
 
-(1) Download and Install a Java Development Kit
+(1) Download and Install a Java 6 and Java 7 Development Kit
 
- 1. If the JDK is already installed, skip to (2).
+ 1. If the JDKs are already installed, skip to (2).
 
- 2. Download a version 6 of Java Development Kit (JDK) release (use the
+ 2. Download a version 6 of the Java Development Kit (JDK) release (use the
     latest update available for your chosen version) from
 
         http://www.oracle.com/technetwork/java/javase/downloads/index.html
@@ -45,7 +45,7 @@ source distribution, do the following:
 
     Note regarding later versions of Java:
 
-      As documented elsewhere, one of components in Apache Tomcat includes
+      As documented elsewhere, one of the components in Apache Tomcat includes
       a private copy of the Apache Commons DBCP library. The source code
       for this library is downloaded, processed by the build script
       (renaming the packages) and compiled.
@@ -53,7 +53,8 @@ source distribution, do the following:
       Due to changes in JDBC interfaces implemented by the library between
       versions of Java SE specification, the library has to target specific
       version of Java and can be compiled only with the JDK version
-      implementing this version of specification.
+      implementing this version of specification. Therefore, the build Tomcat
+      build process must be executed with a Java 6 JDK.
 
       See Apache Commons DBCP project web site for more details on
       available versions of the library and its requirements,
@@ -64,11 +65,24 @@ source distribution, do the following:
       several workarounds are possible. One of them is to skip building
       the component (tomcat-dbcp.jar).
 
- 3. Install the JDK according to the instructions included with the release.
+ 3. Install the Java 6 JDK according to the instructions included with the
+    release.
 
  4. Set an environment variable JAVA_HOME to the pathname of the directory
     into which you installed the JDK release.
 
+ 5. Download a version 7 of the Java Development Kit (JDK) release (use the
+    latest update available for your chosen version) from
+
+        http://www.oracle.com/technetwork/java/javase/downloads/index.html
+        or from another JDK vendor.
+
+ 6. Install the Java 7 JDK according to the instructions included with the
+    release.
+
+* NOTE: The Java 7 JDK is only required if you wish to build Tomcat with
+  JSR-356 (Java WebSocket 1.0) support.
+
 
 (2) Install Apache Ant 1.8.x on your computer
 
@@ -158,6 +172,10 @@ ${tomcat.source}.
 
   See Apache Ant documentation for the <setproxy> task for details.
 
+* NOTE: Users wishing to build Tomcat with JSR-356 (Java WebSocket 1.0) support
+  must also set the java.7.home build property to the location of the Java 7 JDK
+  installation.
+
  3. Go to the sources directory and run Ant:
 
         cd ${tomcat.source}
diff --git a/KEYS b/KEYS
index a62e492..9f91ce2 100644
--- a/KEYS
+++ b/KEYS
@@ -523,3 +523,62 @@ YB5npZhfo59tF4uuvKvYKWghrF8wSwEE7F4cJAuqklo0TArBUd2WB0Wl8kAWtThf
 /PLn/VX9dZEFVPVbPapJpkNv8A==
 =Vxhw
 -----END PGP PUBLIC KEY BLOCK-----
+pub   4096R/D63011C7 2013-09-19
+      Key fingerprint = 713D A88B E509 1153 5FE7  16F5 208B 0AB1 D630 11C7
+uid                  Violeta Georgieva Georgieva (CODE SIGNING KEY) <violetagg at apache.org>
+sig 3        D63011C7 2013-09-19  Violeta Georgieva Georgieva (CODE SIGNING KEY) <violetagg at apache.org>
+sub   4096R/30480593 2013-09-19
+sig          D63011C7 2013-09-19  Violeta Georgieva Georgieva (CODE SIGNING KEY) <violetagg at apache.org>
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v2.0.21 (MingW32)
+
+mQINBFI6WiwBEAD+kkswnsY8eaqvYkS+ZB5MJr7juWrv9Lw9OGsIXFlTvD1XK01c
+E8k4+uA2sOtaXQ5wTMdc5N3YzAXqFxplWuafQgEvhyTTq37M5YCxvtYEZy/EHQYT
+iok5H97lMRKbhLdZB+wkdsa0P/L1FveCUiEawKY/Rrfi+UeRAneSV+m7S+RrPphZ
+M9aNSczqYKfAqlpUAlUcrF/bt59vjhepoHcE4ev6SB+PCs0vbvX4iTvvZCTk1lZ9
+InS2wdK80Jz9pRB0Uf3LEnZxt9e3RkIFdQOCcEISmNlBKQQKFG+zCpIAbVoMLKEw
+rXWl8mLzGzBbhGmLpFroem3Ln1YiAxUqnPR/MoBquYnpTINwePgwKVWyQ1TXG2MF
+Z7DPayBMN+G51rfLS/8iy35pAnNeqbWQjavdUis6/0aRMv5EYMFMAerutQ5v99bA
+rGj6OL3R6repJLOGT4YWcD/Tw+eU1lMWxbq8BbbRU9Fd0iVFhFyKB/DQSxofvTCe
+PdWXRrptrE0/SmvuoTRVPmB21WyJenKdNmVOQ6U+W1Rs+5IKAdWWrGPcUt0qTrRC
+SL8vAQ7MejYLovFtRHslJRs7T3ratpRcQUNOx1jytJhmSUJktNWZWNHqBTe/eOAU
+Yr+QAkQVQXvRVWzHkDHQRTOFmNYIDZYRkzSP19sBWRnYdCs6CbIVPgMJVwARAQAB
+tEVWaW9sZXRhIEdlb3JnaWV2YSBHZW9yZ2lldmEgKENPREUgU0lHTklORyBLRVkp
+IDx2aW9sZXRhZ2dAYXBhY2hlLm9yZz6JAjcEEwEKACEFAlI6WiwCGwMFCwkIBwMF
+FQoJCAsFFgIDAQACHgECF4AACgkQIIsKsdYwEccMXA/+KMQKWfw2T2CXLhqvQLoh
+Irj1Vi9leAttKqKp2NCHLK1jf1qKzUx5U81VvizIGUsDXGlAvnnavrj+hmQqZdsO
+CoJAo7ViIR1ZhNca1tFK4Sy03wdpNyUkvxVuC+3peXmwhjPJoqU2ONCuDl/bCczl
+QAQpgZCMO93h45U9H6JkjqK01aDorQHxvXo+Ap2IViQvDkNtJ515vG2k5K+x2XHw
+Tv19wr5N2rz407TWKzS5hh7QHRgg+PZs/zPf1YHD7Tg5K6vvmZd+5EsDrse6tZXy
+mzz2+8Yg1SNa765Aq6p1uAQf5NKeej/25TbRYT7RyIlgDXPcPrKxy0cKzpqFqCFs
+jJEcN3NlQq+f1tOvUk8cQQS0G+Qws3EU7I74z8KaUfqmO/5ROrXLS50cKC9CODO0
+UFY8FbJDGzS5cFSBlqXYLeQvaOMg0LsV6wZLu6brxEsRYjSpwM8yBFO4bMcTxt4P
+VYtinNZ+6ude8mMz6BK/0/XbAL6rc5jwO2xj7GTCFNRTWOa8IGtwqg3qnAiHcg/V
+bTBQCOmzMujHBXLnZu6vg79BwzE7Ikq634D6HEwi1bC3XuVz+7NqdUQAGPSapwUo
++0wC5DVwdjhe1zWcf2Zc45HWsx0HaGW28x/tBrw78fgwrSSyV2xunbxGpVaaysTy
+Oini8V70uLofn1SHtxvEQCm5Ag0EUjpaLAEQAKV7FnaAcxkzDa7zjrAgLRho44KM
++lBt28+5KO3Jye1Lpf2+4aspu0PXkGW2Twv2tBQNZYs2CWF+vnHNUDuU8TkSpPt+
+2PRSZrQ0K+IpQF/qY3Wf+LYWFNXk5/wHJLGiQv/008svtupng6Ov39JwCNQ1iG3d
+nSWfnqHwQULyE7JcZf1It94G43+6NBvKakstOdK7d40dVhmRIKDdJkWhN3MKrGab
+FGFAF2Nb57IugQ9QO6Ve/BnjKZmJg7TyUZk27LVTC0aUQgGgDOvsF2Iw95IplCZ/
+jVbwdBGjeCStvI3c0DB+E2xwJ0g2Wf/CBLvmU9GKOW0toBfRUXFbfzNTJfW8kglt
+pDuELsVY5vHHxgujdDInHuTW1930zUw0cNA2+ai3sf+UGejh0e3nGfy1uOK3YQ6H
+2YWgqXlOkri9pMlE0NJo/3PW9QDu0YRplGl65k+GtHD2La1akq5V5Et0VNaOypBP
+OqnUM08LofAS126Kerm7uBSUQDDV6t1VTOBgPW5cJF9I8kdp04pzj4qb/3fuOuGc
+kBRfmO/Vkug8U81w/TnxX6EYGy5fyA4JFBJl++waPS/9dXhVnA0qXEivzw9gNQvC
+uXYcM3nm4yUrOouC/OlC1cS+6Wxjrx6qn3NnsVzMCtefNK93+TdheZ0cJrMhJKkW
+v+qttOzPIleqvDK9ABEBAAGJAh8EGAEKAAkFAlI6WiwCGwwACgkQIIsKsdYwEcdN
+tQ/+L8cw9Z9tfrqovO1fGFQwCSaomShsbjoUb5AR3Hj3OuPGwXd8J62mrw+RnGN+
+0w2RyTz52izYvcoB1jmMFQwqi7vM5KCbw6KA8oRX58WSqiWCIwpbUuTODvJrSXjX
+pz/J/d+PVZi8T1HAu5HxDqNC1XR+eUd1xA9Pgnmmw99+0rmzES7xexWADXo/RRPH
+mDCxGK3UKMHDYJLTx8D3MacMitzQulxVo9xWwH1C7ioL3o5zCv2mfIl32WNjqwpD
+h4gNpnAGRthizeYTgyJM9nCrSWgeE+izGZ9F0g5uXzhyk1f6jlUmXiwjMu/XOcJO
+5Rr1e42bWITuP49nB2QbdSqVvVscwCd5TEpOnQtVNZGsss/wQHXDmGSVrYYUQwO/
+cUrU+hTti1IJXgyFi7F1oxde+LCUxXmizKGoY96dVN+TYH5c17ub1/4/DYpOmcly
+tsQ2TOV0BqK4rgKLGfg2mA4zIFOdqXeGefLQVAF5fFzjFKKDi0ewp3sqy+ed6mKY
+1M/HmRX/YzIouFZ3ChFPIpeY23XxJC0BXkWR4pS7qxnelrWBZ+UbleNr9uHat5rC
+B77712dCT9zz85b380DnuMkrgz4HCnHuTcbHXIF1J604lars6ZrjtBvX+OsRHt7V
+f72qKJufP+n01xliW68LP4v93auM8nuE4kkEJ8ncHyuDq/Q=
+=fM0q
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/bin/catalina.sh b/bin/catalina.sh
index c8d5493..9b24d20 100755
--- a/bin/catalina.sh
+++ b/bin/catalina.sh
@@ -94,7 +94,7 @@
 #                   Example (all one line)
 #                   LOGGING_MANAGER="-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager"
 #
-# $Id: catalina.sh 1498485 2013-07-01 14:37:43Z markt $
+# $Id: catalina.sh 1515929 2013-08-20 19:11:24Z markt $
 # -----------------------------------------------------------------------------
 
 # OS specific support.  $var _must_ be set to either true or false.
@@ -309,20 +309,20 @@ elif [ "$1" = "run" ]; then
       echo "Using Security Manager"
     fi
     shift
-    eval exec \"$_RUNJAVA\" \"$LOGGING_CONFIG\" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
-      -Djava.endorsed.dirs=\"$JAVA_ENDORSED_DIRS\" -classpath \"$CLASSPATH\" \
+    eval exec "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
+      -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
       -Djava.security.manager \
-      -Djava.security.policy==\"$CATALINA_BASE/conf/catalina.policy\" \
-      -Dcatalina.base=\"$CATALINA_BASE\" \
-      -Dcatalina.home=\"$CATALINA_HOME\" \
-      -Djava.io.tmpdir=\"$CATALINA_TMPDIR\" \
+      -Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \
+      -Dcatalina.base="\"$CATALINA_BASE\"" \
+      -Dcatalina.home="\"$CATALINA_HOME\"" \
+      -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
       org.apache.catalina.startup.Bootstrap "$@" start
   else
-    eval exec \"$_RUNJAVA\" \"$LOGGING_CONFIG\" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
-      -Djava.endorsed.dirs=\"$JAVA_ENDORSED_DIRS\" -classpath \"$CLASSPATH\" \
-      -Dcatalina.base=\"$CATALINA_BASE\" \
-      -Dcatalina.home=\"$CATALINA_HOME\" \
-      -Djava.io.tmpdir=\"$CATALINA_TMPDIR\" \
+    eval exec "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
+      -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
+      -Dcatalina.base="\"$CATALINA_BASE\"" \
+      -Dcatalina.home="\"$CATALINA_HOME\"" \
+      -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
       org.apache.catalina.startup.Bootstrap "$@" start
   fi
 
@@ -373,22 +373,22 @@ elif [ "$1" = "start" ] ; then
       echo "Using Security Manager"
     fi
     shift
-    eval \"$_RUNJAVA\" \"$LOGGING_CONFIG\" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
-      -Djava.endorsed.dirs=\"$JAVA_ENDORSED_DIRS\" -classpath \"$CLASSPATH\" \
+    eval "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
+      -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
       -Djava.security.manager \
-      -Djava.security.policy==\"$CATALINA_BASE/conf/catalina.policy\" \
-      -Dcatalina.base=\"$CATALINA_BASE\" \
-      -Dcatalina.home=\"$CATALINA_HOME\" \
-      -Djava.io.tmpdir=\"$CATALINA_TMPDIR\" \
+      -Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \
+      -Dcatalina.base="\"$CATALINA_BASE\"" \
+      -Dcatalina.home="\"$CATALINA_HOME\"" \
+      -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
       org.apache.catalina.startup.Bootstrap "$@" start \
       >> "$CATALINA_OUT" 2>&1 "&"
 
   else
-    eval \"$_RUNJAVA\" \"$LOGGING_CONFIG\" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
-      -Djava.endorsed.dirs=\"$JAVA_ENDORSED_DIRS\" -classpath \"$CLASSPATH\" \
-      -Dcatalina.base=\"$CATALINA_BASE\" \
-      -Dcatalina.home=\"$CATALINA_HOME\" \
-      -Djava.io.tmpdir=\"$CATALINA_TMPDIR\" \
+    eval "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
+      -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
+      -Dcatalina.base="\"$CATALINA_BASE\"" \
+      -Dcatalina.home="\"$CATALINA_HOME\"" \
+      -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
       org.apache.catalina.startup.Bootstrap "$@" start \
       >> "$CATALINA_OUT" 2>&1 "&"
 
@@ -434,11 +434,11 @@ elif [ "$1" = "stop" ] ; then
     fi
   fi
 
-  eval \"$_RUNJAVA\" $LOGGING_MANAGER $JAVA_OPTS \
-    -Djava.endorsed.dirs=\"$JAVA_ENDORSED_DIRS\" -classpath \"$CLASSPATH\" \
-    -Dcatalina.base=\"$CATALINA_BASE\" \
-    -Dcatalina.home=\"$CATALINA_HOME\" \
-    -Djava.io.tmpdir=\"$CATALINA_TMPDIR\" \
+  eval "\"$_RUNJAVA\"" $LOGGING_MANAGER $JAVA_OPTS \
+    -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
+    -Dcatalina.base="\"$CATALINA_BASE\"" \
+    -Dcatalina.home="\"$CATALINA_HOME\"" \
+    -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
     org.apache.catalina.startup.Bootstrap "$@" stop
 
   if [ ! -z "$CATALINA_PID" ]; then
@@ -450,6 +450,8 @@ elif [ "$1" = "stop" ] ; then
           if [ $? != 0 ]; then
             if [ -w "$CATALINA_PID" ]; then
               cat /dev/null > "$CATALINA_PID"
+              # If Tomcat has stopped don't try and force a stop with an empty PID file
+              FORCE=0
             else
               echo "Tomcat stopped but the PID file could not be removed or cleared."
             fi
@@ -461,7 +463,8 @@ elif [ "$1" = "stop" ] ; then
         fi
         if [ $SLEEP -eq 0 ]; then
           if [ $FORCE -eq 0 ]; then
-            echo "Tomcat did not stop in time. PID file was not removed."
+            echo "Tomcat did not stop in time. PID file was not removed. To aid diagnostics a thread dump has been written to standard out."
+            kill -3 `cat "$CATALINA_PID"`
           fi
         fi
         SLEEP=`expr $SLEEP - 1 `
@@ -483,7 +486,13 @@ elif [ "$1" = "stop" ] ; then
             if [ $? -gt 0 ]; then
                 rm -f "$CATALINA_PID" >/dev/null 2>&1
                 if [ $? != 0 ]; then
-                    echo "Tomcat was killed but the PID file could not be removed."
+                    if [ -w "$CATALINA_PID" ]; then
+                        cat /dev/null > "$CATALINA_PID"
+                    else
+                        echo "Tomcat was killed but the PID file could not be removed."
+                    fi
+                    # Set this to zero else a warning will be issued about the process still running
+                    KILL_SLEEP_INTERVAL=0
                 fi
                 break
             fi
@@ -501,11 +510,11 @@ elif [ "$1" = "stop" ] ; then
 
 elif [ "$1" = "configtest" ] ; then
 
-    eval \"$_RUNJAVA\" $LOGGING_MANAGER $JAVA_OPTS \
-      -Djava.endorsed.dirs=\"$JAVA_ENDORSED_DIRS\" -classpath \"$CLASSPATH\" \
-      -Dcatalina.base=\"$CATALINA_BASE\" \
-      -Dcatalina.home=\"$CATALINA_HOME\" \
-      -Djava.io.tmpdir=\"$CATALINA_TMPDIR\" \
+    eval "\"$_RUNJAVA\"" $LOGGING_MANAGER $JAVA_OPTS \
+      -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
+      -Dcatalina.base="\"$CATALINA_BASE\"" \
+      -Dcatalina.home="\"$CATALINA_HOME\"" \
+      -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
       org.apache.catalina.startup.Bootstrap configtest
     result=$?
     if [ $result -ne 0 ]; then
diff --git a/bin/daemon.sh b/bin/daemon.sh
index 9a4640d..9821ff9 100755
--- a/bin/daemon.sh
+++ b/bin/daemon.sh
@@ -62,6 +62,11 @@ do
         shift; shift;
         continue
     ;;
+    --service-start-wait-time )
+        SERVICE_START_WAIT_TIME="$2"
+        shift; shift;
+        continue
+    ;;
     * )
         break
     ;;
@@ -101,7 +106,15 @@ fi
 test ".$CATALINA_HOME" = . && CATALINA_HOME=`cd "$DIRNAME/.." >/dev/null; pwd`
 test ".$CATALINA_BASE" = . && CATALINA_BASE="$CATALINA_HOME"
 test ".$CATALINA_MAIN" = . && CATALINA_MAIN=org.apache.catalina.startup.Bootstrap
-test ".$JSVC" = . && JSVC="$CATALINA_BASE/bin/jsvc"
+# If not explicitly set, look for jsvc in CATALINA_BASE first then CATALINA_HOME
+if [ -z $JSVC ]; then
+    JSVC="$CATALINA_BASE/bin/jsvc"
+    if [ ! -x $JSVC ]; then
+        JSVC="$CATALINA_HOME/bin/jsvc"
+    fi
+fi
+# Set the default service-start wait time if necessary
+test ".$SERVICE_START_WAIT_TIME" = . && SERVICE_START_WAIT_TIME=10
 
 # Ensure that any user defined CLASSPATH variables are not used on startup,
 # but allow them to be specified in setenv.sh, in rare case when it is needed.
@@ -169,7 +182,7 @@ case "$1" in
       $JSVC_OPTS \
       -java-home "$JAVA_HOME" \
       -pidfile "$CATALINA_PID" \
-      -wait 10 \
+      -wait "$SERVICE_START_WAIT_TIME" \
       -nodetach \
       -outfile "&1" \
       -errfile "&2" \
@@ -187,7 +200,7 @@ case "$1" in
       -java-home "$JAVA_HOME" \
       -user $TOMCAT_USER \
       -pidfile "$CATALINA_PID" \
-      -wait 10 \
+      -wait "$SERVICE_START_WAIT_TIME" \
       -outfile "$CATALINA_OUT" \
       -errfile "&1" \
       -classpath "$CLASSPATH" \
diff --git a/build.properties.default b/build.properties.default
index a9f2a89..202b624 100644
--- a/build.properties.default
+++ b/build.properties.default
@@ -21,13 +21,13 @@
 # modules that Tomcat depends on.  Copy this file to "build.properties"
 # in the top-level source directory, and customize it as needed.
 #
-# $Id: build.properties.default 1498817 2013-07-02 07:54:21Z markt $
+# $Id: build.properties.default 1526165 2013-09-25 12:23:00Z violetagg $
 # -----------------------------------------------------------------------------
 
 # ----- Version Control Flags -----
 version.major=7
 version.minor=0
-version.build=42
+version.build=45
 version.patch=0
 version.suffix=
 
@@ -46,6 +46,9 @@ test.accesslog=false
 # Workaround against http://bugs.sun.com/view_bug.do?bug_id=6202721
 test.jvmarg.egd=-Djava.security.egd=file:/dev/./urandom
 
+# Location of Java7 JDK
+#java.7.home=/path/to/java7/jdk
+
 # Location of GPG executable (used only for releases)
 gpg.exec=/path/to/gpg
 
@@ -133,7 +136,7 @@ jdt.loc.1=http://archive.eclipse.org/eclipse/downloads/drops4/${jdt.release}/ecj
 jdt.loc.2=http://download.eclipse.org/eclipse/downloads/drops4/${jdt.release}/ecj-${jdt.version}.jar
 
 # ----- Tomcat native library -----
-tomcat-native.version=1.1.27
+tomcat-native.version=1.1.28
 tomcat-native.home=${base.path}/tomcat-native-${tomcat-native.version}
 tomcat-native.tar.gz=${tomcat-native.home}/tomcat-native.tar.gz
 tomcat-native.loc.1=${base-tomcat.loc.1}/tomcat-connectors/native/${tomcat-native.version}/source/tomcat-native-${tomcat-native.version}-src.tar.gz
diff --git a/build.xml b/build.xml
index 9ecc228..ae999df 100644
--- a/build.xml
+++ b/build.xml
@@ -47,6 +47,7 @@
   <property name="servlet.revision" value="FR" />
   <property name="jsp.revision" value="FR" />
   <property name="el.revision" value="FR" />
+  <property name="websocket.revision" value="FR" />
 
   <!-- Release artifact base names -->
   <property name="final.name"            value="${project}-${version}" />
@@ -148,8 +149,18 @@
   <!-- jdbc-pool JARs & source JARs -->
   <property name="tomcat-jdbc.jar" value="${tomcat.pool}/tomcat-jdbc.jar"/>
   <property name="tomcat-jdbc-src.jar" value="${tomcat.pool}/tomcat-jdbc-src.jar"/>
-  
-  
+
+  <!-- WebSocket 1.0 (JSR-356) API and implementation JARs & source JARS     -->
+  <!-- The API JAR is the same as the one that ships with Tomcat 8 except    -->
+  <!-- that it does not use Java 7 features (<> and multi-catch) and has     -->
+  <!-- been compiled for Java 6. The implementation JAR includes a number of -->
+  <!-- modifications to remove the dependencies on the Servlet 3.1 APIs.     -->
+  <!-- The implementation JAR will only work with Tomcat 7.                  -->
+  <property name="websocket-api.jar" value="${tomcat.build}/lib/websocket-api.jar"/>
+  <property name="tomcat7-websocket.jar" value="${tomcat.build}/lib/tomcat7-websocket.jar"/>
+  <property name="websocket-api-src.jar" value="${tomcat.src.jars}/websocket-api-src.jar"/>
+  <property name="tomcat7-websocket-src.jar" value="${tomcat.src.jars}/tomcat7-websocket-src.jar"/>
+
   <!-- Tests To Run -->
   <property name="test.name" value="**/Test*.java"/>
   <property name="test.formatter" value="-Dorg.apache.juli.formatter=java.util.logging.SimpleFormatter"/>
@@ -274,6 +285,14 @@
     <include name="javax/el/*" />
   </patternset>
 
+  <patternset id="files.websocket-api">
+    <include name="javax/websocket/**" />
+  </patternset>
+
+  <patternset id="files.tomcat7-websocket">
+    <include name="org/apache/tomcat/websocket/**" />
+  </patternset>
+
   <patternset id="files.bootstrap">
     <include name="org/apache/catalina/startup/Bootstrap.*" />
     <include name="org/apache/catalina/startup/catalina.properties" />
@@ -358,6 +377,8 @@
     <patternset refid="files.catalina" />
     <patternset refid="files.el-api" />
     <patternset refid="files.servlet-api" />
+    <patternset refid="files.websocket-api" />
+    <patternset refid="files.tomcat7-websocket" />
     <patternset refid="files.tomcat-api" />
     <!-- These pattern sets conflict so include files directly
       <patternset refid="files.tomcat-coyote" />
@@ -510,9 +531,7 @@
            excludes="**/.svn/**"
            encoding="ISO-8859-1"
            includeAntRuntime="true" >
-      <!-- Uncomment this to show unchecked warnings:
       <compilerarg value="-Xlint:unchecked"/>
-      -->
       <include name="org/apache/tomcat/buildutil/CheckEol*" />
     </javac>
 
@@ -553,7 +572,10 @@
     </copy>
   </target>
 
-  <target name="compile" depends="build-prepare,download-compile,compile-prepare,validate">
+  <target name="compile" depends="compile-java6,compile-java7" />
+
+  <target name="compile-java6"
+          depends="build-prepare,download-compile,compile-prepare,validate">
     <!-- Compile internal server components -->
     <javac srcdir="java" destdir="${tomcat.classes}"
            debug="${compile.debug}"
@@ -564,11 +586,11 @@
            excludes="**/.svn/**"
            encoding="ISO-8859-1"
            includeAntRuntime="true" >
-      <!-- Uncomment this to show unchecked warnings:
       <compilerarg value="-Xlint:unchecked"/>
-      -->
       <classpath refid="compile.classpath" />
       <exclude name="org/apache/naming/factory/webservices/**" />
+      <!-- Exclude classes that require Java 7 to compile -->
+      <exclude name="org/apache/tomcat/websocket/**" />
     </javac>
     <!-- Copy static resource files -->
     <copy todir="${tomcat.classes}" encoding="ISO-8859-1">
@@ -583,7 +605,31 @@
     </copy>
 
   </target>
-  
+
+  <target name="compile-java7" depends="compile-java6" if="java.7.home" >
+    <javac sourcepath="" srcdir="java" destdir="${tomcat.classes}"
+           debug="${compile.debug}"
+           deprecation="${compile.deprecation}"
+           source="${compile.source}"
+           target="${compile.target}"
+           optimize="${compile.optimize}"
+           excludes="**/.svn/**"
+           encoding="ISO-8859-1"
+           includeAntRuntime="true"
+           fork="true"
+           executable="${java.7.home}/bin/javac">
+      <compilerarg value="-Xlint:unchecked"/>
+      <classpath refid="compile.classpath" />
+      <!-- Only include classes that require Java 7 to compile -->
+      <include name="org/apache/tomcat/websocket/**" />
+      <!-- Not required but prevents a warning being displayed -->
+      <bootclasspath>
+        <fileset dir="${java.7.home}/jre/lib">
+          <include name="*.jar"/>
+        </fileset>
+      </bootclasspath>
+    </javac>
+  </target>
   
 
   <target name="build-manifests" unless="manifests.uptodate"
@@ -594,21 +640,18 @@
     <filter token="servlet.revision" value="${servlet.revision}"/>
     <filter token="jsp.revision" value="${jsp.revision}"/>
     <filter token="el.revision" value="${el.revision}"/>
+    <filter token="websocket.revision" value="${websocket.revision}"/>
 
     <mkdir dir="${tomcat.manifests}" />
     <copy todir="${tomcat.manifests}" overwrite="yes" filtering="yes"
       encoding="ISO-8859-1">
       <filterset refid="version.filters"/>
-      <fileset dir="${tomcat.home}/res/META-INF" >
-        <include name="*.manifest" />
-        <include name="*.license" />
-        <include name="*.notice" />
-      </fileset>
+      <fileset dir="${tomcat.home}/res/META-INF" />
     </copy>
 
   </target>
 
-  <target name="package" depends="compile,build-manifests" >
+  <target name="package" depends="compile,build-manifests,package-java7" >
     <!-- Common Annotations 1.0 JAR File -->
     <jarIt jarfile="${annotations-api.jar}"
       filesDir="${tomcat.classes}"
@@ -727,6 +770,21 @@
 
   </target>
 
+  <target name="package-java7" depends="compile,build-manifests"
+          if="java.7.home">
+    <!-- WebSocket 1.0 API JAR File -->
+    <jarIt jarfile="${websocket-api.jar}"
+      filesDir="${tomcat.classes}"
+      filesId="files.websocket-api"
+      manifest="${tomcat.manifests}/websocket-api.jar.manifest" />
+
+    <!-- WebSocket 1.0 implementation JAR File -->
+    <jarIt jarfile="${tomcat7-websocket.jar}"
+      filesDir="${tomcat.classes}"
+      filesId="files.tomcat7-websocket"
+      meta-inf="${tomcat.manifests}/tomcat7-websocket.jar"/>
+  </target>
+
   <target name="build-docs" depends="compile-prepare" description="Builds all documentation from XML sources">
 
     <copy todir="${tomcat.build}/webapps">
@@ -1061,9 +1119,13 @@
       failonerror="false"/>
     <copy file="${jdt.jar}" todir="${tomcat.embed}" />
 
+    <!-- Note the meta-inf below will work as long as there is only one JAR
+         that needs to add entries. If there is more than one a more complex
+         solution will be required. -->
     <jarIt jarfile="${tomcat-embed-core.jar}"
            filesDir="${tomcat.classes}"
-           filesId="files.tomcat-embed-core"/>
+           filesId="files.tomcat-embed-core"
+           meta-inf="${tomcat.manifests}/tomcat7-websocket.jar"/>
     <jarIt jarfile="${tomcat-embed-jasper.jar}"
            filesDir="${tomcat.classes}"
            filesId="files.tomcat-embed-jasper"/>
@@ -1149,7 +1211,18 @@
       <classpath refid="tomcat.test.classpath" />
       <include name="org/apache/**" />
       <include name="javax/**" />
+      <exclude unless="java.7.home" name="org/apache/catalina/websocket/**" />
+      <exclude unless="java.7.home" name="org/apache/tomcat/websocket/**" />
     </javac>
+    <!-- Copy static resource files the tests need to load via the class     -->
+    <!-- loader.                                                             -->
+    <copy todir="${test.classes}" encoding="ISO-8859-1">
+      <filterset refid="version.filters"/>
+      <fileset dir="test">
+        <include name="**/*.jks"/>
+        <include name="**/*.pem"/>
+      </fileset>
+    </copy>
   </target>
 
   <!-- Default JUnit log output formatter -->
@@ -1158,7 +1231,7 @@
   <property name="junit.formatter.extension" value=".txt" />
 
   <target name="test" description="Runs the JUnit test cases"
-          depends="test-bio,test-nio,test-apr" >
+          depends="test-init1,test-init2,test-bio,test-nio,test-apr" >
     <fail if="test.result.error" message='Some tests completed with an Error. See ${tomcat.build}/logs for details, search for "FAILED".' />
     <fail if="test.result.failure" message='Some tests completed with a Failure. See ${tomcat.build}/logs for details, search for "FAILED".' />
   </target>
@@ -1187,6 +1260,14 @@
     <available file="${test.apr.loc}" property="apr.exists" />
   </target>
 
+  <target name="test-init1" if="java.7.home">
+    <property name="java.bin.path" value="${java.7.home}/bin/"/>
+  </target>
+
+  <target name="test-init2" unless="java.7.home">
+    <property name="java.bin.path" value=""/>
+  </target>
+
   <macrodef name="runtests"
             description="Runs the unit tests using the specified connector.
               Does not stop on errors, but sets 'test.result.error' and 'test.result.failure' properties.">
@@ -1199,7 +1280,8 @@
       <junit printsummary="yes" fork="yes" dir="." showoutput="yes"
         errorproperty="test.result.error"
         failureproperty="test.result.failure"
-        haltonfailure="${test.haltonfailure}" >
+        haltonfailure="${test.haltonfailure}"
+        jvm="${java.bin.path}java" >
 
         <jvmarg value="${test.jvmarg.egd}"/>
         <jvmarg value="-Djava.library.path=${test.apr.loc}"/>
@@ -1227,6 +1309,11 @@
             <exclude name="**/Tester*.java" />
             <!-- Exclude the tests known to fail -->
             <exclude name="org/apache/catalina/tribes/test/**" />
+            <!-- Exclude both sets of WebSocket tests unless Java 7 is       -->
+            <!-- present as the WebSocket exmaples (used by the tests) won't -->
+            <!-- build without it.                                           -->
+            <exclude unless="java.7.home" name="org/apache/catalina/websocket/**" />
+            <exclude unless="java.7.home" name="org/apache/tomcat/websocket/**" />
           </fileset>
         </batchtest>
       </junit>
@@ -1651,6 +1738,25 @@ Apache Tomcat ${version} native binaries for Win64 AMD64/EMT64 platform.
     </javadoc>
     <patch-javadoc dir="${tomcat.dist}/webapps/docs/elapi"
       docencoding="ISO-8859-1"/>
+    <javadoc packagenames="javax.websocket.*"
+      sourcepath="${tomcat.dist}/src/java"
+      destdir="${tomcat.dist}/webapps/docs/websocketapi"
+      version="true"
+      windowtitle="WebSocket 1.0 API Documentation - Apache Tomcat ${version}"
+      doctitle="WebSocket 1.0 API - Apache Tomcat ${version}"
+      header="<b>WebSocket 1.0 - Apache Tomcat ${version}</b>"
+      bottom="Copyright &#169; 2000-${year} Apache Software Foundation. All Rights Reserved."
+      encoding="ISO-8859-1"
+      additionalparam="-breakiterator"
+      maxmemory="256m" >
+      <classpath>
+        <path refid="compile.classpath"/>
+        <path refid="tomcat.webservices.classpath"/>
+        <path location="${ant.core.lib}"/>
+      </classpath>
+    </javadoc>
+    <patch-javadoc dir="${tomcat.dist}/webapps/docs/websocketapi"
+      docencoding="ISO-8859-1"/>
     <javadoc packagenames="org.apache.*"
       destdir="${tomcat.dist}/webapps/docs/api"
       version="true"
@@ -1669,7 +1775,8 @@ Apache Tomcat ${version} native binaries for Win64 AMD64/EMT64 platform.
       <link href="../servletapi"/>
       <link href="../jspapi"/>
       <link href="../elapi"/>
-      <link href="http://docs.oracle.com/javase/6/docs/api/"/>
+      <link href="../websocketapi"/>
+      <link href="http://docs.oracle.com/javase/7/docs/api/"/>
       <link href="http://commons.apache.org/proper/commons-io/javadocs/api-release/"/>
       <link href="http://docs.oracle.com/javaee/6/api/"/>
       <sourcepath>
@@ -1762,6 +1869,8 @@ Apache Tomcat ${version} native binaries for Win64 AMD64/EMT64 platform.
         <include name="jasper.jar"/>
         <include name="jasper-el.jar"/>
         <include name="servlet-api.jar"/>
+        <include name="websocket-api.jar"/>
+        <include name="tomcat7-websocket.jar"/>
         <include name="tomcat-coyote.jar"/>
         <include name="tomcat-util.jar"/>
       </fileset>
@@ -1933,7 +2042,10 @@ Apache Tomcat ${version} native binaries for Win64 AMD64/EMT64 platform.
   </target>
 
   <!-- Sets properties only required for releases -->
-  <target name="release-init" depends="gpg-init-1,gpg-init-2" />
+  <target name="release-init" depends="gpg-init-1,gpg-init-2" >
+    <fail unless="java.7.home"
+          message="The java.7.home property must be set for a release build"/>
+  </target>
 
   <target name="gpg-init-1">
     <available file="${gpg.exec}" property="gpg.exec.available"/>
@@ -2260,7 +2372,7 @@ Apache Tomcat ${version} native binaries for Win64 AMD64/EMT64 platform.
 
   <!-- Packages the source code in JARs to match the binary JARs -->
   <target name="package-src-jar"
-          depends="build-manifests,build-tomcat-jdbc-src">
+          depends="build-manifests,build-tomcat-jdbc-src,package-src-jar-java7">
 
     <mkdir dir="${tomcat.src.jars}" />
 
@@ -2356,6 +2468,23 @@ Apache Tomcat ${version} native binaries for Win64 AMD64/EMT64 platform.
     <copy file="${tomcat-jdbc-src.jar}" todir="${tomcat.src.jars}" />
   </target>
 
+  <target name="package-src-jar-java7"
+          depends="build-manifests,build-tomcat-jdbc-src"
+          if="java.7.home">
+
+    <!-- WebSocket 1.0 API JAR File -->
+    <jarIt jarfile="${websocket-api-src.jar}"
+      filesDir="java"
+      filesId="files.websocket-api"
+      manifest="${tomcat.manifests}/websocket-api.jar.manifest" />
+
+    <!-- WebSocket 1.0 implementation JAR File -->
+    <jarIt jarfile="${tomcat7-websocket-src.jar}"
+      filesDir="java"
+      filesId="files.tomcat7-websocket" />
+  </target>
+
+
   <!-- ========================= Cleaning Targets ========================== -->
 
   <target name="clean-depend"
@@ -2680,6 +2809,8 @@ Read the Building page on the Apache Tomcat documentation site for details on ho
                default="${tomcat.manifests}/default.notice" />
     <attribute name="license" description="the NOTICE file to use"
                default="${tomcat.manifests}/default.license" />
+    <attribute name="meta-inf" description="additional contents for META-INF"
+               default="${tomcat.manifests}/default" />
     <sequential>
       <jar jarfile="@{jarfile}" manifest="@{manifest}">
         <fileset dir="@{filesDir}">
@@ -2688,6 +2819,8 @@ Read the Building page on the Apache Tomcat documentation site for details on ho
           <exclude name="**/package.html" />
           <exclude name="**/LocalStrings_*" />
         </fileset>
+        <zipfileset dir="@{meta-inf}" prefix="META-INF/"
+                    excludes=".gitignore" />
         <zipfileset file="@{notice}" fullpath="META-INF/NOTICE" />
         <zipfileset file="@{license}" fullpath="META-INF/LICENSE" />
       </jar>
diff --git a/conf/catalina.policy b/conf/catalina.policy
index 665bb02..e67d6af 100644
--- a/conf/catalina.policy
+++ b/conf/catalina.policy
@@ -24,7 +24,7 @@
 // * Read access to the web application's document root directory
 // * Read, write and delete access to the web application's working directory
 //
-// $Id: catalina.policy 1460221 2013-03-23 20:17:29Z kkolinko $
+// $Id: catalina.policy 1524081 2013-09-17 14:55:01Z schultz $
 // ============================================================================
 
 
@@ -84,6 +84,7 @@ grant codeBase "file:${catalina.home}/bin/tomcat-juli.jar" {
 
         permission java.util.PropertyPermission "java.util.logging.config.class", "read";
         permission java.util.PropertyPermission "java.util.logging.config.file", "read";
+        permission java.util.PropertyPermission "org.apache.juli.ClassLoaderLogManager.debug", "read";
         permission java.util.PropertyPermission "catalina.base", "read";
 
         // Note: To enable per context logging configuration, permit read access to
diff --git a/conf/catalina.properties b/conf/catalina.properties
index daa5311..3ff30bf 100644
--- a/conf/catalina.properties
+++ b/conf/catalina.properties
@@ -89,7 +89,7 @@ shared.loader=
 # - Common non-Tomcat JARs
 tomcat.util.scan.DefaultJarScanner.jarsToSkip=\
 bootstrap.jar,commons-daemon.jar,tomcat-juli.jar,\
-annotations-api.jar,el-api.jar,jsp-api.jar,servlet-api.jar,\
+annotations-api.jar,el-api.jar,jsp-api.jar,servlet-api.jar,websocket-api.jar,\
 catalina.jar,catalina-ant.jar,catalina-ha.jar,catalina-tribes.jar,\
 jasper.jar,jasper-el.jar,ecj-*.jar,\
 tomcat-api.jar,tomcat-util.jar,tomcat-coyote.jar,tomcat-dbcp.jar,\
@@ -105,9 +105,9 @@ commons-math*.jar,commons-pool*.jar,\
 jstl.jar,\
 geronimo-spec-jaxrpc*.jar,wsdl4j*.jar,\
 ant.jar,ant-junit*.jar,aspectj*.jar,jmx.jar,h2*.jar,hibernate*.jar,httpclient*.jar,\
-jmx-tools.jar,jta*.jar,log4j*.jar,mail*.jar,slf4j*.jar,\
+jmx-tools.jar,jta*.jar,log4j.jar,log4j-1*.jar,mail*.jar,slf4j*.jar,\
 xercesImpl.jar,xmlParserAPIs.jar,xml-apis.jar,\
-junit.jar,junit-*.jar,ant-launcher.jar
+junit.jar,junit-*.jar,hamcrest*.jar,org.hamcrest*.jar,ant-launcher.jar
 
 # Additional JARs (over and above the default JARs listed above) to skip when
 # scanning for Servlet 3.0 pluggability features. These features include web
@@ -117,7 +117,7 @@ org.apache.catalina.startup.ContextConfig.jarsToSkip=
 
 # Additional JARs (over and above the default JARs listed above) to skip when
 # scanning for TLDs. The list must be a comma separated list of JAR file names.
-org.apache.catalina.startup.TldConfig.jarsToSkip=
+org.apache.catalina.startup.TldConfig.jarsToSkip=tomcat7-websocket.jar
 
 #
 # String cache configuration.
diff --git a/java/javax/annotation/Generated.java b/java/javax/annotation/Generated.java
index d4721db..8a51e04 100644
--- a/java/javax/annotation/Generated.java
+++ b/java/javax/annotation/Generated.java
@@ -14,8 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
 package javax.annotation;
 
 import java.lang.annotation.ElementType;
@@ -23,13 +21,15 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
- at Target({ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, 
-    ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, 
+/**
+ * @since Common Annotations 1.0
+ */
+ at Target({ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR,
+    ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD,
     ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE})
 @Retention(RetentionPolicy.SOURCE)
-
 public @interface Generated {
     public String[] value();
     public String date() default "";
-    public String comment() default "";
+    public String comments() default "";
 }
diff --git a/java/javax/annotation/Resources.java b/java/javax/annotation/ManagedBean.java
similarity index 90%
copy from java/javax/annotation/Resources.java
copy to java/javax/annotation/ManagedBean.java
index 4b398f2..97b959c 100644
--- a/java/javax/annotation/Resources.java
+++ b/java/javax/annotation/ManagedBean.java
@@ -5,17 +5,15 @@
  * The ASF licenses this file to You 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 javax.annotation;
 
 import java.lang.annotation.ElementType;
@@ -23,9 +21,11 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * @since Common Annotations 1.1
+ */
 @Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
-
-public @interface Resources {
-    public Resource[] value();
+public @interface ManagedBean {
+    String value() default "";
 }
diff --git a/java/javax/annotation/PostConstruct.java b/java/javax/annotation/PostConstruct.java
index 8ad363a..c51f630 100644
--- a/java/javax/annotation/PostConstruct.java
+++ b/java/javax/annotation/PostConstruct.java
@@ -14,8 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
 package javax.annotation;
 
 import java.lang.annotation.ElementType;
@@ -23,9 +21,11 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * @since Common Annotations 1.0
+ */
 @Target({ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
-
 public @interface PostConstruct {
     // No attributes
 }
diff --git a/java/javax/annotation/PreDestroy.java b/java/javax/annotation/PreDestroy.java
index d5be75a..00c5273 100644
--- a/java/javax/annotation/PreDestroy.java
+++ b/java/javax/annotation/PreDestroy.java
@@ -14,8 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
 package javax.annotation;
 
 import java.lang.annotation.ElementType;
@@ -23,9 +21,11 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * @since Common Annotations 1.0
+ */
 @Target({ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
-
 public @interface PreDestroy {
     // No attributes
 }
diff --git a/java/javax/annotation/Resource.java b/java/javax/annotation/Resource.java
index 21a5d8b..58c8025 100644
--- a/java/javax/annotation/Resource.java
+++ b/java/javax/annotation/Resource.java
@@ -14,8 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
 package javax.annotation;
 
 import java.lang.annotation.ElementType;
@@ -23,9 +21,11 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * @since Common Annotations 1.0
+ */
 @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
 @Retention(RetentionPolicy.RUNTIME)
-
 public @interface Resource {
     public enum AuthenticationType {
         CONTAINER,
@@ -38,4 +38,8 @@ public @interface Resource {
     public boolean shareable() default true;
     public String description() default "";
     public String mappedName() default "";
+    /**
+     * @since Common Annotations 1.1
+     */
+    public String lookup() default "";
 }
diff --git a/java/javax/annotation/Resources.java b/java/javax/annotation/Resources.java
index 4b398f2..7e53758 100644
--- a/java/javax/annotation/Resources.java
+++ b/java/javax/annotation/Resources.java
@@ -14,8 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
 package javax.annotation;
 
 import java.lang.annotation.ElementType;
@@ -23,9 +21,11 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * @since Common Annotations 1.0
+ */
 @Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
-
 public @interface Resources {
     public Resource[] value();
 }
diff --git a/java/javax/annotation/security/DeclareRoles.java b/java/javax/annotation/security/DeclareRoles.java
index d5d214a..0f7e652 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/java/javax/annotation/security/DeclareRoles.java
@@ -14,8 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
 package javax.annotation.security;
 
 import java.lang.annotation.ElementType;
@@ -23,9 +21,11 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * @since Common Annotations 1.0
+ */
 @Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
-
 public @interface DeclareRoles {
     public String[] value();
 }
diff --git a/java/javax/annotation/security/DenyAll.java b/java/javax/annotation/security/DenyAll.java
index 6fdf829..fcfd6ec 100644
--- a/java/javax/annotation/security/DenyAll.java
+++ b/java/javax/annotation/security/DenyAll.java
@@ -14,8 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
 package javax.annotation.security;
 
 import java.lang.annotation.ElementType;
@@ -23,9 +21,11 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
- at Target({ElementType.METHOD})
+/**
+ * @since Common Annotations 1.0
+ */
+ at Target({ElementType.METHOD, ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
-
 public @interface DenyAll {
     // No attributes
 }
diff --git a/java/javax/annotation/security/PermitAll.java b/java/javax/annotation/security/PermitAll.java
index dfe3aa5..6707551 100644
--- a/java/javax/annotation/security/PermitAll.java
+++ b/java/javax/annotation/security/PermitAll.java
@@ -14,8 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
 package javax.annotation.security;
 
 import java.lang.annotation.ElementType;
@@ -23,9 +21,11 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * @since Common Annotations 1.0
+ */
 @Target({ElementType.TYPE, ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
-
 public @interface PermitAll {
     // No attributes
 }
diff --git a/java/javax/annotation/security/RolesAllowed.java b/java/javax/annotation/security/RolesAllowed.java
index fced761..566cd2f 100644
--- a/java/javax/annotation/security/RolesAllowed.java
+++ b/java/javax/annotation/security/RolesAllowed.java
@@ -14,8 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
 package javax.annotation.security;
 
 import java.lang.annotation.ElementType;
@@ -23,9 +21,11 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * @since Common Annotations 1.0
+ */
 @Target({ElementType.TYPE, ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
-
 public @interface RolesAllowed {
     public String[] value();
 }
diff --git a/java/javax/annotation/security/RunAs.java b/java/javax/annotation/security/RunAs.java
index 3920d7c..ea6ce16 100644
--- a/java/javax/annotation/security/RunAs.java
+++ b/java/javax/annotation/security/RunAs.java
@@ -14,8 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
 package javax.annotation.security;
 
 import java.lang.annotation.ElementType;
@@ -23,9 +21,11 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * @since Common Annotations 1.0
+ */
 @Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
-
 public @interface RunAs {
     public String value();
 }
diff --git a/java/javax/annotation/Resources.java b/java/javax/annotation/sql/DataSourceDefinition.java
similarity index 58%
copy from java/javax/annotation/Resources.java
copy to java/javax/annotation/sql/DataSourceDefinition.java
index 4b398f2..edf37ad 100644
--- a/java/javax/annotation/Resources.java
+++ b/java/javax/annotation/sql/DataSourceDefinition.java
@@ -5,27 +5,44 @@
  * The ASF licenses this file to You 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 javax.annotation;
+package javax.annotation.sql;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * @since Common Annotations 1.1
+ */
 @Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
-
-public @interface Resources {
-    public Resource[] value();
+public @interface DataSourceDefinition {
+    String className();
+    String name();
+    String description() default "";
+    String url() default "";
+    String user() default "";
+    String password() default "";
+    String databaseName() default "";
+    int portNumber() default -1;
+    String serverName() default "localhost";
+    int isolationLevel() default -1;
+    boolean transactional() default true;
+    int initialPoolSize() default -1;
+    int maxPoolSize() default -1;
+    int minPoolSize() default -1;
+    int maxIdleTime() default -1;
+    int maxStatements() default -1;
+    String[] properties() default {};
+    int loginTimeout() default 0;
 }
diff --git a/java/javax/annotation/security/DeclareRoles.java b/java/javax/annotation/sql/DataSourceDefinitions.java
similarity index 86%
copy from java/javax/annotation/security/DeclareRoles.java
copy to java/javax/annotation/sql/DataSourceDefinitions.java
index d5d214a..ce71818 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/java/javax/annotation/sql/DataSourceDefinitions.java
@@ -5,27 +5,27 @@
  * The ASF licenses this file to You 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 javax.annotation.security;
+package javax.annotation.sql;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * @since Common Annotations 1.1
+ */
 @Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
-
-public @interface DeclareRoles {
-    public String[] value();
+public @interface DataSourceDefinitions {
+    DataSourceDefinition[] value();
 }
diff --git a/java/javax/el/ArrayELResolver.java b/java/javax/el/ArrayELResolver.java
index a319d60..fac83db 100644
--- a/java/javax/el/ArrayELResolver.java
+++ b/java/javax/el/ArrayELResolver.java
@@ -19,7 +19,6 @@ package javax.el;
 
 import java.beans.FeatureDescriptor;
 import java.lang.reflect.Array;
-import java.util.Arrays;
 import java.util.Iterator;
 
 public class ArrayELResolver extends ELResolver {
@@ -62,8 +61,12 @@ public class ArrayELResolver extends ELResolver {
 
         if (base != null && base.getClass().isArray()) {
             context.setPropertyResolved(true);
-            int idx = coerce(property);
-            checkBounds(base, idx);
+            try {
+                int idx = coerce(property);
+                checkBounds(base, idx);
+            } catch (IllegalArgumentException e) {
+                // ignore
+            }
             return base.getClass().getComponentType();
         }
 
@@ -111,8 +114,12 @@ public class ArrayELResolver extends ELResolver {
 
         if (base != null && base.getClass().isArray()) {
             context.setPropertyResolved(true);
-            int idx = coerce(property);
-            checkBounds(base, idx);
+            try {
+                int idx = coerce(property);
+                checkBounds(base, idx);
+            } catch (IllegalArgumentException e) {
+                // ignore
+            }
         }
 
         return this.readOnly;
@@ -120,20 +127,6 @@ public class ArrayELResolver extends ELResolver {
 
     @Override
     public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
-        if (base != null && base.getClass().isArray()) {
-            FeatureDescriptor[] descs = new FeatureDescriptor[Array.getLength(base)];
-            for (int i = 0; i < descs.length; i++) {
-                descs[i] = new FeatureDescriptor();
-                descs[i].setDisplayName("["+i+"]");
-                descs[i].setExpert(false);
-                descs[i].setHidden(false);
-                descs[i].setName(""+i);
-                descs[i].setPreferred(true);
-                descs[i].setValue(RESOLVABLE_AT_DESIGN_TIME, Boolean.FALSE);
-                descs[i].setValue(TYPE, Integer.class);
-            }
-            return Arrays.asList(descs).iterator();
-        }
         return null;
     }
 
diff --git a/java/javax/el/BeanELResolver.java b/java/javax/el/BeanELResolver.java
index 93371d2..46e6c7b 100644
--- a/java/javax/el/BeanELResolver.java
+++ b/java/javax/el/BeanELResolver.java
@@ -175,10 +175,6 @@ public class BeanELResolver extends ELResolver {
 
     @Override
     public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
-        if (context == null) {
-            throw new NullPointerException();
-        }
-
         if (base == null) {
             return null;
         }
@@ -200,10 +196,6 @@ public class BeanELResolver extends ELResolver {
 
     @Override
     public Class<?> getCommonPropertyType(ELContext context, Object base) {
-        if (context == null) {
-            throw new NullPointerException();
-        }
-
         if (base != null) {
             return Object.class;
         }
diff --git a/java/javax/el/ELContext.java b/java/javax/el/ELContext.java
index 1cdf5c7..161f5ae 100644
--- a/java/javax/el/ELContext.java
+++ b/java/javax/el/ELContext.java
@@ -40,7 +40,14 @@ public abstract class ELContext {
     }
     
     // Can't use Class<?> because API needs to match specification
+    /**
+     * @throws NullPointerException
+     *              If the provided key is <code>null</code>
+     */
     public Object getContext(@SuppressWarnings("rawtypes") Class key) {
+        if (key == null) {
+            throw new NullPointerException();
+        }
         if (this.map == null) {
             return null;
         }
diff --git a/java/javax/el/ListELResolver.java b/java/javax/el/ListELResolver.java
index bc63932..77c4ba5 100644
--- a/java/javax/el/ListELResolver.java
+++ b/java/javax/el/ListELResolver.java
@@ -19,7 +19,6 @@ package javax.el;
 
 import java.beans.FeatureDescriptor;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
@@ -121,10 +120,15 @@ public class ListELResolver extends ELResolver {
         if (base instanceof List<?>) {
             context.setPropertyResolved(true);
             List<?> list = (List<?>) base;
-            int idx = coerce(property);
-            if (idx < 0 || idx >= list.size()) {
-                throw new PropertyNotFoundException(
-                        new ArrayIndexOutOfBoundsException(idx).getMessage());
+            try {
+                int idx = coerce(property);
+                if (idx < 0 || idx >= list.size()) {
+                    throw new PropertyNotFoundException(
+                            new ArrayIndexOutOfBoundsException(idx)
+                                    .getMessage());
+                }
+            } catch (IllegalArgumentException e) {
+                // ignore
             }
             return this.readOnly || UNMODIFIABLE.equals(list.getClass());
         }
@@ -134,20 +138,6 @@ public class ListELResolver extends ELResolver {
 
     @Override
     public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
-        if (base instanceof List<?>) {
-            FeatureDescriptor[] descs = new FeatureDescriptor[((List<?>) base).size()];
-            for (int i = 0; i < descs.length; i++) {
-                descs[i] = new FeatureDescriptor();
-                descs[i].setDisplayName("["+i+"]");
-                descs[i].setExpert(false);
-                descs[i].setHidden(false);
-                descs[i].setName(""+i);
-                descs[i].setPreferred(true);
-                descs[i].setValue(RESOLVABLE_AT_DESIGN_TIME, Boolean.FALSE);
-                descs[i].setValue(TYPE, Integer.class);
-            }
-            return Arrays.asList(descs).iterator();
-        }
         return null;
     }
 
diff --git a/java/javax/el/MapELResolver.java b/java/javax/el/MapELResolver.java
index 118301e..2d059f0 100644
--- a/java/javax/el/MapELResolver.java
+++ b/java/javax/el/MapELResolver.java
@@ -124,11 +124,12 @@ public class MapELResolver extends ELResolver {
                 key = itr.next();
                 desc = new FeatureDescriptor();
                 desc.setDisplayName(key.toString());
+                desc.setShortDescription("");
                 desc.setExpert(false);
                 desc.setHidden(false);
                 desc.setName(key.toString());
                 desc.setPreferred(true);
-                desc.setValue(RESOLVABLE_AT_DESIGN_TIME, Boolean.FALSE);
+                desc.setValue(RESOLVABLE_AT_DESIGN_TIME, Boolean.TRUE);
                 desc.setValue(TYPE, key.getClass());
                 feats.add(desc);
             }
diff --git a/java/javax/el/ResourceBundleELResolver.java b/java/javax/el/ResourceBundleELResolver.java
index 4e324ef..1f17b66 100644
--- a/java/javax/el/ResourceBundleELResolver.java
+++ b/java/javax/el/ResourceBundleELResolver.java
@@ -96,9 +96,10 @@ public class ResourceBundleELResolver extends ELResolver {
         
         if (base instanceof ResourceBundle) {
             context.setPropertyResolved(true);
+            return true;
         }
         
-        return true;
+        return false;
     }
 
     @Override
@@ -116,6 +117,7 @@ public class ResourceBundleELResolver extends ELResolver {
                 key = e.nextElement();
                 feat = new FeatureDescriptor();
                 feat.setDisplayName(key);
+                feat.setShortDescription("");
                 feat.setExpert(false);
                 feat.setHidden(false);
                 feat.setName(key);
diff --git a/java/javax/servlet/ServletContext.java b/java/javax/servlet/ServletContext.java
index 191f8f6..62b6a37 100644
--- a/java/javax/servlet/ServletContext.java
+++ b/java/javax/servlet/ServletContext.java
@@ -419,8 +419,8 @@ public interface ServletContext {
      * @param name
      *            a <code>String</code> containing the name of the parameter
      *            whose value is requested
-     * @return a <code>String</code> containing at least the servlet container
-     *         name and version number
+     * @return a <code>String</code> containing the value of the initialization
+     *         parameter
      * @see ServletConfig#getInitParameter
      */
     public String getInitParameter(String name);
diff --git a/java/javax/servlet/http/HttpSession.java b/java/javax/servlet/http/HttpSession.java
index ce99b4e..d15b97c 100644
--- a/java/javax/servlet/http/HttpSession.java
+++ b/java/javax/servlet/http/HttpSession.java
@@ -60,7 +60,7 @@ import javax.servlet.ServletContext;
  * Session information is scoped only to the current web application (
  * <code>ServletContext</code>), so information stored in one context will not
  * be directly visible in another.
- * 
+ *
  * @see HttpSessionBindingListener
  */
 public interface HttpSession {
@@ -68,7 +68,7 @@ public interface HttpSession {
     /**
      * Returns the time when this session was created, measured in milliseconds
      * since midnight January 1, 1970 GMT.
-     * 
+     *
      * @return a <code>long</code> specifying when this session was created,
      *         expressed in milliseconds since 1/1/1970 GMT
      * @exception IllegalStateException
@@ -80,7 +80,7 @@ public interface HttpSession {
      * Returns a string containing the unique identifier assigned to this
      * session. The identifier is assigned by the servlet container and is
      * implementation dependent.
-     * 
+     *
      * @return a string specifying the identifier assigned to this session
      * @exception IllegalStateException
      *                if this method is called on an invalidated session
@@ -94,7 +94,7 @@ public interface HttpSession {
      * <p>
      * Actions that your application takes, such as getting or setting a value
      * associated with the session, do not affect the access time.
-     * 
+     *
      * @return a <code>long</code> representing the last time the client sent a
      *         request associated with this session, expressed in milliseconds
      *         since 1/1/1970 GMT
@@ -105,7 +105,7 @@ public interface HttpSession {
 
     /**
      * Returns the ServletContext to which this session belongs.
-     * 
+     *
      * @return The ServletContext object for the web application
      * @since 2.3
      */
@@ -115,7 +115,7 @@ public interface HttpSession {
      * Specifies the time, in seconds, between client requests before the
      * servlet container will invalidate this session. A zero or negative time
      * indicates that the session should never timeout.
-     * 
+     *
      * @param interval
      *            An integer specifying the number of seconds
      */
@@ -127,7 +127,7 @@ public interface HttpSession {
      * the servlet container will invalidate the session. The maximum time
      * interval can be set with the <code>setMaxInactiveInterval</code> method.
      * A zero or negative time indicates that the session should never timeout.
-     * 
+     *
      * @return an integer specifying the number of seconds this session remains
      *         open between client requests
      * @see #setMaxInactiveInterval
@@ -146,7 +146,7 @@ public interface HttpSession {
     /**
      * Returns the object bound with the specified name in this session, or
      * <code>null</code> if no object is bound under the name.
-     * 
+     *
      * @param name
      *            a string specifying the name of the object
      * @return the object with the specified name
@@ -171,7 +171,7 @@ public interface HttpSession {
     /**
      * Returns an <code>Enumeration</code> of <code>String</code> objects
      * containing the names of all the objects bound to this session.
-     * 
+     *
      * @return an <code>Enumeration</code> of <code>String</code> objects
      *         specifying the names of all the objects bound to this session
      * @exception IllegalStateException
@@ -206,8 +206,8 @@ public interface HttpSession {
      * <code>HttpSessionBindingListener.valueUnbound</code> method is called.
      * <p>
      * If the value passed in is null, this has the same effect as calling
-     * <code>removeAttribute()<code>.
-     * 
+     * <code>removeAttribute()</code>.
+     *
      * @param name
      *            the name to which the object is bound; cannot be null
      * @param value
@@ -241,7 +241,7 @@ public interface HttpSession {
      * <code>HttpSessionBindingListener.valueUnbound</code>. The container then
      * notifies any <code>HttpSessionAttributeListener</code>s in the web
      * application.
-     * 
+     *
      * @param name
      *            the name of the object to remove from this session
      * @exception IllegalStateException
@@ -263,7 +263,7 @@ public interface HttpSession {
 
     /**
      * Invalidates this session then unbinds any objects bound to it.
-     * 
+     *
      * @exception IllegalStateException
      *                if this method is called on an already invalidated session
      */
@@ -274,7 +274,7 @@ public interface HttpSession {
      * session or if the client chooses not to join the session. For example, if
      * the server used only cookie-based sessions, and the client had disabled
      * the use of cookies, then a session would be new on each request.
-     * 
+     *
      * @return <code>true</code> if the server has created a session, but the
      *         client has not yet joined
      * @exception IllegalStateException
diff --git a/java/javax/annotation/security/DeclareRoles.java b/java/javax/websocket/ClientEndpoint.java
similarity index 68%
copy from java/javax/annotation/security/DeclareRoles.java
copy to java/javax/websocket/ClientEndpoint.java
index d5d214a..ee98417 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/java/javax/websocket/ClientEndpoint.java
@@ -5,27 +5,30 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.annotation.security;
+package javax.websocket;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
- at Target({ElementType.TYPE})
- at Retention(RetentionPolicy.RUNTIME)
+import javax.websocket.ClientEndpointConfig.Configurator;
 
-public @interface DeclareRoles {
-    public String[] value();
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.TYPE)
+public @interface ClientEndpoint {
+    String[] subprotocols() default {};
+    Class<? extends Decoder>[] decoders() default {};
+    Class<? extends Encoder>[] encoders() default {};
+    public Class<? extends Configurator> configurator()
+            default Configurator.class;
 }
diff --git a/java/javax/websocket/ClientEndpointConfig.java b/java/javax/websocket/ClientEndpointConfig.java
new file mode 100644
index 0000000..13b6cba
--- /dev/null
+++ b/java/javax/websocket/ClientEndpointConfig.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.websocket;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public interface ClientEndpointConfig extends EndpointConfig {
+
+    List<String> getPreferredSubprotocols();
+
+    List<Extension> getExtensions();
+
+    public Configurator getConfigurator();
+
+    public final class Builder {
+
+        private static final Configurator DEFAULT_CONFIGURATOR =
+                new Configurator() {};
+
+
+        public static Builder create() {
+            return new Builder();
+        }
+
+
+        private Builder() {
+            // Hide default constructor
+        }
+
+        private Configurator configurator = DEFAULT_CONFIGURATOR;
+        private List<String> preferredSubprotocols = Collections.emptyList();
+        private List<Extension> extensions = Collections.emptyList();
+        private List<Class<? extends Encoder>> encoders =
+                Collections.emptyList();
+        private List<Class<? extends Decoder>> decoders =
+                Collections.emptyList();
+
+
+        public ClientEndpointConfig build() {
+            return new DefaultClientEndpointConfig(preferredSubprotocols,
+                    extensions, encoders, decoders, configurator);
+        }
+
+
+        public Builder configurator(Configurator configurator) {
+            if (configurator == null) {
+                this.configurator = DEFAULT_CONFIGURATOR;
+            } else {
+                this.configurator = configurator;
+            }
+            return this;
+        }
+
+
+        public Builder preferredSubprotocols(
+                List<String> preferredSubprotocols) {
+            if (preferredSubprotocols == null ||
+                    preferredSubprotocols.size() == 0) {
+                this.preferredSubprotocols = Collections.emptyList();
+            } else {
+                this.preferredSubprotocols =
+                        Collections.unmodifiableList(preferredSubprotocols);
+            }
+            return this;
+        }
+
+
+        public Builder extensions(
+                List<Extension> extensions) {
+            if (extensions == null || extensions.size() == 0) {
+                this.extensions = Collections.emptyList();
+            } else {
+                this.extensions = Collections.unmodifiableList(extensions);
+            }
+            return this;
+        }
+
+
+        public Builder encoders(List<Class<? extends Encoder>> encoders) {
+            if (encoders == null || encoders.size() == 0) {
+                this.encoders = Collections.emptyList();
+            } else {
+                this.encoders = Collections.unmodifiableList(encoders);
+            }
+            return this;
+        }
+
+
+        public Builder decoders(List<Class<? extends Decoder>> decoders) {
+            if (decoders == null || decoders.size() == 0) {
+                this.decoders = Collections.emptyList();
+            } else {
+                this.decoders = Collections.unmodifiableList(decoders);
+            }
+            return this;
+        }
+    }
+
+
+    public class Configurator {
+
+        /**
+         * Provides the client with a mechanism to inspect and/or modify the headers
+         * that are sent to the server to start the WebSocket handshake.
+         *
+         * @param headers   The HTTP headers
+         */
+        public void beforeRequest(Map<String, List<String>> headers) {
+            // NO-OP
+        }
+
+        /**
+         * Provides the client with a mechanism to inspect the handshake response
+         * that is returned from the server.
+         *
+         * @param handshakeResponse The response
+         */
+        public void afterResponse(HandshakeResponse handshakeResponse) {
+            // NO-OP
+        }
+    }
+}
diff --git a/java/javax/websocket/CloseReason.java b/java/javax/websocket/CloseReason.java
new file mode 100644
index 0000000..ef88d13
--- /dev/null
+++ b/java/javax/websocket/CloseReason.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.websocket;
+
+public class CloseReason {
+
+    private final CloseCode closeCode;
+    private final String reasonPhrase;
+
+    public CloseReason(CloseReason.CloseCode closeCode, String reasonPhrase) {
+        this.closeCode = closeCode;
+        this.reasonPhrase = reasonPhrase;
+    }
+
+    public CloseCode getCloseCode() {
+        return closeCode;
+    }
+
+    public String getReasonPhrase() {
+        return reasonPhrase;
+    }
+
+    @Override
+    public String toString() {
+        return "CloseReason: code [" + closeCode.getCode() +
+                "], reason [" + reasonPhrase + "]";
+    }
+
+    public interface CloseCode {
+        int getCode();
+    }
+
+    public enum CloseCodes implements CloseReason.CloseCode {
+
+        NORMAL_CLOSURE(1000),
+        GOING_AWAY(1001),
+        PROTOCOL_ERROR(1002),
+        CANNOT_ACCEPT(1003),
+        RESERVED(1004),
+        NO_STATUS_CODE(1005),
+        CLOSED_ABNORMALLY(1006),
+        NOT_CONSISTENT(1007),
+        VIOLATED_POLICY(1008),
+        TOO_BIG(1009),
+        NO_EXTENSION(1010),
+        UNEXPECTED_CONDITION(1011),
+        SERVICE_RESTART(1012),
+        TRY_AGAIN_LATER(1013),
+        TLS_HANDSHAKE_FAILURE(1015);
+
+        private int code;
+
+        CloseCodes(int code) {
+            this.code = code;
+        }
+
+        public static CloseCode getCloseCode(final int code) {
+            if (code > 2999 && code < 5000) {
+                return new CloseCode() {
+                    @Override
+                    public int getCode() {
+                        return code;
+                    }
+                };
+            }
+            switch (code) {
+                case 1000:
+                    return CloseCodes.NORMAL_CLOSURE;
+                case 1001:
+                    return CloseCodes.GOING_AWAY;
+                case 1002:
+                    return CloseCodes.PROTOCOL_ERROR;
+                case 1003:
+                    return CloseCodes.CANNOT_ACCEPT;
+                case 1004:
+                    return CloseCodes.RESERVED;
+                case 1005:
+                    return CloseCodes.NO_STATUS_CODE;
+                case 1006:
+                    return CloseCodes.CLOSED_ABNORMALLY;
+                case 1007:
+                    return CloseCodes.NOT_CONSISTENT;
+                case 1008:
+                    return CloseCodes.VIOLATED_POLICY;
+                case 1009:
+                    return CloseCodes.TOO_BIG;
+                case 1010:
+                    return CloseCodes.NO_EXTENSION;
+                case 1011:
+                    return CloseCodes.UNEXPECTED_CONDITION;
+                case 1012:
+                    return CloseCodes.SERVICE_RESTART;
+                case 1013:
+                    return CloseCodes.TRY_AGAIN_LATER;
+                case 1015:
+                    return CloseCodes.TLS_HANDSHAKE_FAILURE;
+                default:
+                    throw new IllegalArgumentException(
+                            "Invalid close code: [" + code + "]");
+            }
+        }
+
+        @Override
+        public int getCode() {
+            return code;
+        }
+    }
+}
diff --git a/java/javax/websocket/ContainerProvider.java b/java/javax/websocket/ContainerProvider.java
new file mode 100644
index 0000000..1824626
--- /dev/null
+++ b/java/javax/websocket/ContainerProvider.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.websocket;
+
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+/**
+ * Use the {@link ServiceLoader} mechanism to provide instances of the WebSocket
+ * client container.
+ */
+public abstract class ContainerProvider {
+
+    private static final String DEFAULT_PROVIDER_CLASS_NAME =
+            "org.apache.tomcat.websocket.WsWebSocketContainer";
+
+    /**
+     * Create a new container used to create outgoing WebSocket connections.
+     */
+    public static WebSocketContainer getWebSocketContainer() {
+        WebSocketContainer result = null;
+
+        ServiceLoader<ContainerProvider> serviceLoader =
+                ServiceLoader.load(ContainerProvider.class);
+        Iterator<ContainerProvider> iter = serviceLoader.iterator();
+        while (result == null && iter.hasNext()) {
+            result = iter.next().getContainer();
+        }
+
+        // Fall-back. Also used by unit tests
+        if (result == null) {
+            try {
+                @SuppressWarnings("unchecked")
+                Class<WebSocketContainer> clazz =
+                        (Class<WebSocketContainer>) Class.forName(
+                                DEFAULT_PROVIDER_CLASS_NAME);
+                result = clazz.newInstance();
+            } catch (ClassNotFoundException e) {
+                // No options left. Just return null.
+            } catch (InstantiationException e) {
+                // No options left. Just return null.
+            } catch (IllegalAccessException e) {
+                // No options left. Just return null.
+            }
+        }
+        return result;
+    }
+
+    protected abstract WebSocketContainer getContainer();
+}
diff --git a/java/javax/websocket/DecodeException.java b/java/javax/websocket/DecodeException.java
new file mode 100644
index 0000000..771cfa5
--- /dev/null
+++ b/java/javax/websocket/DecodeException.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.websocket;
+
+import java.nio.ByteBuffer;
+
+public class DecodeException extends Exception {
+
+    private static final long serialVersionUID = 1L;
+
+    private ByteBuffer bb;
+    private String encodedString;
+
+    public DecodeException(ByteBuffer bb, String message, Throwable cause) {
+        super(message, cause);
+        this.bb = bb;
+    }
+
+    public DecodeException(String encodedString, String message,
+            Throwable cause) {
+        super(message, cause);
+        this.encodedString = encodedString;
+    }
+
+    public DecodeException(ByteBuffer bb, String message) {
+        super(message);
+        this.bb = bb;
+    }
+
+    public DecodeException(String encodedString, String message) {
+        super(message);
+        this.encodedString = encodedString;
+    }
+
+    public ByteBuffer getBytes() {
+        return bb;
+    }
+
+    public String getText() {
+        return encodedString;
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java b/java/javax/websocket/Decoder.java
similarity index 51%
copy from java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
copy to java/javax/websocket/Decoder.java
index 813bea8..fad262e 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
+++ b/java/javax/websocket/Decoder.java
@@ -6,7 +6,7 @@
  * (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
+ *     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,
@@ -14,35 +14,40 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.coyote.http11.upgrade;
+package javax.websocket;
 
 import java.io.IOException;
-import java.io.OutputStream;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
 
+public interface Decoder {
 
-/**
- * Allows data to be written to the upgraded connection.
- */
-public class UpgradeOutbound extends OutputStream {
+    abstract void init(EndpointConfig endpointConfig);
+
+    abstract void destroy();
+
+    interface Binary<T> extends Decoder {
+
+        T decode(ByteBuffer bytes) throws DecodeException;
 
-    @Override
-    public void flush() throws IOException {
-        processor.flush();
+        boolean willDecode(ByteBuffer bytes);
     }
 
-    private final UpgradeProcessor<?> processor;
+    interface BinaryStream<T> extends Decoder {
 
-    public UpgradeOutbound(UpgradeProcessor<?> processor) {
-        this.processor = processor;
+        T decode(InputStream is) throws DecodeException, IOException;
     }
 
-    @Override
-    public void write(int b) throws IOException {
-        processor.write(b);
+    interface Text<T> extends Decoder {
+
+        T decode(String s) throws DecodeException;
+
+        boolean willDecode(String s);
     }
 
-    @Override
-    public void write(byte[] b, int off, int len) throws IOException {
-        processor.write(b, off, len);
+    interface TextStream<T> extends Decoder {
+
+        T decode(Reader reader) throws DecodeException, IOException;
     }
 }
diff --git a/java/javax/websocket/DefaultClientEndpointConfig.java b/java/javax/websocket/DefaultClientEndpointConfig.java
new file mode 100644
index 0000000..d271371
--- /dev/null
+++ b/java/javax/websocket/DefaultClientEndpointConfig.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.websocket;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+final class DefaultClientEndpointConfig implements ClientEndpointConfig {
+
+    private final List<String> preferredSubprotocols;
+    private final List<Extension> extensions;
+    private final List<Class<? extends Encoder>> encoders;
+    private final List<Class<? extends Decoder>> decoders;
+    private final Map<String,Object> userProperties = new ConcurrentHashMap<String, Object>();
+    private final Configurator configurator;
+
+
+    DefaultClientEndpointConfig(List<String> preferredSubprotocols,
+            List<Extension> extensions,
+            List<Class<? extends Encoder>> encoders,
+            List<Class<? extends Decoder>> decoders,
+            Configurator configurator) {
+        this.preferredSubprotocols = preferredSubprotocols;
+        this.extensions = extensions;
+        this.decoders = decoders;
+        this.encoders = encoders;
+        this.configurator = configurator;
+    }
+
+
+    @Override
+    public List<String> getPreferredSubprotocols() {
+        return preferredSubprotocols;
+    }
+
+
+    @Override
+    public List<Extension> getExtensions() {
+        return extensions;
+    }
+
+
+    @Override
+    public List<Class<? extends Encoder>> getEncoders() {
+        return encoders;
+    }
+
+
+    @Override
+    public List<Class<? extends Decoder>> getDecoders() {
+        return decoders;
+    }
+
+
+    @Override
+    public final Map<String, Object> getUserProperties() {
+        return userProperties;
+    }
+
+
+    @Override
+    public Configurator getConfigurator() {
+        return configurator;
+    }
+}
diff --git a/java/javax/annotation/PostConstruct.java b/java/javax/websocket/DeploymentException.java
similarity index 67%
copy from java/javax/annotation/PostConstruct.java
copy to java/javax/websocket/DeploymentException.java
index 8ad363a..1678fd0 100644
--- a/java/javax/annotation/PostConstruct.java
+++ b/java/javax/websocket/DeploymentException.java
@@ -5,27 +5,26 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.websocket;
 
+public class DeploymentException extends Exception {
 
-package javax.annotation;
+    private static final long serialVersionUID = 1L;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+    public DeploymentException(String message) {
+        super(message);
+    }
 
- at Target({ElementType.METHOD})
- at Retention(RetentionPolicy.RUNTIME)
-
-public @interface PostConstruct {
-    // No attributes
+    public DeploymentException(String message, Throwable cause) {
+        super(message, cause);
+    }
 }
diff --git a/java/javax/annotation/security/DeclareRoles.java b/java/javax/websocket/EncodeException.java
similarity index 58%
copy from java/javax/annotation/security/DeclareRoles.java
copy to java/javax/websocket/EncodeException.java
index d5d214a..fdb536a 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/java/javax/websocket/EncodeException.java
@@ -5,27 +5,34 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.websocket;
 
+public class EncodeException extends Exception {
 
-package javax.annotation.security;
+    private static final long serialVersionUID = 1L;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+    private Object object;
 
- at Target({ElementType.TYPE})
- at Retention(RetentionPolicy.RUNTIME)
+    public EncodeException(Object object, String message) {
+        super(message);
+        this.object = object;
+    }
 
-public @interface DeclareRoles {
-    public String[] value();
+    public EncodeException(Object object, String message, Throwable cause) {
+        super(message, cause);
+        this.object = object;
+    }
+
+    public Object getObject() {
+        return this.object;
+    }
 }
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java b/java/javax/websocket/Encoder.java
similarity index 53%
copy from java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
copy to java/javax/websocket/Encoder.java
index 813bea8..42a107f 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
+++ b/java/javax/websocket/Encoder.java
@@ -6,7 +6,7 @@
  * (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
+ *     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,
@@ -14,35 +14,38 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.coyote.http11.upgrade;
+package javax.websocket;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.ByteBuffer;
 
+public interface Encoder {
 
-/**
- * Allows data to be written to the upgraded connection.
- */
-public class UpgradeOutbound extends OutputStream {
+    abstract void init(EndpointConfig endpointConfig);
+
+    abstract void destroy();
+
+    interface Text<T> extends Encoder {
 
-    @Override
-    public void flush() throws IOException {
-        processor.flush();
+        String encode(T object) throws EncodeException;
     }
 
-    private final UpgradeProcessor<?> processor;
+    interface TextStream<T> extends Encoder {
 
-    public UpgradeOutbound(UpgradeProcessor<?> processor) {
-        this.processor = processor;
+        void encode(T object, Writer writer)
+                throws EncodeException, IOException;
     }
 
-    @Override
-    public void write(int b) throws IOException {
-        processor.write(b);
+    interface Binary<T> extends Encoder {
+
+        ByteBuffer encode(T object) throws EncodeException;
     }
 
-    @Override
-    public void write(byte[] b, int off, int len) throws IOException {
-        processor.write(b, off, len);
+    interface BinaryStream<T> extends Encoder {
+
+        void encode(T object, OutputStream os)
+                throws EncodeException, IOException;
     }
 }
diff --git a/java/javax/websocket/Endpoint.java b/java/javax/websocket/Endpoint.java
new file mode 100644
index 0000000..7ed2a1d
--- /dev/null
+++ b/java/javax/websocket/Endpoint.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.websocket;
+
+public abstract class Endpoint {
+
+    /**
+     * Event that is triggered when a new session starts.
+     *
+     * @param session   The new session.
+     */
+    public abstract void onOpen(Session session, EndpointConfig config);
+
+    /**
+     * Event that is triggered when a session has closed.
+     *
+     * @param session       The session
+     * @param closeReason   Why the session was closed
+     */
+    public void onClose(Session session, CloseReason closeReason) {
+        // NO-OP by default
+    }
+
+    /**
+     * Event that is triggered when a protocol error occurs.
+     *
+     * @param session   The session
+     * @param throwable The exception
+     */
+    public void onError(Session session, Throwable throwable) {
+        // NO-OP by default
+    }
+}
diff --git a/java/javax/annotation/PreDestroy.java b/java/javax/websocket/EndpointConfig.java
similarity index 67%
copy from java/javax/annotation/PreDestroy.java
copy to java/javax/websocket/EndpointConfig.java
index d5be75a..0b6c968 100644
--- a/java/javax/annotation/PreDestroy.java
+++ b/java/javax/websocket/EndpointConfig.java
@@ -5,27 +5,25 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.websocket;
 
+import java.util.List;
+import java.util.Map;
 
-package javax.annotation;
+public interface EndpointConfig {
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+    List<Class<? extends Encoder>> getEncoders();
 
- at Target({ElementType.METHOD})
- at Retention(RetentionPolicy.RUNTIME)
+    List<Class<? extends Decoder>> getDecoders();
 
-public @interface PreDestroy {
-    // No attributes
+    Map<String,Object> getUserProperties();
 }
diff --git a/java/org/apache/tomcat/util/net/SocketStatus.java b/java/javax/websocket/Extension.java
similarity index 78%
copy from java/org/apache/tomcat/util/net/SocketStatus.java
copy to java/javax/websocket/Extension.java
index bf53c2b..b95b27b 100644
--- a/java/org/apache/tomcat/util/net/SocketStatus.java
+++ b/java/javax/websocket/Extension.java
@@ -14,14 +14,16 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
+package javax.websocket;
 
-package org.apache.tomcat.util.net;
+import java.util.List;
 
-/**
- * Someone, please change the enum name.
- *
- * @author remm
- */
-public enum SocketStatus {
-    OPEN, STOP, TIMEOUT, DISCONNECT, ERROR
+public interface Extension {
+    String getName();
+    List<Parameter> getParameters();
+
+    interface Parameter {
+        String getName();
+        String getValue();
+    }
 }
diff --git a/java/javax/annotation/PreDestroy.java b/java/javax/websocket/HandshakeResponse.java
similarity index 67%
copy from java/javax/annotation/PreDestroy.java
copy to java/javax/websocket/HandshakeResponse.java
index d5be75a..807192e 100644
--- a/java/javax/annotation/PreDestroy.java
+++ b/java/javax/websocket/HandshakeResponse.java
@@ -5,27 +5,26 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.websocket;
 
+import java.util.List;
+import java.util.Map;
 
-package javax.annotation;
+public interface HandshakeResponse {
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+    /**
+     * Name of the WebSocket accept HTTP header.
+     */
+    public static final String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept";
 
- at Target({ElementType.METHOD})
- at Retention(RetentionPolicy.RUNTIME)
-
-public @interface PreDestroy {
-    // No attributes
+    Map<String,List<String>> getHeaders();
 }
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java b/java/javax/websocket/MessageHandler.java
similarity index 50%
copy from java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
copy to java/javax/websocket/MessageHandler.java
index 813bea8..2c30d99 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
+++ b/java/javax/websocket/MessageHandler.java
@@ -6,7 +6,7 @@
  * (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
+ *     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,
@@ -14,35 +14,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.coyote.http11.upgrade;
+package javax.websocket;
 
-import java.io.IOException;
-import java.io.OutputStream;
+public interface MessageHandler {
 
+    interface Partial<T> extends MessageHandler {
 
-/**
- * Allows data to be written to the upgraded connection.
- */
-public class UpgradeOutbound extends OutputStream {
-
-    @Override
-    public void flush() throws IOException {
-        processor.flush();
-    }
-
-    private final UpgradeProcessor<?> processor;
-
-    public UpgradeOutbound(UpgradeProcessor<?> processor) {
-        this.processor = processor;
+        /**
+         * Called when part of a message is available to be processed.
+         *
+         * @param messagePart   The message part
+         * @param last          <code>true</code> if this is the last part of
+         *                      this message, else <code>false</code>
+         */
+        void onMessage(T messagePart, boolean last);
     }
 
-    @Override
-    public void write(int b) throws IOException {
-        processor.write(b);
-    }
+    interface Whole<T> extends MessageHandler {
 
-    @Override
-    public void write(byte[] b, int off, int len) throws IOException {
-        processor.write(b, off, len);
+        /**
+         * Called when a whole message is available to be processed.
+         *
+         * @param message   The message
+         */
+        void onMessage(T message);
     }
 }
diff --git a/java/javax/annotation/PreDestroy.java b/java/javax/websocket/OnClose.java
similarity index 84%
copy from java/javax/annotation/PreDestroy.java
copy to java/javax/websocket/OnClose.java
index d5be75a..6ee61d3 100644
--- a/java/javax/annotation/PreDestroy.java
+++ b/java/javax/websocket/OnClose.java
@@ -5,27 +5,23 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.annotation;
+package javax.websocket;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
- at Target({ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
-
-public @interface PreDestroy {
-    // No attributes
+ at Target(ElementType.METHOD)
+public @interface OnClose {
 }
diff --git a/java/javax/annotation/PreDestroy.java b/java/javax/websocket/OnError.java
similarity index 84%
copy from java/javax/annotation/PreDestroy.java
copy to java/javax/websocket/OnError.java
index d5be75a..ce43148 100644
--- a/java/javax/annotation/PreDestroy.java
+++ b/java/javax/websocket/OnError.java
@@ -5,27 +5,23 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.annotation;
+package javax.websocket;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
- at Target({ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
-
-public @interface PreDestroy {
-    // No attributes
+ at Target(ElementType.METHOD)
+public @interface OnError {
 }
diff --git a/java/javax/annotation/security/DenyAll.java b/java/javax/websocket/OnMessage.java
similarity index 84%
copy from java/javax/annotation/security/DenyAll.java
copy to java/javax/websocket/OnMessage.java
index 6fdf829..564fa99 100644
--- a/java/javax/annotation/security/DenyAll.java
+++ b/java/javax/websocket/OnMessage.java
@@ -5,27 +5,24 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.annotation.security;
+package javax.websocket;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
- at Target({ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
-
-public @interface DenyAll {
-    // No attributes
+ at Target(ElementType.METHOD)
+public @interface OnMessage {
+    long maxMessageSize() default -1;
 }
diff --git a/java/javax/annotation/PreDestroy.java b/java/javax/websocket/OnOpen.java
similarity index 84%
copy from java/javax/annotation/PreDestroy.java
copy to java/javax/websocket/OnOpen.java
index d5be75a..9f0ea6e 100644
--- a/java/javax/annotation/PreDestroy.java
+++ b/java/javax/websocket/OnOpen.java
@@ -5,27 +5,23 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.annotation;
+package javax.websocket;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
- at Target({ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
-
-public @interface PreDestroy {
-    // No attributes
+ at Target(ElementType.METHOD)
+public @interface OnOpen {
 }
diff --git a/java/javax/annotation/security/DeclareRoles.java b/java/javax/websocket/PongMessage.java
similarity index 64%
copy from java/javax/annotation/security/DeclareRoles.java
copy to java/javax/websocket/PongMessage.java
index d5d214a..966137a 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/java/javax/websocket/PongMessage.java
@@ -5,27 +5,26 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.websocket;
 
+import java.nio.ByteBuffer;
 
-package javax.annotation.security;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
- at Target({ElementType.TYPE})
- at Retention(RetentionPolicy.RUNTIME)
-
-public @interface DeclareRoles {
-    public String[] value();
+/**
+ * Represents a WebSocket Pong message and used by message handlers to enable
+ * applications to process the response to any Pings they send.
+ */
+public interface PongMessage {
+    /**
+     * Obtain the payload of the Pong message as a ByteBuffer.
+     */
+    ByteBuffer getApplicationData();
 }
diff --git a/java/javax/websocket/RemoteEndpoint.java b/java/javax/websocket/RemoteEndpoint.java
new file mode 100644
index 0000000..ff929e9
--- /dev/null
+++ b/java/javax/websocket/RemoteEndpoint.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.websocket;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Future;
+
+
+public interface RemoteEndpoint {
+
+    interface Async extends RemoteEndpoint {
+
+        /**
+         * Obtain the timeout (in milliseconds) for sending a message
+         * asynchronously. A non-positive value means an infinite timeout. The
+         * default value is determined by
+         * {@link WebSocketContainer#getDefaultAsyncSendTimeout()}.
+         */
+        long getSendTimeout();
+
+        /**
+         * Set the timeout (in milliseconds) for sending a message asynchronously. A
+         * non-positive value means an infinite timeout. The default value is
+         * determined by {@link WebSocketContainer#getDefaultAsyncSendTimeout()}.
+         */
+        void setSendTimeout(long timeout);
+
+        /**
+         * Send the message asynchronously, using the SendHandler to signal to the
+         * client when the message has been sent.
+         * @param text          The text message to send
+         * @param completion    Used to signal to the client when the message has
+         *                      been sent
+         */
+        void sendText(String text, SendHandler completion);
+
+        /**
+         * Send the message asynchronously, using the Future to signal to the client
+         * when the message has been sent.
+         * @param text          The text message to send
+         */
+        Future<Void> sendText(String text);
+
+        /**
+         * Send the message asynchronously, using the Future to signal to the client
+         * when the message has been sent.
+         * @param data          The text message to send
+         */
+        Future<Void> sendBinary(ByteBuffer data);
+
+        /**
+         * Send the message asynchronously, using the SendHandler to signal to the
+         * client when the message has been sent.
+         * @param data          The text message to send
+         * @param completion    Used to signal to the client when the message has
+         *                      been sent
+         */
+        void sendBinary(ByteBuffer data, SendHandler completion);
+
+        Future<Void> sendObject(Object obj);
+
+        void sendObject(Object obj, SendHandler completion);
+
+    }
+
+    interface Basic extends RemoteEndpoint {
+
+        /**
+         * Send the message, blocking until the message is sent.
+         * @param text  The text message to send.
+         * @throws IOException
+         */
+        void sendText(String text) throws IOException;
+
+        /**
+         * Send the message, blocking until the message is sent.
+         * @param data  The binary message to send
+         * @throws IOException
+         */
+        void sendBinary(ByteBuffer data) throws IOException;
+
+        /**
+         * Sends part of a text message to the remote endpoint. Once the first part
+         * of a message has been sent, no other text or binary messages may be sent
+         * until all remaining parts of this message have been sent.
+         *
+         * @param fragment  The partial message to send
+         * @param isLast    <code>true</code> if this is the last part of the
+         *                  message, otherwise <code>false</code>
+         * @throws IOException
+         */
+        void sendText(String fragment, boolean isLast) throws IOException;
+
+        /**
+         * Sends part of a binary message to the remote endpoint. Once the first
+         * part of a message has been sent, no other text or binary messages may be
+         * sent until all remaining parts of this message have been sent.
+         *
+         * @param partialByte   The partial message to send
+         * @param isLast        <code>true</code> if this is the last part of the
+         *                      message, otherwise <code>false</code>
+         * @throws IOException
+         */
+        void sendBinary(ByteBuffer partialByte, boolean isLast) throws IOException;
+
+        OutputStream getSendStream() throws IOException;
+
+        Writer getSendWriter() throws IOException;
+
+        void sendObject(Object o) throws IOException, EncodeException;
+
+    }
+    /**
+     * Enable or disable the batching of outgoing messages for this endpoint. If
+     * batching is disabled when it was previously enabled then this method will
+     * block until any currently batched messages have been written.
+     *
+     * @param batchingAllowed   New setting
+     * @throws IOException      If changing the value resulted in a call to
+     *                          {@link #flushBatch()} and that call threw an
+     *                          {@link IOException}.
+     */
+    void setBatchingAllowed(boolean batchingAllowed) throws IOException;
+
+    /**
+     * Obtains the current batching status of the endpoint.
+     */
+    boolean getBatchingAllowed();
+
+    /**
+     * Flush any currently batched messages to the remote endpoint. This method
+     * will block until the flush completes.
+     */
+    void flushBatch() throws IOException;
+
+    /**
+     * Send a ping message blocking until the message has been sent. Note that
+     * if a message is in the process of being sent asynchronously, this method
+     * will block until that message and this ping has been sent.
+     *
+     * @param applicationData   The payload for the ping message
+     */
+    void sendPing(ByteBuffer applicationData)
+            throws IOException, IllegalArgumentException;
+
+    /**
+     * Send a pong message blocking until the message has been sent. Note that
+     * if a message is in the process of being sent asynchronously, this method
+     * will block until that message and this pong has been sent.
+     *
+     * @param applicationData   The payload for the pong message
+     */
+    void sendPong(ByteBuffer applicationData)
+            throws IOException, IllegalArgumentException;
+}
+
diff --git a/java/javax/annotation/PreDestroy.java b/java/javax/websocket/SendHandler.java
similarity index 67%
copy from java/javax/annotation/PreDestroy.java
copy to java/javax/websocket/SendHandler.java
index d5be75a..65b9a19 100644
--- a/java/javax/annotation/PreDestroy.java
+++ b/java/javax/websocket/SendHandler.java
@@ -5,27 +5,18 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.websocket;
 
+public interface SendHandler {
 
-package javax.annotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
- at Target({ElementType.METHOD})
- at Retention(RetentionPolicy.RUNTIME)
-
-public @interface PreDestroy {
-    // No attributes
+    void onResult(SendResult result);
 }
diff --git a/java/javax/annotation/security/DeclareRoles.java b/java/javax/websocket/SendResult.java
similarity index 62%
copy from java/javax/annotation/security/DeclareRoles.java
copy to java/javax/websocket/SendResult.java
index d5d214a..2fb6176 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/java/javax/websocket/SendResult.java
@@ -5,27 +5,35 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.websocket;
 
+public final class SendResult {
+    private Throwable exception;
+    private boolean ok = true;
 
-package javax.annotation.security;
+    public SendResult(Throwable exception) {
+        this.exception = exception;
+        this.ok = false;
+    }
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+    public SendResult() {
+        // NO-OP
+    }
 
- at Target({ElementType.TYPE})
- at Retention(RetentionPolicy.RUNTIME)
+    public Throwable getException() {
+        return exception;
+    }
 
-public @interface DeclareRoles {
-    public String[] value();
+    public boolean isOK() {
+        return ok;
+    }
 }
diff --git a/java/javax/websocket/Session.java b/java/javax/websocket/Session.java
new file mode 100644
index 0000000..d6812a4
--- /dev/null
+++ b/java/javax/websocket/Session.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.websocket;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.URI;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public interface Session extends Closeable {
+
+    /**
+     * Returns the container that created this session.
+     */
+    WebSocketContainer getContainer();
+
+    void addMessageHandler(MessageHandler listener)
+            throws IllegalStateException;
+
+    Set<MessageHandler> getMessageHandlers();
+
+    void removeMessageHandler(MessageHandler listener);
+
+    String getProtocolVersion();
+
+    String getNegotiatedSubprotocol();
+
+    List<Extension> getNegotiatedExtensions();
+
+    boolean isSecure();
+
+    boolean isOpen();
+
+    /**
+     * Get the idle timeout for this session in milliseconds. Zero or negative
+     * values indicate an infinite timeout.
+     */
+    long getMaxIdleTimeout();
+
+    /**
+     * Set the idle timeout for this session in milliseconds. Zero or negative
+     * values indicate an infinite timeout.
+     */
+    void setMaxIdleTimeout(long seconds);
+
+    /**
+     * Set the current maximum buffer size (in bytes) for binary messages.
+     */
+    void setMaxBinaryMessageBufferSize(int max);
+
+    /**
+     * Get the current maximum buffer size (in bytes) for binary messages.
+     */
+    int getMaxBinaryMessageBufferSize();
+
+    /**
+     * Set the current maximum buffer size (in characters) for text messages.
+     */
+    void setMaxTextMessageBufferSize(int max);
+
+    /**
+     * Get the current maximum buffer size (in characters) for text messages.
+     */
+    int getMaxTextMessageBufferSize();
+
+    RemoteEndpoint.Async getAsyncRemote();
+
+    RemoteEndpoint.Basic getBasicRemote();
+
+    /**
+     * Provides a unique identifier for the session. This identifier should not
+     * be relied upon to be generated from a secure random source.
+     */
+    String getId();
+
+    /**
+     * Close the connection to the remote end point using the code
+     * {@link javax.websocket.CloseReason.CloseCodes#NORMAL_CLOSURE} and an
+     * empty reason phrase.
+     *
+     * @throws IOException
+     */
+    @Override
+    void close() throws IOException;
+
+
+    /**
+     * Close the connection to the remote end point using the specified code
+     * and reason phrase.
+     *
+     * @throws IOException
+     */
+    void close(CloseReason closeStatus) throws IOException;
+
+    URI getRequestURI();
+
+    Map<String, List<String>> getRequestParameterMap();
+
+    String getQueryString();
+
+    Map<String,String> getPathParameters();
+
+    Map<String,Object> getUserProperties();
+
+    Principal getUserPrincipal();
+
+    /**
+     * Obtain the set of currently open sessions for the local endpoint that
+     * this session is associated with.
+     */
+    Set<Session> getOpenSessions();
+}
diff --git a/java/javax/annotation/security/DeclareRoles.java b/java/javax/websocket/SessionException.java
similarity index 63%
copy from java/javax/annotation/security/DeclareRoles.java
copy to java/javax/websocket/SessionException.java
index d5d214a..428b82e 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/java/javax/websocket/SessionException.java
@@ -5,27 +5,31 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.websocket;
 
+public class SessionException extends Exception {
 
-package javax.annotation.security;
+    private static final long serialVersionUID = 1L;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+    private final Session session;
 
- at Target({ElementType.TYPE})
- at Retention(RetentionPolicy.RUNTIME)
 
-public @interface DeclareRoles {
-    public String[] value();
+    public SessionException(String message, Throwable cause, Session session) {
+        super(message, cause);
+        this.session = session;
+    }
+
+
+    public Session getSession() {
+        return session;
+    }
 }
diff --git a/java/javax/websocket/WebSocketContainer.java b/java/javax/websocket/WebSocketContainer.java
new file mode 100644
index 0000000..bfd98e2
--- /dev/null
+++ b/java/javax/websocket/WebSocketContainer.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.websocket;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Set;
+
+public interface WebSocketContainer {
+
+    /**
+     * Obtain the default timeout (in milliseconds) for sending a message
+     * asynchronously. A non-positive value means an infinite timeout.
+     */
+    long getDefaultAsyncSendTimeout();
+
+    /**
+     * Set the default timeout (in milliseconds) for sending a message
+     * asynchronously. A non-positive value means an infinite timeout.
+     */
+    void setAsyncSendTimeout(long timeout);
+
+    Session connectToServer(Object endpoint, URI path)
+            throws DeploymentException, IOException;
+
+    Session connectToServer(Class<?> annotatedEndpointClass, URI path)
+            throws DeploymentException, IOException;
+
+    /**
+     * Creates a new connection to the WebSocket.
+     *
+     * @param endpoint
+     *            The endpoint instance that will handle responses from the
+     *            server
+     * @param clientEndpointConfiguration
+     *            Used to configure the new connection
+     * @param path
+     *            The full URL of the WebSocket endpoint to connect to
+     *
+     * @return The WebSocket session for the connection
+     *
+     * @throws DeploymentException  If the connection can not be established
+     */
+    Session connectToServer(Endpoint endpoint,
+            ClientEndpointConfig clientEndpointConfiguration, URI path)
+            throws DeploymentException, IOException;
+
+    /**
+     * Creates a new connection to the WebSocket.
+     *
+     * @param endpoint
+     *            An instance of this class will be created to handle responses
+     *            from the server
+     * @param clientEndpointConfiguration
+     *            Used to configure the new connection
+     * @param path
+     *            The full URL of the WebSocket endpoint to connect to
+     *
+     * @return The WebSocket session for the connection
+     *
+     * @throws DeploymentException  If the connection can not be established
+     */
+    Session connectToServer(Class<? extends Endpoint> endpoint,
+            ClientEndpointConfig clientEndpointConfiguration, URI path)
+            throws DeploymentException, IOException;
+
+    /**
+     * Get the current default session idle timeout in milliseconds. Zero or
+     * negative values indicate an infinite timeout.
+     */
+    long getDefaultMaxSessionIdleTimeout();
+
+    /**
+     * Set the current default session idle timeout in milliseconds. Zero or
+     * negative values indicate an infinite timeout.
+     */
+    void setDefaultMaxSessionIdleTimeout(long timeout);
+
+    /**
+     * Get the default maximum buffer size (in bytes) for binary messages.
+     */
+    int getDefaultMaxBinaryMessageBufferSize();
+
+    /**
+     * Set the default maximum buffer size (in bytes) for binary messages.
+     */
+    void setDefaultMaxBinaryMessageBufferSize(int max);
+
+    /**
+     * Get the default maximum buffer size (in characters) for text messages.
+     */
+    int getDefaultMaxTextMessageBufferSize();
+
+    /**
+     * Set the default maximum buffer size (in characters) for text messages.
+     */
+    void setDefaultMaxTextMessageBufferSize(int max);
+
+    /**
+     * Get the set of extensions that are supported by this WebSocket
+     * implementation.
+     */
+    Set<Extension> getInstalledExtensions();
+}
diff --git a/java/javax/websocket/server/DefaultServerEndpointConfig.java b/java/javax/websocket/server/DefaultServerEndpointConfig.java
new file mode 100644
index 0000000..79830a5
--- /dev/null
+++ b/java/javax/websocket/server/DefaultServerEndpointConfig.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.websocket.server;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+import javax.websocket.Extension;
+
+/**
+ * Provides the default configuration for WebSocket server endpoints.
+ */
+final class DefaultServerEndpointConfig implements ServerEndpointConfig {
+
+    private final Class<?> endpointClass;
+    private final String path;
+    private final List<String> subprotocols;
+    private final List<Extension> extensions;
+    private final List<Class<? extends Encoder>> encoders;
+    private final List<Class<? extends Decoder>> decoders;
+    private final Configurator serverEndpointConfigurator;
+    private final Map<String,Object> userProperties = new ConcurrentHashMap<String, Object>();
+
+    DefaultServerEndpointConfig(
+            Class<?> endpointClass, String path,
+            List<String> subprotocols, List<Extension> extensions,
+            List<Class<? extends Encoder>> encoders,
+            List<Class<? extends Decoder>> decoders,
+            Configurator serverEndpointConfigurator) {
+        this.endpointClass = endpointClass;
+        this.path = path;
+        this.subprotocols = subprotocols;
+        this.extensions = extensions;
+        this.encoders = encoders;
+        this.decoders = decoders;
+        this.serverEndpointConfigurator = serverEndpointConfigurator;
+    }
+
+    @Override
+    public Class<?> getEndpointClass() {
+        return endpointClass;
+    }
+
+    @Override
+    public List<Class<? extends Encoder>> getEncoders() {
+        return this.encoders;
+    }
+
+    @Override
+    public List<Class<? extends Decoder>> getDecoders() {
+        return this.decoders;
+    }
+
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public Configurator getConfigurator() {
+        return serverEndpointConfigurator;
+    }
+
+    @Override
+    public final Map<String, Object> getUserProperties() {
+        return userProperties;
+    }
+
+    @Override
+    public final List<String> getSubprotocols() {
+        return subprotocols;
+    }
+
+    @Override
+    public final List<Extension> getExtensions() {
+        return extensions;
+    }
+}
diff --git a/java/javax/websocket/server/HandshakeRequest.java b/java/javax/websocket/server/HandshakeRequest.java
new file mode 100644
index 0000000..23f7e48
--- /dev/null
+++ b/java/javax/websocket/server/HandshakeRequest.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.websocket.server;
+
+import java.net.URI;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents the HTTP request that asked to be upgraded to WebSocket.
+ */
+public interface HandshakeRequest {
+
+    static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key";
+    static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
+    static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version";
+    static final String SEC_WEBSOCKET_EXTENSIONS= "Sec-WebSocket-Extensions";
+
+    Map<String,List<String>> getHeaders();
+
+    Principal getUserPrincipal();
+
+    URI getRequestURI();
+
+    boolean isUserInRole(String role);
+
+    /**
+     * Get the HTTP Session object associated with this request. Object is used
+     * to avoid a direct dependency on the Servlet API.
+     */
+    Object getHttpSession();
+
+    Map<String, List<String>> getParameterMap();
+
+    String getQueryString();
+}
diff --git a/java/javax/annotation/security/DeclareRoles.java b/java/javax/websocket/server/PathParam.java
similarity index 72%
copy from java/javax/annotation/security/DeclareRoles.java
copy to java/javax/websocket/server/PathParam.java
index d5d214a..ff1d085 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/java/javax/websocket/server/PathParam.java
@@ -5,27 +5,29 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.annotation.security;
+package javax.websocket.server;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
- at Target({ElementType.TYPE})
+/**
+ * Used to annotate method parameters on POJO endpoints the the {@link
+ * ServerEndpoint} has been defined with a {@link ServerEndpoint#value()} that
+ * uses a URI template.
+ */
 @Retention(RetentionPolicy.RUNTIME)
-
-public @interface DeclareRoles {
-    public String[] value();
+ at Target(ElementType.PARAMETER)
+public @interface PathParam {
+    String value();
 }
diff --git a/java/javax/websocket/server/ServerApplicationConfig.java b/java/javax/websocket/server/ServerApplicationConfig.java
new file mode 100644
index 0000000..b91f1c4
--- /dev/null
+++ b/java/javax/websocket/server/ServerApplicationConfig.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.websocket.server;
+
+import java.util.Set;
+
+import javax.websocket.Endpoint;
+
+/**
+ * Applications may provide an implementation of this interface to filter the
+ * discovered WebSocket endpoints that are deployed. Implementations of this
+ * class will be discovered via an ServletContainerInitializer scan.
+ */
+public interface ServerApplicationConfig {
+
+    /**
+     * Enables applications to filter the discovered implementations of
+     * {@link ServerEndpointConfig}.
+     *
+     * @param scanned   The {@link Endpoint} implementations found in the
+     *                  application
+     * @return  The set of configurations for the endpoint the application
+     *              wishes to deploy
+     */
+    Set<ServerEndpointConfig> getEndpointConfigs(
+            Set<Class<? extends Endpoint>> scanned);
+
+    /**
+     * Enables applications to filter the discovered classes annotated with
+     * {@link ServerEndpoint}.
+     *
+     * @param scanned   The POJOs annotated with {@link ServerEndpoint} found in
+     *                  the application
+     * @return  The set of POJOs the application wishes to deploy
+     */
+    Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned);
+}
diff --git a/java/javax/annotation/security/DeclareRoles.java b/java/javax/websocket/server/ServerContainer.java
similarity index 60%
copy from java/javax/annotation/security/DeclareRoles.java
copy to java/javax/websocket/server/ServerContainer.java
index d5d214a..3243a07 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/java/javax/websocket/server/ServerContainer.java
@@ -5,27 +5,26 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.websocket.server;
 
+import javax.websocket.DeploymentException;
+import javax.websocket.WebSocketContainer;
 
-package javax.annotation.security;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
- at Target({ElementType.TYPE})
- at Retention(RetentionPolicy.RUNTIME)
+/**
+ * Provides the ability to deploy endpoints programmatically.
+ */
+public interface ServerContainer extends WebSocketContainer {
+    public abstract void addEndpoint(Class<?> clazz) throws DeploymentException;
 
-public @interface DeclareRoles {
-    public String[] value();
+    public abstract void addEndpoint(ServerEndpointConfig sec)
+            throws DeploymentException;
 }
diff --git a/java/javax/annotation/Resources.java b/java/javax/websocket/server/ServerEndpoint.java
similarity index 60%
copy from java/javax/annotation/Resources.java
copy to java/javax/websocket/server/ServerEndpoint.java
index 4b398f2..ae16660 100644
--- a/java/javax/annotation/Resources.java
+++ b/java/javax/websocket/server/ServerEndpoint.java
@@ -5,27 +5,40 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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 javax.annotation;
+package javax.websocket.server;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
- at Target({ElementType.TYPE})
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+
 @Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.TYPE)
+public @interface ServerEndpoint {
+
+    /**
+     * URI or URI-template that the annotated class should be mapped to.
+     */
+    String value();
+
+    String[] subprotocols() default {};
+
+    Class<? extends Decoder>[] decoders() default {};
+
+    Class<? extends Encoder>[] encoders() default {};
 
-public @interface Resources {
-    public Resource[] value();
+    public Class<? extends ServerEndpointConfig.Configurator> configurator()
+            default ServerEndpointConfig.Configurator.class;
 }
diff --git a/java/javax/websocket/server/ServerEndpointConfig.java b/java/javax/websocket/server/ServerEndpointConfig.java
new file mode 100644
index 0000000..54c9925
--- /dev/null
+++ b/java/javax/websocket/server/ServerEndpointConfig.java
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.websocket.server;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ServiceLoader;
+
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+
+/**
+ * Provides configuration information for WebSocket endpoints published to a
+ * server. Applications may provide their own implementation or use
+ * {@link Builder}.
+ */
+public interface ServerEndpointConfig extends EndpointConfig {
+
+    Class<?> getEndpointClass();
+
+    /**
+     * Returns the path at which this WebSocket server endpoint has been
+     * registered. It may be a path or a level 0 URI template.
+     */
+    String getPath();
+
+    List<String> getSubprotocols();
+
+    List<Extension> getExtensions();
+
+    Configurator getConfigurator();
+
+
+    public final class Builder {
+
+        public static Builder create(
+                Class<?> endpointClass, String path) {
+            return new Builder(endpointClass, path);
+        }
+
+
+        private final Class<?> endpointClass;
+        private final String path;
+        private List<Class<? extends Encoder>> encoders =
+                Collections.emptyList();
+        private List<Class<? extends Decoder>> decoders =
+                Collections.emptyList();
+        private List<String> subprotocols = Collections.emptyList();
+        private List<Extension> extensions = Collections.emptyList();
+        private Configurator configurator =
+                Configurator.fetchContainerDefaultConfigurator();
+
+
+        private Builder(Class<?> endpointClass,
+                String path) {
+            this.endpointClass = endpointClass;
+            this.path = path;
+        }
+
+        public ServerEndpointConfig build() {
+            return new DefaultServerEndpointConfig(endpointClass, path,
+                    subprotocols, extensions, encoders, decoders, configurator);
+        }
+
+
+        public Builder encoders(
+                List<Class<? extends Encoder>> encoders) {
+            if (encoders == null || encoders.size() == 0) {
+                this.encoders = Collections.emptyList();
+            } else {
+                this.encoders = Collections.unmodifiableList(encoders);
+            }
+            return this;
+        }
+
+
+        public Builder decoders(
+                List<Class<? extends Decoder>> decoders) {
+            if (decoders == null || decoders.size() == 0) {
+                this.decoders = Collections.emptyList();
+            } else {
+                this.decoders = Collections.unmodifiableList(decoders);
+            }
+            return this;
+        }
+
+
+        public Builder subprotocols(
+                List<String> subprotocols) {
+            if (subprotocols == null || subprotocols.size() == 0) {
+                this.subprotocols = Collections.emptyList();
+            } else {
+                this.subprotocols = Collections.unmodifiableList(subprotocols);
+            }
+            return this;
+        }
+
+
+        public Builder extensions(
+                List<Extension> extensions) {
+            if (extensions == null || extensions.size() == 0) {
+                this.extensions = Collections.emptyList();
+            } else {
+                this.extensions = Collections.unmodifiableList(extensions);
+            }
+            return this;
+        }
+
+
+        public Builder configurator(Configurator serverEndpointConfigurator) {
+            if (serverEndpointConfigurator == null) {
+                this.configurator = Configurator.fetchContainerDefaultConfigurator();
+            } else {
+                this.configurator = serverEndpointConfigurator;
+            }
+            return this;
+        }
+    }
+
+
+    public class Configurator {
+
+        private static volatile Configurator defaultImpl = null;
+        private static final Object defaultImplLock = new Object();
+
+        private static final String DEFAULT_IMPL_CLASSNAME =
+                "org.apache.tomcat.websocket.server.DefaultServerEndpointConfigurator";
+
+        static Configurator fetchContainerDefaultConfigurator() {
+            if (defaultImpl == null) {
+                synchronized (defaultImplLock) {
+                    if (defaultImpl == null) {
+                        defaultImpl = loadDefault();
+                    }
+                }
+            }
+            return defaultImpl;
+        }
+
+
+        private static Configurator loadDefault() {
+            Configurator result = null;
+
+            ServiceLoader<Configurator> serviceLoader =
+                    ServiceLoader.load(Configurator.class);
+
+            Iterator<Configurator> iter = serviceLoader.iterator();
+            while (result == null && iter.hasNext()) {
+                result = iter.next();
+            }
+
+            // Fall-back. Also used by unit tests
+            if (result == null) {
+                try {
+                    @SuppressWarnings("unchecked")
+                    Class<Configurator> clazz =
+                            (Class<Configurator>) Class.forName(
+                                    DEFAULT_IMPL_CLASSNAME);
+                    result = clazz.newInstance();
+                } catch (ClassNotFoundException e) {
+                    // No options left. Just return null.
+                } catch (InstantiationException e) {
+                    // No options left. Just return null.
+                } catch (IllegalAccessException e) {
+                    // No options left. Just return null.
+                }
+            }
+            return result;
+        }
+
+        public String getNegotiatedSubprotocol(List<String> supported,
+                List<String> requested) {
+            return fetchContainerDefaultConfigurator().getNegotiatedSubprotocol(supported, requested);
+        }
+
+        public List<Extension> getNegotiatedExtensions(List<Extension> installed,
+                List<Extension> requested) {
+            return fetchContainerDefaultConfigurator().getNegotiatedExtensions(installed, requested);
+        }
+
+        public boolean checkOrigin(String originHeaderValue) {
+            return fetchContainerDefaultConfigurator().checkOrigin(originHeaderValue);
+        }
+
+        public void modifyHandshake(ServerEndpointConfig sec,
+                HandshakeRequest request, HandshakeResponse response) {
+            fetchContainerDefaultConfigurator().modifyHandshake(sec, request, response);
+        }
+
+        public <T extends Object> T getEndpointInstance(Class<T> clazz)
+                throws InstantiationException {
+            return fetchContainerDefaultConfigurator().getEndpointInstance(
+                    clazz);
+        }
+    }
+}
diff --git a/java/org/apache/catalina/Context.java b/java/org/apache/catalina/Context.java
index c555a73..97a351c 100644
--- a/java/org/apache/catalina/Context.java
+++ b/java/org/apache/catalina/Context.java
@@ -40,6 +40,7 @@ import org.apache.catalina.deploy.LoginConfig;
 import org.apache.catalina.deploy.NamingResources;
 import org.apache.catalina.deploy.SecurityConstraint;
 import org.apache.catalina.util.CharsetMapper;
+import org.apache.tomcat.InstanceManager;
 import org.apache.tomcat.JarScanner;
 import org.apache.tomcat.util.http.mapper.Mapper;
 
@@ -61,7 +62,7 @@ import org.apache.tomcat.util.http.mapper.Mapper;
  * <p>
  *
  * @author Craig R. McClanahan
- * @version $Id: Context.java 1492415 2013-06-12 20:41:33Z markt $
+ * @version $Id: Context.java 1514663 2013-08-16 11:51:28Z markt $
  */
 
 public interface Context extends Container {
@@ -712,6 +713,16 @@ public interface Context extends Container {
      */
     public boolean getLogEffectiveWebXml();
 
+    /**
+     * Get the instance manager associated with this context.
+     */
+    public InstanceManager getInstanceManager();
+
+    /**
+     * Set the instance manager associated with this context.
+     */
+    public void setInstanceManager(InstanceManager instanceManager);
+
     // --------------------------------------------------------- Public Methods
 
 
diff --git a/java/org/apache/catalina/authenticator/SpnegoAuthenticator.java b/java/org/apache/catalina/authenticator/SpnegoAuthenticator.java
index bc1e64e..d923d35 100644
--- a/java/org/apache/catalina/authenticator/SpnegoAuthenticator.java
+++ b/java/org/apache/catalina/authenticator/SpnegoAuthenticator.java
@@ -251,14 +251,13 @@ public class SpnegoAuthenticator extends AuthenticatorBase {
                     isStoreDelegatedCredential());
         } catch (GSSException e) {
             if (log.isDebugEnabled()) {
-                log.debug(sm.getString("spnegoAuthenticator.ticketValidateFail",
-                        e));
+                log.debug(sm.getString("spnegoAuthenticator.ticketValidateFail"), e);
             }
             response.setHeader("WWW-Authenticate", "Negotiate");
             response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
             return false;
         } catch (PrivilegedActionException e) {
-            log.error(sm.getString("spnegoAuthenticator.serviceLoginFail", e));
+            log.error(sm.getString("spnegoAuthenticator.serviceLoginFail"), e);
             response.setHeader("WWW-Authenticate", "Negotiate");
             response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
             return false;
diff --git a/java/org/apache/catalina/connector/CoyoteAdapter.java b/java/org/apache/catalina/connector/CoyoteAdapter.java
index 2b7966f..7b51005 100644
--- a/java/org/apache/catalina/connector/CoyoteAdapter.java
+++ b/java/org/apache/catalina/connector/CoyoteAdapter.java
@@ -56,7 +56,7 @@ import org.apache.tomcat.util.res.StringManager;
  *
  * @author Craig R. McClanahan
  * @author Remy Maucherat
- * @version $Id: CoyoteAdapter.java 1452797 2013-03-05 14:04:57Z markt $
+ * @version $Id: CoyoteAdapter.java 1514651 2013-08-16 10:55:12Z markt $
  */
 public class CoyoteAdapter implements Adapter {
 
@@ -155,7 +155,7 @@ public class CoyoteAdapter implements Adapter {
         boolean error = false;
         boolean read = false;
         try {
-            if (status == SocketStatus.OPEN) {
+            if (status == SocketStatus.OPEN_READ) {
                 if (response.isClosed()) {
                     // The event has been closed asynchronously, so call end instead of
                     // read to cleanup the pipeline
@@ -219,7 +219,7 @@ public class CoyoteAdapter implements Adapter {
                 connector.getService().getContainer().getPipeline().getFirst().event(request, response, request.getEvent());
             }
             if (response.isClosed() || !request.isComet()) {
-                if (status==SocketStatus.OPEN &&
+                if (status==SocketStatus.OPEN_READ &&
                         request.getEvent().getEventType() != EventType.END) {
                     //CometEvent.close was called during an event other than END
                     request.getEvent().setEventType(CometEvent.EventType.END);
@@ -308,7 +308,7 @@ public class CoyoteAdapter implements Adapter {
                 if (!response.isClosed() && !response.isError()) {
                     if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
                         // Invoke a read event right away if there are available bytes
-                        if (event(req, res, SocketStatus.OPEN)) {
+                        if (event(req, res, SocketStatus.OPEN_READ)) {
                             comet = true;
                             res.action(ActionCode.COMET_BEGIN, null);
                         }
@@ -411,7 +411,7 @@ public class CoyoteAdapter implements Adapter {
                     if (!response.isClosed() && !response.isError()) {
                         if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
                             // Invoke a read event right away if there are available bytes
-                            if (event(req, res, SocketStatus.OPEN)) {
+                            if (event(req, res, SocketStatus.OPEN_READ)) {
                                 comet = true;
                                 res.action(ActionCode.COMET_BEGIN, null);
                             }
diff --git a/java/org/apache/catalina/connector/OutputBuffer.java b/java/org/apache/catalina/connector/OutputBuffer.java
index 1f68431..93e025d 100644
--- a/java/org/apache/catalina/connector/OutputBuffer.java
+++ b/java/org/apache/catalina/connector/OutputBuffer.java
@@ -24,6 +24,8 @@ import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
 import java.util.HashMap;
 
+import javax.servlet.http.HttpServletResponse;
+
 import org.apache.catalina.Globals;
 import org.apache.coyote.ActionCode;
 import org.apache.coyote.Response;
@@ -300,7 +302,12 @@ public class OutputBuffer extends Writer
             }
         }
 
-        doFlush(false);
+        if (coyoteResponse.getStatus() ==
+                HttpServletResponse.SC_SWITCHING_PROTOCOLS) {
+            doFlush(true);
+        } else {
+            doFlush(false);
+        }
         closed = true;
 
         // The request should have been completely read by the time the response
diff --git a/java/org/apache/catalina/connector/Request.java b/java/org/apache/catalina/connector/Request.java
index 7b3a6f2..2c79f00 100644
--- a/java/org/apache/catalina/connector/Request.java
+++ b/java/org/apache/catalina/connector/Request.java
@@ -22,6 +22,7 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
+import java.lang.reflect.InvocationTargetException;
 import java.nio.charset.Charset;
 import java.security.Principal;
 import java.text.SimpleDateFormat;
@@ -40,6 +41,7 @@ import java.util.TimeZone;
 import java.util.TreeMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import javax.naming.NamingException;
 import javax.security.auth.Subject;
 import javax.servlet.AsyncContext;
 import javax.servlet.DispatcherType;
@@ -74,7 +76,7 @@ import org.apache.catalina.realm.GenericPrincipal;
 import org.apache.catalina.util.ParameterMap;
 import org.apache.catalina.util.StringParser;
 import org.apache.coyote.ActionCode;
-import org.apache.coyote.http11.upgrade.UpgradeInbound;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.ExceptionUtils;
@@ -101,7 +103,7 @@ import org.apache.tomcat.util.res.StringManager;
  *
  * @author Remy Maucherat
  * @author Craig R. McClanahan
- * @version $Id: Request.java 1486942 2013-05-28 14:48:58Z markt $
+ * @version $Id: Request.java 1515817 2013-08-20 13:14:48Z markt $
  */
 
 public class Request
@@ -2819,12 +2821,16 @@ public class Request
     }
 
 
-    // --------------------------------------------------------- Upgrade Methods
+    // --------------------------------- Tomcat proprietary HTTP upgrade methods
 
-    public void doUpgrade(UpgradeInbound inbound)
+    /**
+     * @deprecated  Will be removed in Tomcat 8.0.x.
+     */
+    @Deprecated
+    public void doUpgrade(org.apache.coyote.http11.upgrade.UpgradeInbound inbound)
             throws IOException {
 
-        coyoteRequest.action(ActionCode.UPGRADE, inbound);
+        coyoteRequest.action(ActionCode.UPGRADE_TOMCAT, inbound);
 
         // Output required by RFC2616. Protocol specific headers should have
         // already been set.
@@ -2833,6 +2839,35 @@ public class Request
     }
 
 
+    // ---------------------------------- Servlet 3.1 based HTTP upgrade methods
+
+    @SuppressWarnings("unchecked")
+    public <T extends HttpUpgradeHandler> T upgrade(
+            Class<T> httpUpgradeHandlerClass) throws ServletException {
+            
+        T handler;
+        try {
+            handler = (T) context.getInstanceManager().newInstance(httpUpgradeHandlerClass);
+        } catch (InstantiationException e) {
+            throw new ServletException(e);
+        } catch (IllegalAccessException e) {
+            throw new ServletException(e);
+        } catch (InvocationTargetException e) {
+            throw new ServletException(e);
+        } catch (NamingException e) {
+            throw new ServletException(e);
+        }
+
+        coyoteRequest.action(ActionCode.UPGRADE, handler);
+
+        // Output required by RFC2616. Protocol specific headers should have
+        // already been set.
+        response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
+
+        return handler;
+    }
+
+
     // ------------------------------------------------------ Protected Methods
 
 
diff --git a/java/org/apache/catalina/connector/RequestFacade.java b/java/org/apache/catalina/connector/RequestFacade.java
index eab3168..587cb28 100644
--- a/java/org/apache/catalina/connector/RequestFacade.java
+++ b/java/org/apache/catalina/connector/RequestFacade.java
@@ -42,6 +42,7 @@ import javax.servlet.http.Part;
 import org.apache.catalina.Globals;
 import org.apache.catalina.security.SecurityUtil;
 import org.apache.coyote.http11.upgrade.UpgradeInbound;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
 import org.apache.tomcat.util.res.StringManager;
 
 /**
@@ -51,7 +52,7 @@ import org.apache.tomcat.util.res.StringManager;
  * @author Craig R. McClanahan
  * @author Remy Maucherat
  * @author Jean-Francois Arcand
- * @version $Id: RequestFacade.java 1302358 2012-03-19 10:09:12Z markt $
+ * @version $Id: RequestFacade.java 1514706 2013-08-16 13:43:23Z markt $
  */
 
 @SuppressWarnings("deprecation")
@@ -1102,4 +1103,9 @@ public class RequestFacade implements HttpServletRequest {
             throws IOException {
         request.doUpgrade(inbound);
     }
+    
+    public <T extends HttpUpgradeHandler> T upgrade(
+            Class<T> httpUpgradeHandlerClass) throws ServletException {
+        return request.upgrade(httpUpgradeHandlerClass);
+    }
 }
diff --git a/java/org/apache/catalina/core/ApplicationContext.java b/java/org/apache/catalina/core/ApplicationContext.java
index ae5ecf7..b07e954 100644
--- a/java/org/apache/catalina/core/ApplicationContext.java
+++ b/java/org/apache/catalina/core/ApplicationContext.java
@@ -5,9 +5,9 @@
  * The ASF licenses this file to You 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.
@@ -84,7 +84,7 @@ import org.apache.tomcat.util.res.StringManager;
  *
  * @author Craig R. McClanahan
  * @author Remy Maucherat
- * @version $Id: ApplicationContext.java 1493015 2013-06-14 10:00:57Z markt $
+ * @version $Id: ApplicationContext.java 1514663 2013-08-16 11:51:28Z markt $
  */
 
 public class ApplicationContext
@@ -97,7 +97,7 @@ public class ApplicationContext
 
     static {
         STRICT_SERVLET_COMPLIANCE = Globals.STRICT_SERVLET_COMPLIANCE;
-        
+
         String requireSlash = System.getProperty(
                 "org.apache.catalina.core.ApplicationContext.GET_RESOURCE_REQUIRE_SLASH");
         if (requireSlash == null) {
@@ -302,7 +302,7 @@ public class ApplicationContext
         }
     }
 
-    
+
     /**
      * Return the main path associated with this context.
      */
@@ -310,7 +310,7 @@ public class ApplicationContext
     public String getContextPath() {
         return context.getPath();
     }
-    
+
 
     /**
      * Return the value of the specified initialization parameter, or
@@ -395,7 +395,7 @@ public class ApplicationContext
         Wrapper wrapper = (Wrapper) context.findChild(name);
         if (wrapper == null)
             return (null);
-        
+
         return new ApplicationDispatcher(wrapper, null, null, null, null, name);
 
     }
@@ -444,7 +444,7 @@ public class ApplicationContext
         if (normalizedPath == null)
             return (null);
 
-        pos = normalizedPath.length(); 
+        pos = normalizedPath.length();
 
         // Use the thread local URI and mapping data
         DispatchData dd = dispatchData.get();
@@ -495,10 +495,10 @@ public class ApplicationContext
         String pathInfo = mappingData.pathInfo.toString();
 
         mappingData.recycle();
-        
+
         // Construct a RequestDispatcher to process this request
         return new ApplicationDispatcher
-            (wrapper, uriCC.toString(), wrapperPath, pathInfo, 
+            (wrapper, uriCC.toString(), wrapperPath, pathInfo,
              queryString, null);
 
     }
@@ -523,7 +523,7 @@ public class ApplicationContext
                 !path.startsWith("/") && GET_RESOURCE_REQUIRE_SLASH)
             throw new MalformedURLException(sm.getString(
                     "applicationContext.requestDispatcher.iae", path));
-        
+
         String normPath = RequestUtil.normalize(path);
         if (normPath == null)
             return (null);
@@ -724,7 +724,7 @@ public class ApplicationContext
     @Override
     @Deprecated
     public void log(Exception exception, String message) {
-        
+
         context.getLogger().error(message, exception);
 
     }
@@ -738,7 +738,7 @@ public class ApplicationContext
      */
     @Override
     public void log(String message, Throwable throwable) {
-        
+
         context.getLogger().error(message, throwable);
 
     }
@@ -893,11 +893,11 @@ public class ApplicationContext
     @Override
     public FilterRegistration.Dynamic addFilter(String filterName,
             String filterClass) throws IllegalStateException {
-        
+
         return addFilter(filterName, filterClass, null);
     }
 
-    
+
     /**
      * Add filter to context.
      * @param   filterName  Name of filter to add
@@ -915,11 +915,11 @@ public class ApplicationContext
     @Override
     public FilterRegistration.Dynamic addFilter(String filterName,
             Filter filter) throws IllegalStateException {
-        
+
         return addFilter(filterName, null, filter);
     }
 
-    
+
     /**
      * Add filter to context.
      * @param   filterName  Name of filter to add
@@ -937,13 +937,13 @@ public class ApplicationContext
     @Override
     public FilterRegistration.Dynamic addFilter(String filterName,
             Class<? extends Filter> filterClass) throws IllegalStateException {
-        
+
         return addFilter(filterName, filterClass.getName(), null);
     }
 
     private FilterRegistration.Dynamic addFilter(String filterName,
             String filterClass, Filter filter) throws IllegalStateException {
-        
+
         if (filterName == null || filterName.equals("")) {
             throw new IllegalArgumentException(sm.getString(
                     "applicationContext.invalidFilterName", filterName));
@@ -957,7 +957,7 @@ public class ApplicationContext
         }
 
         FilterDef filterDef = context.findFilterDef(filterName);
-        
+
         // Assume a 'complete' FilterRegistration is one that has a class and
         // a name
         if (filterDef == null) {
@@ -977,14 +977,15 @@ public class ApplicationContext
             filterDef.setFilterClass(filter.getClass().getName());
             filterDef.setFilter(filter);
         }
-        
+
         return new ApplicationFilterRegistration(filterDef, context);
-    } 
-    
+    }
+
     @Override
     public <T extends Filter> T createFilter(Class<T> c)
     throws ServletException {
         try {
+            @SuppressWarnings("unchecked")
             T filter = (T) context.getInstanceManager().newInstance(c.getName());
             return filter;
         } catch (IllegalAccessException e) {
@@ -1011,7 +1012,7 @@ public class ApplicationContext
         return new ApplicationFilterRegistration(filterDef, context);
     }
 
-    
+
     /**
      * Add servlet to context.
      * @param   servletName  Name of servlet to add
@@ -1029,7 +1030,7 @@ public class ApplicationContext
     @Override
     public ServletRegistration.Dynamic addServlet(String servletName,
             String servletClass) throws IllegalStateException {
-        
+
         return addServlet(servletName, servletClass, null);
     }
 
@@ -1055,7 +1056,7 @@ public class ApplicationContext
         return addServlet(servletName, null, servlet);
     }
 
-    
+
     /**
      * Add servlet to context.
      * @param   servletName  Name of servlet to add
@@ -1080,7 +1081,7 @@ public class ApplicationContext
 
     private ServletRegistration.Dynamic addServlet(String servletName,
             String servletClass, Servlet servlet) throws IllegalStateException {
-        
+
         if (servletName == null || servletName.equals("")) {
             throw new IllegalArgumentException(sm.getString(
                     "applicationContext.invalidServletName", servletName));
@@ -1092,9 +1093,9 @@ public class ApplicationContext
                     sm.getString("applicationContext.addServlet.ise",
                             getContextPath()));
         }
-        
+
         Wrapper wrapper = (Wrapper) context.findChild(servletName);
-        
+
         // Assume a 'complete' ServletRegistration is one that has a class and
         // a name
         if (wrapper == null) {
@@ -1127,6 +1128,7 @@ public class ApplicationContext
     public <T extends Servlet> T createServlet(Class<T> c)
     throws ServletException {
         try {
+            @SuppressWarnings("unchecked")
             T servlet = (T) context.getInstanceManager().newInstance(c.getName());
             context.dynamicServletCreated(servlet);
             return servlet;
@@ -1151,10 +1153,10 @@ public class ApplicationContext
         if (wrapper == null) {
             return null;
         }
-        
+
         return new ApplicationServletRegistration(wrapper, context);
     }
-    
+
 
     /**
      * By default {@link SessionTrackingMode#URL} is always supported, {@link
@@ -1171,15 +1173,15 @@ public class ApplicationContext
 
     private void populateSessionTrackingModes() {
         // URL re-writing is always enabled by default
-        defaultSessionTrackingModes = EnumSet.of(SessionTrackingMode.URL); 
+        defaultSessionTrackingModes = EnumSet.of(SessionTrackingMode.URL);
         supportedSessionTrackingModes = EnumSet.of(SessionTrackingMode.URL);
-        
+
         if (context.getCookies()) {
             defaultSessionTrackingModes.add(SessionTrackingMode.COOKIE);
             supportedSessionTrackingModes.add(SessionTrackingMode.COOKIE);
         }
 
-        // SSL not enabled by default as it can only used on its own 
+        // SSL not enabled by default as it can only used on its own
         // Context > Host > Engine > Service
         Service s = ((Engine) context.getParent().getParent()).getService();
         Connector[] connectors = s.findConnectors();
@@ -1189,7 +1191,7 @@ public class ApplicationContext
                 supportedSessionTrackingModes.add(SessionTrackingMode.SSL);
                 break;
             }
-        } 
+        }
     }
 
     /**
@@ -1226,7 +1228,7 @@ public class ApplicationContext
                     sm.getString("applicationContext.setSessionTracking.ise",
                             getContextPath()));
         }
-        
+
         // Check that only supported tracking modes have been requested
         for (SessionTrackingMode sessionTrackingMode : sessionTrackingModes) {
             if (!supportedSessionTrackingModes.contains(sessionTrackingMode)) {
@@ -1244,7 +1246,7 @@ public class ApplicationContext
                         getContextPath()));
             }
         }
-        
+
         this.sessionTrackingModes = sessionTrackingModes;
     }
 
@@ -1253,8 +1255,8 @@ public class ApplicationContext
     public boolean setInitParameter(String name, String value) {
         return parameters.putIfAbsent(name, value) == null;
     }
-    
-    
+
+
     @Override
     public void addListener(Class<? extends EventListener> listenerClass) {
         EventListener listener;
@@ -1271,7 +1273,7 @@ public class ApplicationContext
 
     @Override
     public void addListener(String className) {
-        
+
         try {
             Object obj = context.getInstanceManager().newInstance(className);
 
@@ -1305,7 +1307,7 @@ public class ApplicationContext
                     "applicationContext.addListener.iae.cnfe", className),
                     e);
         }
-        
+
     }
 
 
@@ -1325,7 +1327,7 @@ public class ApplicationContext
             context.addApplicationEventListener(t);
             match = true;
         }
-        
+
         if (t instanceof HttpSessionListener
                 || (t instanceof ServletContextListener &&
                         newServletContextListenerAllowed)) {
@@ -1334,9 +1336,9 @@ public class ApplicationContext
             context.addApplicationLifecycleListener(t);
             match = true;
         }
-        
+
         if (match) return;
-        
+
         if (t instanceof ServletContextListener) {
             throw new IllegalArgumentException(sm.getString(
                     "applicationContext.addListener.iae.sclNotAllowed",
@@ -1353,8 +1355,9 @@ public class ApplicationContext
     public <T extends EventListener> T createListener(Class<T> c)
             throws ServletException {
         try {
+            @SuppressWarnings("unchecked")
             T listener =
-                (T) context.getInstanceManager().newInstance(c.getName());
+                (T) context.getInstanceManager().newInstance(c);
             if (listener instanceof ServletContextListener ||
                     listener instanceof ServletContextAttributeListener ||
                     listener instanceof ServletRequestListener ||
@@ -1375,27 +1378,26 @@ public class ApplicationContext
             throw new ServletException(e);
         } catch (InstantiationException e) {
             throw new ServletException(e);
-        } catch (ClassNotFoundException e) {
-            throw new ServletException(e);
-        }    }
+        }
+    }
 
 
     @Override
     public void declareRoles(String... roleNames) {
-        
+
         if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
             //TODO Spec breaking enhancement to ignore this restriction
             throw new IllegalStateException(
                     sm.getString("applicationContext.addRole.ise",
                             getContextPath()));
         }
-        
+
         if (roleNames == null) {
             throw new IllegalArgumentException(
                     sm.getString("applicationContext.roles.iae",
                             getContextPath()));
         }
-        
+
         for (String role : roleNames) {
             if (role == null || "".equals(role)) {
                 throw new IllegalArgumentException(
@@ -1424,7 +1426,7 @@ public class ApplicationContext
                         new RuntimePermission("getClassLoader"));
             }
         }
-        
+
         return result;
     }
 
@@ -1445,7 +1447,7 @@ public class ApplicationContext
     public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
         Map<String, ApplicationFilterRegistration> result =
             new HashMap<String, ApplicationFilterRegistration>();
-        
+
         FilterDef[] filterDefs = context.findFilterDefs();
         for (FilterDef filterDef : filterDefs) {
             result.put(filterDef.getFilterName(),
@@ -1473,7 +1475,7 @@ public class ApplicationContext
     public Map<String, ? extends ServletRegistration> getServletRegistrations() {
         Map<String, ApplicationServletRegistration> result =
             new HashMap<String, ApplicationServletRegistration>();
-        
+
         Container[] wrappers = context.findChildren();
         for (Container wrapper : wrappers) {
             result.put(((Wrapper) wrapper).getName(),
@@ -1484,12 +1486,12 @@ public class ApplicationContext
         return result;
     }
 
-    
+
     // -------------------------------------------------------- Package Methods
     protected StandardContext getContext() {
         return this.context;
     }
-    
+
     @Deprecated
     protected Map<String,String> getReadonlyAttributes() {
         return this.readOnlyAttributes;
@@ -1513,10 +1515,10 @@ public class ApplicationContext
             String key = keys.next();
             removeAttribute(key);
         }
-        
+
     }
-    
-    
+
+
     /**
      * Return the facade associated with this ApplicationContext.
      */
@@ -1541,7 +1543,7 @@ public class ApplicationContext
     protected void setNewServletContextListenerAllowed(boolean allowed) {
         this.newServletContextListenerAllowed = allowed;
     }
-    
+
     /**
      * List resource paths (recursively), and store all of them in the given
      * Set.
@@ -1572,13 +1574,13 @@ public class ApplicationContext
      */
     private static String getJNDIUri(String hostName, String path) {
         String result;
-        
+
         if (path.startsWith("/")) {
             result = "/" + hostName + path;
         } else {
             result = "/" + hostName + "/" + path;
         }
-        
+
         return result;
     }
 
diff --git a/java/org/apache/catalina/core/ApplicationContextFacade.java b/java/org/apache/catalina/core/ApplicationContextFacade.java
index dad9905..da52ef3 100644
--- a/java/org/apache/catalina/core/ApplicationContextFacade.java
+++ b/java/org/apache/catalina/core/ApplicationContextFacade.java
@@ -55,7 +55,7 @@ import org.apache.tomcat.util.ExceptionUtils;
  *
  * @author Remy Maucherat
  * @author Jean-Francois Arcand
- * @version $Id: ApplicationContextFacade.java 1201569 2011-11-14 01:36:07Z kkolinko $
+ * @version $Id: ApplicationContextFacade.java 1511221 2013-08-07 09:23:07Z markt $
  */
 
 public class ApplicationContextFacade implements ServletContext {
@@ -448,8 +448,9 @@ public class ApplicationContextFacade implements ServletContext {
     public FilterRegistration.Dynamic addFilter(String filterName,
             Filter filter) {
         if (SecurityUtil.isPackageProtectionEnabled()) {
-            return (FilterRegistration.Dynamic) doPrivileged(
-                    "addFilter", new Class[]{String.class, Filter.class}, new Object[]{filterName, filter});
+            return (FilterRegistration.Dynamic) doPrivileged("addFilter",
+                    new Class[]{String.class, Filter.class},
+                    new Object[]{filterName, filter});
         } else {
             return context.addFilter(filterName, filter);
         }
@@ -460,8 +461,9 @@ public class ApplicationContextFacade implements ServletContext {
     public FilterRegistration.Dynamic addFilter(String filterName,
             Class<? extends Filter> filterClass) {
         if (SecurityUtil.isPackageProtectionEnabled()) {
-            return (FilterRegistration.Dynamic) doPrivileged(
-                    "addFilter", new Object[]{filterName, filterClass.getName()});
+            return (FilterRegistration.Dynamic) doPrivileged("addFilter",
+                    new Class[]{String.class, Class.class},
+                    new Object[]{filterName, filterClass});
         } else {
             return context.addFilter(filterName, filterClass);
         }
@@ -515,8 +517,9 @@ public class ApplicationContextFacade implements ServletContext {
     public ServletRegistration.Dynamic addServlet(String servletName,
             Servlet servlet) {
         if (SecurityUtil.isPackageProtectionEnabled()) {
-            return (ServletRegistration.Dynamic) doPrivileged(
-                    "addServlet", new Class[]{String.class, Servlet.class}, new Object[]{servletName, servlet});
+            return (ServletRegistration.Dynamic) doPrivileged("addServlet",
+                    new Class[]{String.class, Servlet.class},
+                    new Object[]{servletName, servlet});
         } else {
             return context.addServlet(servletName, servlet);
         }
@@ -527,8 +530,9 @@ public class ApplicationContextFacade implements ServletContext {
     public ServletRegistration.Dynamic addServlet(String servletName,
             Class<? extends Servlet> servletClass) {
         if (SecurityUtil.isPackageProtectionEnabled()) {
-            return (ServletRegistration.Dynamic) doPrivileged(
-                    "addServlet", new Object[]{servletName, servletClass.getName()});
+            return (ServletRegistration.Dynamic) doPrivileged("addServlet",
+                    new Class[]{String.class, Class.class},
+                    new Object[]{servletName, servletClass});
         } else {
             return context.addServlet(servletName, servletClass);
         }
@@ -628,7 +632,8 @@ public class ApplicationContextFacade implements ServletContext {
     public void addListener(Class<? extends EventListener> listenerClass) {
         if (SecurityUtil.isPackageProtectionEnabled()) {
             doPrivileged("addListener",
-                    new Object[]{listenerClass.getName()});
+                    new Class[]{Class.class},
+                    new Object[]{listenerClass});
         } else {
             context.addListener(listenerClass);
         }
@@ -650,7 +655,8 @@ public class ApplicationContextFacade implements ServletContext {
     public <T extends EventListener> void addListener(T t) {
         if (SecurityUtil.isPackageProtectionEnabled()) {
             doPrivileged("addListener",
-                    new Object[]{t.getClass().getName()});
+                    new Class[]{EventListener.class},
+                    new Object[]{t});
         } else {
             context.addListener(t);
         }
@@ -681,9 +687,7 @@ public class ApplicationContextFacade implements ServletContext {
     @Override
     public void declareRoles(String... roleNames) {
         if (SecurityUtil.isPackageProtectionEnabled()) {
-//FIXME
-            doPrivileged("declareRoles",
-                    new Object[]{roleNames});
+            doPrivileged("declareRoles", new Object[]{roleNames});
         } else {
             context.declareRoles(roleNames);
         }
diff --git a/java/org/apache/catalina/core/AprLifecycleListener.java b/java/org/apache/catalina/core/AprLifecycleListener.java
index bffbd1e..9401287 100644
--- a/java/org/apache/catalina/core/AprLifecycleListener.java
+++ b/java/org/apache/catalina/core/AprLifecycleListener.java
@@ -39,7 +39,7 @@ import org.apache.tomcat.util.res.StringManager;
  *
  * @author Remy Maucherat
  * @author Filip Hanik
- * @version $Id: AprLifecycleListener.java 1445210 2013-02-12 15:45:58Z markt $
+ * @version $Id: AprLifecycleListener.java 1523649 2013-09-16 13:52:15Z markt $
  * @since 4.1
  */
 
@@ -60,9 +60,9 @@ public class AprLifecycleListener
 
     protected static final int TCN_REQUIRED_MAJOR = 1;
     protected static final int TCN_REQUIRED_MINOR = 1;
-    protected static final int TCN_REQUIRED_PATCH = 24;
+    protected static final int TCN_REQUIRED_PATCH = 28;
     protected static final int TCN_RECOMMENDED_MINOR = 1;
-    protected static final int TCN_RECOMMENDED_PV = 27;
+    protected static final int TCN_RECOMMENDED_PV = 28;
 
 
     // ---------------------------------------------- Properties
diff --git a/java/org/apache/catalina/core/AsyncContextImpl.java b/java/org/apache/catalina/core/AsyncContextImpl.java
index bd08110..737b8ff 100644
--- a/java/org/apache/catalina/core/AsyncContextImpl.java
+++ b/java/org/apache/catalina/core/AsyncContextImpl.java
@@ -5,9 +5,9 @@
  * The ASF licenses this file to You 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.
@@ -53,14 +53,14 @@ import org.apache.tomcat.InstanceManager;
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.res.StringManager;
 /**
- * 
+ *
  * @author fhanik
  *
  */
 public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
-    
+
     private static final Log log = LogFactory.getLog(AsyncContextImpl.class);
-    
+
     protected static final StringManager sm =
         StringManager.getManager(Constants.Package);
 
@@ -74,7 +74,7 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
     private AsyncEvent event = null;
     private Request request;
     private volatile InstanceManager instanceManager;
-    
+
     public AsyncContextImpl(Request request) {
         if (log.isDebugEnabled()) {
             logDebug("Constructor");
@@ -132,11 +132,11 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
             }
         }
     }
-    
+
     public boolean timeout() {
         AtomicBoolean result = new AtomicBoolean();
         request.getCoyoteRequest().action(ActionCode.ASYNC_TIMEOUT, result);
-        
+
         if (result.get()) {
 
             ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
@@ -229,7 +229,7 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
                 }
             }
         };
-        
+
         this.dispatch = run;
         this.request.getCoyoteRequest().action(ActionCode.ASYNC_DISPATCH, null);
     }
@@ -255,7 +255,7 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
         Runnable wrapper = new RunnableWrapper(run, context);
         this.request.getCoyoteRequest().action(ActionCode.ASYNC_RUN, wrapper);
     }
-    
+
     @Override
     public void addListener(AsyncListener listener) {
         check();
@@ -273,6 +273,7 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
         listeners.add(wrapper);
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     public <T extends AsyncListener> T createListener(Class<T> clazz)
             throws ServletException {
@@ -300,7 +301,7 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
         }
         return listener;
     }
-    
+
     public void recycle() {
         if (log.isDebugEnabled()) {
             logDebug("recycle    ");
@@ -326,7 +327,7 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
 
     public void setStarted(Context context, ServletRequest request,
             ServletResponse response, boolean originalRequestResponse) {
-        
+
         this.request.getCoyoteRequest().action(
                 ActionCode.ASYNC_START, this);
 
@@ -335,7 +336,7 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
         this.servletResponse = response;
         this.hasOriginalRequestAndResponse = originalRequestResponse;
         this.event = new AsyncEvent(this, request, response);
-        
+
         List<AsyncListenerWrapper> listenersCopy =
             new ArrayList<AsyncListenerWrapper>();
         listenersCopy.addAll(listeners);
@@ -380,7 +381,7 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
         }
     }
 
-    
+
     @Override
     public long getTimeout() {
         check();
@@ -446,7 +447,7 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
         }
     }
 
-    
+
     private void logDebug(String method) {
         String rHashCode;
         String crHashCode;
@@ -508,7 +509,7 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
                 instanceManager = new DefaultInstanceManager(null,
                         new HashMap<String, Map<String, String>>(),
                         context,
-                        getClass().getClassLoader()); 
+                        getClass().getClassLoader());
             }
         }
         return instanceManager;
@@ -524,12 +525,12 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
     private static class DebugException extends Exception {
         private static final long serialVersionUID = 1L;
     }
-    
+
     private static class RunnableWrapper implements Runnable {
 
         private Runnable wrapped = null;
         private Context context = null;
-        
+
         public RunnableWrapper(Runnable wrapped, Context ctxt) {
             this.wrapped = wrapped;
             this.context = ctxt;
@@ -544,7 +545,7 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
             } else {
                 oldCL = Thread.currentThread().getContextClassLoader();
             }
-            
+
             try {
                 if (Globals.IS_SECURITY_ENABLED) {
                     PrivilegedAction<Void> pa = new PrivilegedSetTccl(
@@ -564,7 +565,7 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
                 }
             }
         }
-        
+
     }
 
 
diff --git a/java/org/apache/catalina/core/DefaultInstanceManager.java b/java/org/apache/catalina/core/DefaultInstanceManager.java
index 5da6d7d..1a513c3 100644
--- a/java/org/apache/catalina/core/DefaultInstanceManager.java
+++ b/java/org/apache/catalina/core/DefaultInstanceManager.java
@@ -55,7 +55,7 @@ import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.res.StringManager;
 
 /**
- * @version $Id: DefaultInstanceManager.java 1437338 2013-01-23 11:02:35Z markt $
+ * @version $Id: DefaultInstanceManager.java 1514663 2013-08-16 11:51:28Z markt $
  */
 public class DefaultInstanceManager implements InstanceManager {
 
@@ -133,6 +133,11 @@ public class DefaultInstanceManager implements InstanceManager {
     }
 
     @Override
+    public Object newInstance(Class<?> clazz) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException {
+        return newInstance(clazz.newInstance(), clazz);
+    }
+
+    @Override
     public Object newInstance(String className) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException, ClassNotFoundException {
         Class<?> clazz = loadClassMaybePrivileged(className, classLoader);
         return newInstance(clazz.newInstance(), clazz);
diff --git a/java/org/apache/catalina/core/StandardContext.java b/java/org/apache/catalina/core/StandardContext.java
index 4b2cdb5..bf4b9df 100644
--- a/java/org/apache/catalina/core/StandardContext.java
+++ b/java/org/apache/catalina/core/StandardContext.java
@@ -136,7 +136,7 @@ import org.apache.tomcat.util.scan.StandardJarScanner;
  *
  * @author Craig R. McClanahan
  * @author Remy Maucherat
- * @version $Id: StandardContext.java 1493073 2013-06-14 13:51:13Z markt $
+ * @version $Id: StandardContext.java 1514139 2013-08-15 02:37:36Z jboynes $
  */
 
 public class StandardContext extends ContainerBase
@@ -4861,7 +4861,7 @@ public class StandardContext extends ContainerBase
                 ExceptionUtils.handleThrowable(t);
                 getLogger().error
                     (sm.getString("standardContext.applicationListener",
-                                  listeners[i]), t);
+                                  listeners[i].getClassName()), t);
                 ok = false;
             }
         }
diff --git a/java/org/apache/catalina/filters/CsrfPreventionFilter.java b/java/org/apache/catalina/filters/CsrfPreventionFilter.java
index a1adeb4..bb0652c 100644
--- a/java/org/apache/catalina/filters/CsrfPreventionFilter.java
+++ b/java/org/apache/catalina/filters/CsrfPreventionFilter.java
@@ -176,6 +176,7 @@ public class CsrfPreventionFilter extends FilterBase {
 
             HttpSession session = req.getSession(false);
 
+            @SuppressWarnings("unchecked")
             LruCache<String> nonceCache = (session == null) ? null
                     : (LruCache<String>) session.getAttribute(
                             Constants.CSRF_NONCE_SESSION_ATTR_NAME);
diff --git a/java/org/apache/catalina/ha/context/ReplicatedContext.java b/java/org/apache/catalina/ha/context/ReplicatedContext.java
index bea1cc0..09c7f41 100644
--- a/java/org/apache/catalina/ha/context/ReplicatedContext.java
+++ b/java/org/apache/catalina/ha/context/ReplicatedContext.java
@@ -5,9 +5,9 @@
  * The ASF licenses this file to You 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.
@@ -45,7 +45,7 @@ public class ReplicatedContext extends StandardContext implements MapOwner {
     private int mapSendOptions = Channel.SEND_OPTIONS_DEFAULT;
     private static final Log log = LogFactory.getLog( ReplicatedContext.class );
     protected static long DEFAULT_REPL_TIMEOUT = 15000;//15 seconds
-    
+
     /**
      * Start this component and implement the requirements
      * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
@@ -53,6 +53,7 @@ public class ReplicatedContext extends StandardContext implements MapOwner {
      * @exception LifecycleException if this component detects a fatal error
      *  that prevents this component from being used
      */
+    @SuppressWarnings("unchecked")
     @Override
     protected synchronized void startInternal() throws LifecycleException {
 
@@ -72,7 +73,7 @@ public class ReplicatedContext extends StandardContext implements MapOwner {
             throw new LifecycleException("Failed to start ReplicatedContext",x);
         }
     }
-    
+
     /**
      * Stop this component and implement the requirements
      * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
@@ -82,7 +83,7 @@ public class ReplicatedContext extends StandardContext implements MapOwner {
      */
     @Override
     protected synchronized void stopInternal() throws LifecycleException {
-        
+
         super.stopInternal();
 
         AbstractMap<String,Object> map =
@@ -100,7 +101,7 @@ public class ReplicatedContext extends StandardContext implements MapOwner {
     public int getMapSendOptions() {
         return mapSendOptions;
     }
-    
+
     public ClassLoader[] getClassLoaders() {
         Loader loader = null;
         ClassLoader classLoader = null;
@@ -113,7 +114,7 @@ public class ReplicatedContext extends StandardContext implements MapOwner {
             return new ClassLoader[] {classLoader,Thread.currentThread().getContextClassLoader()};
         }
     }
-    
+
     @Override
     public ServletContext getServletContext() {
         if (context == null) {
@@ -126,38 +127,38 @@ public class ReplicatedContext extends StandardContext implements MapOwner {
 
     }
 
-    
+
     protected static class ReplApplContext extends ApplicationContext {
         protected ConcurrentHashMap<String, Object> tomcatAttributes =
             new ConcurrentHashMap<String, Object>();
-        
+
         public ReplApplContext(ReplicatedContext context) {
             super(context);
         }
-        
+
         protected ReplicatedContext getParent() {
             return (ReplicatedContext)getContext();
         }
-        
+
         @Override
         protected ServletContext getFacade() {
              return super.getFacade();
         }
-        
+
         public AbstractMap<String,Object> getAttributeMap() {
             return (AbstractMap<String,Object>)this.attributes;
         }
         public void setAttributeMap(AbstractMap<String,Object> map) {
             this.attributes = map;
         }
-        
+
         @Override
         public void removeAttribute(String name) {
             tomcatAttributes.remove(name);
             //do nothing
             super.removeAttribute(name);
         }
-        
+
         @Override
         public void setAttribute(String name, Object value) {
             if ( (!getParent().getState().isAvailable()) || "org.apache.jasper.runtime.JspApplicationContextImpl".equals(name) ){
@@ -165,7 +166,7 @@ public class ReplicatedContext extends StandardContext implements MapOwner {
             } else
                 super.setAttribute(name,value);
         }
-        
+
         @Override
         public Object getAttribute(String name) {
             Object obj = tomcatAttributes.get(name);
@@ -175,7 +176,7 @@ public class ReplicatedContext extends StandardContext implements MapOwner {
                 return obj;
             }
         }
-        
+
         @SuppressWarnings("unchecked")
         @Override
         public Enumeration<String> getAttributeNames() {
@@ -209,7 +210,7 @@ public class ReplicatedContext extends StandardContext implements MapOwner {
 
         }
     }
-    
+
     @Override
     public void objectMadePrimay(Object key, Object value) {
         //noop
diff --git a/java/org/apache/catalina/ha/deploy/FileMessageFactory.java b/java/org/apache/catalina/ha/deploy/FileMessageFactory.java
index d9bd91a..cf0ffe3 100644
--- a/java/org/apache/catalina/ha/deploy/FileMessageFactory.java
+++ b/java/org/apache/catalina/ha/deploy/FileMessageFactory.java
@@ -376,6 +376,10 @@ public class FileMessageFactory {
         FileMessageFactory write = getInstance(new File(args[1]), true);
         FileMessage msg = new FileMessage(null, args[0], args[0]);
         msg = read.readMessage(msg);
+        if (msg == null) {
+            System.out.println("Empty input file : " + args[0]);
+            return;
+        }
         System.out.println("Expecting to write " + msg.getTotalNrOfMsgs()
                 + " messages.");
         int cnt = 0;
diff --git a/java/org/apache/catalina/ha/session/BackupManager.java b/java/org/apache/catalina/ha/session/BackupManager.java
index 00844e2..c2d8a99 100644
--- a/java/org/apache/catalina/ha/session/BackupManager.java
+++ b/java/org/apache/catalina/ha/session/BackupManager.java
@@ -5,9 +5,9 @@
  * The ASF licenses this file to You 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.
@@ -42,7 +42,7 @@ import org.apache.tomcat.util.res.StringManager;
 public class BackupManager extends ClusterManagerBase
         implements MapOwner, DistributedManager {
 
-    private static final Log log = LogFactory.getLog(BackupManager.class);
+    private final Log log = LogFactory.getLog(BackupManager.class);
 
     /**
      * The string manager for this package.
@@ -53,14 +53,14 @@ public class BackupManager extends ClusterManagerBase
 
     /** Set to true if we don't want the sessions to expire on shutdown */
     protected boolean mExpireSessionsOnShutdown = true;
-    
+
     /**
      * The name of this manager
      */
     protected String name;
 
     /**
-     * 
+     *
      */
     private int mapSendOptions = Channel.SEND_OPTIONS_SYNCHRONIZED_ACK|Channel.SEND_OPTIONS_USE_ACK;
 
@@ -70,6 +70,11 @@ public class BackupManager extends ClusterManagerBase
     private long rpcTimeout = DEFAULT_REPL_TIMEOUT;
 
     /**
+     * Flag for whether to terminate this map that failed to start.
+     */
+    private boolean terminateOnStartFailure = false;
+
+    /**
      * Constructor, just calls super()
      *
      */
@@ -79,7 +84,7 @@ public class BackupManager extends ClusterManagerBase
 
 
 //******************************************************************************/
-//      ClusterManager Interface     
+//      ClusterManager Interface
 //******************************************************************************/
 
     @Override
@@ -125,7 +130,7 @@ public class BackupManager extends ClusterManagerBase
     public Session createEmptySession() {
         return new DeltaSession(this);
     }
-    
+
 
     @Override
     public String getName() {
@@ -140,13 +145,14 @@ public class BackupManager extends ClusterManagerBase
      * Starts the cluster communication channel, this will connect with the
      * other nodes in the cluster, and request the current session state to be
      * transferred to this node.
-     * 
+     *
      * @exception LifecycleException if this component detects a fatal error
      *  that prevents this component from being used
      */
+    @SuppressWarnings("unchecked")
     @Override
     protected synchronized void startInternal() throws LifecycleException {
-        
+
         super.startInternal();
 
         try {
@@ -164,7 +170,8 @@ public class BackupManager extends ClusterManagerBase
                                                           cluster.getChannel(),
                                                           rpcTimeout,
                                                           getMapName(),
-                                                          getClassLoaders());
+                                                          getClassLoaders(),
+                                                          terminateOnStartFailure);
             map.setChannelSendOptions(mapSendOptions);
             this.sessions = map;
         }  catch ( Exception x ) {
@@ -173,7 +180,7 @@ public class BackupManager extends ClusterManagerBase
         }
         setState(LifecycleState.STARTING);
     }
-    
+
     public String getMapName() {
         String name = cluster.getManagerName(getName(),this)+"-"+"map";
         if ( log.isDebugEnabled() ) log.debug("Backup manager, Setting map name to:"+name);
@@ -184,7 +191,7 @@ public class BackupManager extends ClusterManagerBase
     /**
      * Stop this component and implement the requirements
      * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
-     * 
+     *
      * This will disconnect the cluster communication channel and stop the
      * listener thread.
      *
@@ -234,11 +241,19 @@ public class BackupManager extends ClusterManagerBase
         return rpcTimeout;
     }
 
+    public void setTerminateOnStartFailure(boolean terminateOnStartFailure) {
+        this.terminateOnStartFailure = terminateOnStartFailure;
+    }
+
+    public boolean isTerminateOnStartFailure() {
+        return terminateOnStartFailure;
+    }
+
     @Override
     public String[] getInvalidatedSessions() {
         return new String[0];
     }
-    
+
     @Override
     public ClusterManager cloneFromTemplate() {
         BackupManager result = new BackupManager();
@@ -246,6 +261,7 @@ public class BackupManager extends ClusterManagerBase
         result.mExpireSessionsOnShutdown = mExpireSessionsOnShutdown;
         result.mapSendOptions = mapSendOptions;
         result.rpcTimeout = rpcTimeout;
+        result.terminateOnStartFailure = terminateOnStartFailure;
         return result;
     }
 
diff --git a/java/org/apache/catalina/ha/session/DeltaManager.java b/java/org/apache/catalina/ha/session/DeltaManager.java
index bf709b6..aa89532 100644
--- a/java/org/apache/catalina/ha/session/DeltaManager.java
+++ b/java/org/apache/catalina/ha/session/DeltaManager.java
@@ -62,13 +62,13 @@ import org.apache.tomcat.util.res.StringManager;
  * @author Craig R. McClanahan
  * @author Jean-Francois Arcand
  * @author Peter Rossbach
- * @version $Id: DeltaManager.java 1436252 2013-01-21 10:06:33Z kfujino $
+ * @version $Id: DeltaManager.java 1525831 2013-09-24 10:22:24Z kfujino $
  */
 
 public class DeltaManager extends ClusterManagerBase{
 
     // ---------------------------------------------------- Security Classes
-    public static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(DeltaManager.class);
+    public final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(DeltaManager.class);
 
     /**
      * The string manager for this package.
diff --git a/java/org/apache/catalina/ha/session/DeltaSession.java b/java/org/apache/catalina/ha/session/DeltaSession.java
index 1dafdb8..accb407 100644
--- a/java/org/apache/catalina/ha/session/DeltaSession.java
+++ b/java/org/apache/catalina/ha/session/DeltaSession.java
@@ -53,7 +53,7 @@ import org.apache.tomcat.util.res.StringManager;
  * track of deltas during a request.
  *
  * @author Filip Hanik
- * @version $Id: DeltaSession.java 1327623 2012-04-18 18:46:41Z kkolinko $
+ * @version $Id: DeltaSession.java 1520358 2013-09-05 16:13:06Z markt $
  */
 
 public class DeltaSession extends StandardSession implements Externalizable,ClusterSession,ReplicatedMapEntry {
@@ -385,12 +385,12 @@ public class DeltaSession extends StandardSession implements Externalizable,Clus
      */
     @Override
     public boolean isValid() {
-        if (this.expiring) {
-            return true;
-        }
         if (!this.isValid) {
             return false;
         }
+        if (this.expiring) {
+            return true;
+        }
         if (ACTIVITY_CHECK && accessCount.get() > 0) {
             return true;
         }
@@ -445,30 +445,49 @@ public class DeltaSession extends StandardSession implements Externalizable,Clus
     }
 
     public void expire(boolean notify, boolean notifyCluster) {
-        if (expiring)
+
+        // Check to see if session has already been invalidated.
+        // Do not check expiring at this point as expire should not return until
+        // isValid is false
+        if (!isValid)
             return;
-        String expiredId = getIdInternal();
-
-        if(notifyCluster && expiredId != null && manager != null &&
-           manager instanceof DeltaManager) {
-            DeltaManager dmanager = (DeltaManager)manager;
-            CatalinaCluster cluster = dmanager.getCluster();
-            ClusterMessage msg = dmanager.requestCompleted(expiredId, true);
-            if (msg != null) {
-                cluster.send(msg);
+
+        synchronized (this) {
+            // Check again, now we are inside the sync so this code only runs once
+            // Double check locking - isValid needs to be volatile
+            if (!isValid)
+                return;
+
+            if (manager == null)
+                return;
+
+            // Mark this session as "being expired". The flag will be unset in
+            // the call to super.expire(notify)
+            expiring = true;
+
+            String expiredId = getIdInternal();
+
+            if(notifyCluster && expiredId != null &&
+                    manager instanceof DeltaManager) {
+                DeltaManager dmanager = (DeltaManager)manager;
+                CatalinaCluster cluster = dmanager.getCluster();
+                ClusterMessage msg = dmanager.requestCompleted(expiredId, true);
+                if (msg != null) {
+                    cluster.send(msg);
+                }
             }
-        }
 
-        super.expire(notify);
+            super.expire(notify);
 
-        if (notifyCluster) {
-            if (log.isDebugEnabled())
-                log.debug(sm.getString("deltaSession.notifying",
-                                       ((ClusterManager)manager).getName(), 
-                                       Boolean.valueOf(isPrimarySession()), 
-                                       expiredId));
-            if ( manager instanceof DeltaManager ) {
-                ( (DeltaManager) manager).sessionExpired(expiredId);
+            if (notifyCluster) {
+                if (log.isDebugEnabled())
+                    log.debug(sm.getString("deltaSession.notifying",
+                                           ((ClusterManager)manager).getName(),
+                                           Boolean.valueOf(isPrimarySession()),
+                                           expiredId));
+                if ( manager instanceof DeltaManager ) {
+                    ( (DeltaManager) manager).sessionExpired(expiredId);
+                }
             }
         }
     }
diff --git a/java/org/apache/catalina/ha/session/SessionIDMessage.java b/java/org/apache/catalina/ha/session/SessionIDMessage.java
index 6a1b9e8..53b38f1 100644
--- a/java/org/apache/catalina/ha/session/SessionIDMessage.java
+++ b/java/org/apache/catalina/ha/session/SessionIDMessage.java
@@ -20,12 +20,13 @@ import org.apache.catalina.ha.ClusterMessageBase;
 
 /**
  * Session id change cluster message
- * 
+ *
  * @author Peter Rossbach
- * 
- * @version $Id: SessionIDMessage.java 1463105 2013-04-01 07:38:26Z kfujino $
+ *
+ * @version $Id: SessionIDMessage.java 1511932 2013-08-08 18:38:34Z markt $
  * @deprecated Will be removed in Tomcat 8.0.x
  */
+ at Deprecated
 public class SessionIDMessage extends ClusterMessageBase {
 
     private static final long serialVersionUID = 1L;
@@ -66,7 +67,7 @@ public class SessionIDMessage extends ClusterMessageBase {
     public void setHost(String host) {
         this.host = host;
     }
-    
+
     /**
      * @return Returns the context name.
      */
@@ -94,7 +95,7 @@ public class SessionIDMessage extends ClusterMessageBase {
         this.messageNumber = messageNumber;
     }
 
-    
+
     /**
      * @return Returns the backupSessionID.
      */
diff --git a/java/org/apache/catalina/ha/session/mbeans-descriptors.xml b/java/org/apache/catalina/ha/session/mbeans-descriptors.xml
index ef91fdb..58358c5 100644
--- a/java/org/apache/catalina/ha/session/mbeans-descriptors.xml
+++ b/java/org/apache/catalina/ha/session/mbeans-descriptors.xml
@@ -542,6 +542,11 @@
       description="Timeout for RPC messages, how long we will wait for a reply"
       type="long"/>
     <attribute
+      name="terminateOnStartFailure"
+      description="Flag for whether to terminate this map that failed to start."
+      is="true"
+      type="boolean"/>
+    <attribute
       name="secureRandomAlgorithm"
       description="The secure random number generator algorithm name"
       type="java.lang.String"/>
diff --git a/java/org/apache/catalina/ha/tcp/SimpleTcpCluster.java b/java/org/apache/catalina/ha/tcp/SimpleTcpCluster.java
index 274b116..84cf06b 100644
--- a/java/org/apache/catalina/ha/tcp/SimpleTcpCluster.java
+++ b/java/org/apache/catalina/ha/tcp/SimpleTcpCluster.java
@@ -73,7 +73,7 @@ import org.apache.tomcat.util.res.StringManager;
  * @author Filip Hanik
  * @author Remy Maucherat
  * @author Peter Rossbach
- * @version $Id: SimpleTcpCluster.java 1463105 2013-04-01 07:38:26Z kfujino $
+ * @version $Id: SimpleTcpCluster.java 1505638 2013-07-22 09:28:04Z kfujino $
  */
 public class SimpleTcpCluster extends LifecycleMBeanBase
         implements CatalinaCluster, LifecycleListener, IDynamicProperty,
@@ -1032,6 +1032,7 @@ public class SimpleTcpCluster extends LifecycleMBeanBase
     }
 
     private void unregisterMember(Member member) {
+        if (member == null) return;
         ObjectName oname = memberOnameMap.remove(member);
         if (oname != null) {
             unregister(oname);
diff --git a/java/org/apache/catalina/manager/HTMLManagerServlet.java b/java/org/apache/catalina/manager/HTMLManagerServlet.java
index 8ea1a04..ced3428 100644
--- a/java/org/apache/catalina/manager/HTMLManagerServlet.java
+++ b/java/org/apache/catalina/manager/HTMLManagerServlet.java
@@ -77,7 +77,7 @@ import org.apache.tomcat.util.res.StringManager;
 * @author Bip Thelin
 * @author Malcolm Edgar
 * @author Glenn L. Nielsen
-* @version $Id: HTMLManagerServlet.java 1428969 2013-01-04 16:45:13Z kkolinko $
+* @version $Id: HTMLManagerServlet.java 1516711 2013-08-23 06:50:16Z violetagg $
 * @see ManagerServlet
 */
 
@@ -116,7 +116,8 @@ public final class HTMLManagerServlet extends ManagerServlet {
                       HttpServletResponse response)
         throws IOException, ServletException {
 
-        StringManager smClient = getStringManager(request);
+        StringManager smClient = StringManager.getManager(
+                Constants.Package, request.getLocales());
         
         // Identify the request parameters that we need
         // By obtaining the command from the pathInfo, per-command security can
@@ -175,7 +176,8 @@ public final class HTMLManagerServlet extends ManagerServlet {
                       HttpServletResponse response)
         throws IOException, ServletException {
 
-        StringManager smClient = getStringManager(request);
+        StringManager smClient = StringManager.getManager(
+                Constants.Package, request.getLocales());
 
         // Identify the request parameters that we need
         // By obtaining the command from the pathInfo, per-command security can
@@ -1319,6 +1321,8 @@ public final class HTMLManagerServlet extends ManagerServlet {
         "</tr>\n" +
         "</table>\n" +
         "</form>\n" +
+        "</td>\n" +
+        "</tr>\n" +
         "</table>\n" +
         "<br>\n" +
         "\n";
diff --git a/java/org/apache/catalina/manager/ManagerServlet.java b/java/org/apache/catalina/manager/ManagerServlet.java
index a511d8c..dde3958 100644
--- a/java/org/apache/catalina/manager/ManagerServlet.java
+++ b/java/org/apache/catalina/manager/ManagerServlet.java
@@ -148,7 +148,7 @@ import org.apache.tomcat.util.res.StringManager;
  *
  * @author Craig R. McClanahan
  * @author Remy Maucherat
- * @version $Id: ManagerServlet.java 1390886 2012-09-27 08:24:49Z markt $
+ * @version $Id: ManagerServlet.java 1514496 2013-08-15 21:11:58Z markt $
  */
 
 public class ManagerServlet extends HttpServlet implements ContainerServlet {
@@ -319,7 +319,8 @@ public class ManagerServlet extends HttpServlet implements ContainerServlet {
                       HttpServletResponse response)
         throws IOException, ServletException {
 
-        StringManager smClient = getStringManager(request);
+        StringManager smClient = StringManager.getManager(
+                Constants.Package, request.getLocales());
 
         // Identify the request parameters that we need
         String command = request.getPathInfo();
@@ -406,7 +407,8 @@ public class ManagerServlet extends HttpServlet implements ContainerServlet {
                       HttpServletResponse response)
         throws IOException, ServletException {
 
-        StringManager smClient = getStringManager(request);
+        StringManager smClient = StringManager.getManager(
+                Constants.Package, request.getLocales());
 
         // Identify the request parameters that we need
         String command = request.getPathInfo();
@@ -1575,6 +1577,11 @@ public class ManagerServlet extends HttpServlet implements ContainerServlet {
     }
 
 
+    /**
+     * @deprecated Use {@link StringManager#getManager(String, Enumeration)}.
+     *             This method will be removed in Tomcat 8.
+     */
+    @Deprecated
     protected StringManager getStringManager(HttpServletRequest req) {
         Enumeration<Locale> requestedLocales = req.getLocales();
         while (requestedLocales.hasMoreElements()) {
diff --git a/java/org/apache/catalina/manager/host/HTMLHostManagerServlet.java b/java/org/apache/catalina/manager/host/HTMLHostManagerServlet.java
index 788a46f..c1f4153 100644
--- a/java/org/apache/catalina/manager/host/HTMLHostManagerServlet.java
+++ b/java/org/apache/catalina/manager/host/HTMLHostManagerServlet.java
@@ -56,7 +56,7 @@ import org.apache.tomcat.util.res.StringManager;
 * @author Malcolm Edgar
 * @author Glenn L. Nielsen
 * @author Peter Rossbach
-* @version $Id: HTMLHostManagerServlet.java 1086992 2011-03-30 15:41:02Z markt $
+* @version $Id: HTMLHostManagerServlet.java 1514496 2013-08-15 21:11:58Z markt $
 * @see org.apache.catalina.manager.ManagerServlet
 */
 
@@ -80,7 +80,8 @@ public final class HTMLHostManagerServlet extends HostManagerServlet {
                       HttpServletResponse response)
         throws IOException, ServletException {
 
-        StringManager smClient = getStringManager(request);
+        StringManager smClient = StringManager.getManager(
+                Constants.Package, request.getLocales());
 
         // Identify the request parameters that we need
         String command = request.getPathInfo();
@@ -120,7 +121,8 @@ public final class HTMLHostManagerServlet extends HostManagerServlet {
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
 
-        StringManager smClient = getStringManager(request);
+        StringManager smClient = StringManager.getManager(
+                Constants.Package, request.getLocales());
 
         // Identify the request parameters that we need
         String command = request.getPathInfo();
diff --git a/java/org/apache/catalina/manager/host/HostManagerServlet.java b/java/org/apache/catalina/manager/host/HostManagerServlet.java
index 405c1bd..545ccb5 100644
--- a/java/org/apache/catalina/manager/host/HostManagerServlet.java
+++ b/java/org/apache/catalina/manager/host/HostManagerServlet.java
@@ -91,7 +91,7 @@ import org.apache.tomcat.util.res.StringManager;
  *
  * @author Craig R. McClanahan
  * @author Remy Maucherat
- * @version $Id: HostManagerServlet.java 1162172 2011-08-26 17:12:33Z markt $
+ * @version $Id: HostManagerServlet.java 1514496 2013-08-15 21:11:58Z markt $
  */
 
 public class HostManagerServlet
@@ -212,7 +212,8 @@ public class HostManagerServlet
                       HttpServletResponse response)
         throws IOException, ServletException {
 
-        StringManager smClient = getStringManager(request);
+        StringManager smClient = StringManager.getManager(
+                Constants.Package, request.getLocales());
 
         // Identify the request parameters that we need
         String command = request.getPathInfo();
@@ -709,6 +710,11 @@ public class HostManagerServlet
     }
 
 
+    /**
+     * @deprecated Use {@link StringManager#getManager(String, Enumeration)}.
+     *             This method will be removed in Tomcat 8.
+     */
+    @Deprecated
     protected StringManager getStringManager(HttpServletRequest req) {
         Enumeration<Locale> requestedLocales = req.getLocales();
         while (requestedLocales.hasMoreElements()) {
diff --git a/java/org/apache/catalina/mbeans/MBeanFactory.java b/java/org/apache/catalina/mbeans/MBeanFactory.java
index 6d729bb..335b800 100644
--- a/java/org/apache/catalina/mbeans/MBeanFactory.java
+++ b/java/org/apache/catalina/mbeans/MBeanFactory.java
@@ -57,7 +57,7 @@ import org.apache.catalina.valves.ValveBase;
  * <code>org.apache.catalina.core.StandardServer</code> component.</p>
  *
  * @author Amy Roh
- * @version $Id: MBeanFactory.java 1361764 2012-07-15 19:14:11Z markt $
+ * @version $Id: MBeanFactory.java 1515583 2013-08-19 20:11:47Z markt $
  */
 
 public class MBeanFactory {
@@ -78,8 +78,6 @@ public class MBeanFactory {
      * Construct a <code>ModelMBean</code> with default
      * <code>ModelMBeanInfo</code> information.
      *
-     * @exception javax.management.MBeanException if the initializer of an
-     *  object throws an exception
      * @exception javax.management.RuntimeOperationsException if an
      *  IllegalArgumentException occurs
      */
diff --git a/java/org/apache/catalina/realm/JAASRealm.java b/java/org/apache/catalina/realm/JAASRealm.java
index 8093fa8..0d55368 100644
--- a/java/org/apache/catalina/realm/JAASRealm.java
+++ b/java/org/apache/catalina/realm/JAASRealm.java
@@ -5,9 +5,9 @@
  * The ASF licenses this file to You 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.
@@ -95,7 +95,7 @@ import org.apache.tomcat.util.ExceptionUtils;
  *     application name used to obtain the JAAS LoginContext ("Catalina" in
  *     a default installation). Tomcat must be able to find an application
  *     with this name in the JAAS configuration file. Here is a hypothetical
- *     JAAS configuration file entry for a database-oriented login module that uses 
+ *     JAAS configuration file entry for a database-oriented login module that uses
  *     a Tomcat-managed JNDI database resource:
  *     <blockquote><pre>Catalina {
 org.foobar.auth.DatabaseLoginModule REQUIRED
@@ -114,17 +114,17 @@ org.foobar.auth.DatabaseLoginModule REQUIRED
 <blockquote><code>CATALINA_OPTS="-Djava.security.auth.login.config=$CATALINA_HOME/conf/jaas.config"</code></blockquote>
  * </li>
  * <li>As part of the login process, JAASRealm registers its own <code>CallbackHandler</code>,
- *     called (unsurprisingly) <code>JAASCallbackHandler</code>. This handler supplies the 
+ *     called (unsurprisingly) <code>JAASCallbackHandler</code>. This handler supplies the
  *     HTTP requests's username and credentials to the user-supplied <code>LoginModule</code></li>
  * <li>As with other <code>Realm</code> implementations, digested passwords are supported if
- *     the <code><Realm></code> element in <code>server.xml</code> contains a 
+ *     the <code><Realm></code> element in <code>server.xml</code> contains a
  *     <code>digest</code> attribute; <code>JAASCallbackHandler</code> will digest the password
- *     prior to passing it back to the <code>LoginModule</code></li>  
+ *     prior to passing it back to the <code>LoginModule</code></li>
 * </ul>
 *
 * @author Craig R. McClanahan
 * @author Yoav Shapira
- * @version $Id: JAASRealm.java 1498501 2013-07-01 15:00:34Z markt $
+ * @version $Id: JAASRealm.java 1511932 2013-08-08 18:38:34Z markt $
  */
 
 public class JAASRealm extends RealmBase {
@@ -206,7 +206,7 @@ public class JAASRealm extends RealmBase {
     public void setAppName(String name) {
         appName = name;
     }
-    
+
     /**
      * getter for the <code>appName</code> member variable
      */
@@ -233,7 +233,7 @@ public class JAASRealm extends RealmBase {
      */
     public boolean isUseContextClassLoader() {
         return useContextClassLoader;
-    } 
+    }
 
     @Override
     public void setContainer(Container container) {
@@ -257,11 +257,11 @@ public class JAASRealm extends RealmBase {
       * that represent security roles.
       */
      protected String roleClassNames = null;
-     
+
      public String getRoleClassNames() {
          return (this.roleClassNames);
      }
-     
+
      /**
       * Sets the list of comma-delimited classes that represent roles. The
       * classes in the list must implement <code>java.security.Principal</code>.
@@ -271,15 +271,15 @@ public class JAASRealm extends RealmBase {
      public void setRoleClassNames(String roleClassNames) {
          this.roleClassNames = roleClassNames;
      }
-     
+
      /**
       * Parses a comma-delimited list of class names, and store the class names
       * in the provided List. Each class must implement
       * <code>java.security.Principal</code>.
-      * 
+      *
       * @param classNamesString a comma-delimited list of fully qualified class names.
       * @param classNamesList the list in which the class names will be stored.
-      *        The list is cleared before being populated. 
+      *        The list is cleared before being populated.
       */
      protected void parseClassNames(String classNamesString, List<String> classNamesList) {
          classNamesList.clear();
@@ -291,7 +291,7 @@ public class JAASRealm extends RealmBase {
 
          String[] classNames = classNamesString.split("[ ]*,[ ]*");
          for (int i=0; i<classNames.length; i++) {
-             if (classNames[i].length()==0) continue;        
+             if (classNames[i].length()==0) continue;
              try {
                  Class<?> principalClass = Class.forName(classNames[i], false,
                          loader);
@@ -305,18 +305,18 @@ public class JAASRealm extends RealmBase {
                  log.error("Class "+classNames[i]+" not found! Class not added.");
              }
          }
-     }     
-     
+     }
+
      /**
       * Comma-delimited list of <code>java.security.Principal</code> classes
       * that represent individual users.
       */
      protected String userClassNames = null;
-     
+
      public String getUserClassNames() {
          return (this.userClassNames);
      }
-     
+
      /**
       * Sets the list of comma-delimited classes that represent individual
       * users. The classes in the list must implement
@@ -339,7 +339,7 @@ public class JAASRealm extends RealmBase {
 
     }
 
-    
+
     // --------------------------------------------------------- Public Methods
 
 
@@ -356,7 +356,7 @@ public class JAASRealm extends RealmBase {
         return authenticate(username,
                 new JAASCallbackHandler(this, username, credentials));
     }
-     
+
 
     /**
      * Return the <code>Principal</code> associated with the specified username
@@ -524,8 +524,8 @@ public class JAASRealm extends RealmBase {
      * by the JAASLoginModule. The first <code>Principal</code> object that matches
      * one of the class names supplied as a "user class" is the user Principal.
      * This object is returned to the caller.
-     * Any remaining principal objects returned by the LoginModules are mapped to  
-     * roles, but only if their respective classes match one of the "role class" classes. 
+     * Any remaining principal objects returned by the LoginModules are mapped to
+     * roles, but only if their respective classes match one of the "role class" classes.
      * If a user Principal cannot be constructed, return <code>null</code>.
      * @param subject The <code>Subject</code> representing the logged-in user
      * @param loginContext Associated with the Principal so
@@ -555,7 +555,7 @@ public class JAASRealm extends RealmBase {
                     log.debug(sm.getString("jaasRealm.userPrincipalSuccess", principal.getName()));
                 }
             }
-            
+
             if (roleClasses.contains(principalClass)) {
                 roles.add(principal.getName());
                 if( log.isDebugEnabled() ) {
@@ -594,7 +594,7 @@ public class JAASRealm extends RealmBase {
       */
      protected String makeLegalForJAAS(final String src) {
          String result = src;
-         
+
          // Default name is "other" per JAAS spec
          if(result == null) {
              result = "other";
@@ -649,6 +649,7 @@ public class JAASRealm extends RealmBase {
                 URL resource = Thread.currentThread().getContextClassLoader().
                         getResource(configFile);
                 URI uri = resource.toURI();
+                @SuppressWarnings("unchecked")
                 Class<Configuration> sunConfigFile = (Class<Configuration>)
                         Class.forName("com.sun.security.auth.login.ConfigFile");
                 Constructor<Configuration> constructor =
diff --git a/java/org/apache/catalina/realm/JDBCRealm.java b/java/org/apache/catalina/realm/JDBCRealm.java
index ed70520..8fd9a73 100644
--- a/java/org/apache/catalina/realm/JDBCRealm.java
+++ b/java/org/apache/catalina/realm/JDBCRealm.java
@@ -5,9 +5,9 @@
  * The ASF licenses this file to You 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.
@@ -47,7 +47,7 @@ import org.apache.tomcat.util.ExceptionUtils;
 * @author Craig R. McClanahan
 * @author Carson McDonald
 * @author Ignacio Ortega
-* @version $Id: JDBCRealm.java 1495202 2013-06-20 21:38:59Z markt $
+* @version $Id: JDBCRealm.java 1509160 2013-08-01 10:17:46Z markt $
 */
 
 public class JDBCRealm
@@ -428,7 +428,7 @@ public class JDBCRealm
         }
 
         ArrayList<String> roles = getRoles(username);
-        
+
         // Create and return a suitable Principal for this user
         return (new GenericPrincipal(username, credentials, roles));
 
@@ -550,19 +550,19 @@ public class JDBCRealm
             try {
                 // Ensure that we have an open database connection
                 open();
-                
+
                 stmt = credentials(dbConnection, username);
                 rs = stmt.executeQuery();
-                dbConnection.commit();
-                    
                 if (rs.next()) {
                     dbCredentials = rs.getString(1);
                 }
-                    
+
+                dbConnection.commit();
+
                 if (dbCredentials != null) {
                     dbCredentials = dbCredentials.trim();
                 }
-                
+
                 return dbCredentials;
 
             } catch (SQLException e) {
@@ -583,10 +583,10 @@ public class JDBCRealm
             if (dbConnection != null) {
                 close(dbConnection);
             }
-            
+
             numberOfTries--;
         }
-        
+
         return (null);
     }
 
@@ -608,7 +608,7 @@ public class JDBCRealm
      * Return the roles associated with the gven user name.
      */
     protected ArrayList<String> getRoles(String username) {
-        
+
         if (allRolesMode != AllRolesMode.STRICT_MODE && !isRoleStoreDefined()) {
             // Using an authentication only configuration and no role store has
             // been defined so don't spend cycles looking
@@ -629,10 +629,10 @@ public class JDBCRealm
         int numberOfTries = 2;
         while (numberOfTries>0) {
             try {
-                
+
                 // Ensure that we have an open database connection
                 open();
-                
+
                 try {
                     // Accumulate the user's roles
                     ArrayList<String> roleList = new ArrayList<String>();
@@ -646,9 +646,9 @@ public class JDBCRealm
                     }
                     rs.close();
                     rs = null;
-                    
+
                     return (roleList);
-                    
+
                 } finally {
                     if (rs!=null) {
                         try {
@@ -659,25 +659,25 @@ public class JDBCRealm
                     }
                     dbConnection.commit();
                 }
-                
+
             } catch (SQLException e) {
-                
+
                 // Log the problem for posterity
                 containerLog.error(sm.getString("jdbcRealm.exception"), e);
-                
+
                 // Close the connection so that it gets reopened next time
                 if (dbConnection != null)
                     close(dbConnection);
-                
+
             }
-            
+
             numberOfTries--;
         }
-        
+
         return null;
     }
-    
-    
+
+
     /**
      * Open (if necessary) and return a database connection for use by
      * this Realm.
diff --git a/java/org/apache/catalina/realm/JNDIRealm.java b/java/org/apache/catalina/realm/JNDIRealm.java
index 7cf1ca5..bb29ec0 100644
--- a/java/org/apache/catalina/realm/JNDIRealm.java
+++ b/java/org/apache/catalina/realm/JNDIRealm.java
@@ -172,7 +172,7 @@ import org.ietf.jgss.GSSCredential;
  *
  * @author John Holman
  * @author Craig R. McClanahan
- * @version $Id: JNDIRealm.java 1497545 2013-06-27 20:02:46Z markt $
+ * @version $Id: JNDIRealm.java 1518212 2013-08-28 14:05:19Z markt $
  */
 
 public class JNDIRealm extends RealmBase {
@@ -1048,7 +1048,7 @@ public class JNDIRealm extends RealmBase {
                    with broken SSL
                 */
                 // log the exception so we know it's there.
-                containerLog.warn(sm.getString("jndiRealm.exception"), e);
+                containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
 
                 // close the connection so we know it will be reopened.
                 if (context != null)
@@ -1063,7 +1063,7 @@ public class JNDIRealm extends RealmBase {
             } catch (CommunicationException e) {
 
                 // log the exception so we know it's there.
-                containerLog.warn(sm.getString("jndiRealm.exception"), e);
+                containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
 
                 // close the connection so we know it will be reopened.
                 if (context != null)
@@ -1078,7 +1078,7 @@ public class JNDIRealm extends RealmBase {
             } catch (ServiceUnavailableException e) {
 
                 // log the exception so we know it's there.
-                containerLog.warn(sm.getString("jndiRealm.exception"), e);
+                containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
 
                 // close the connection so we know it will be reopened.
                 if (context != null)
@@ -2007,7 +2007,7 @@ public class JNDIRealm extends RealmBase {
             } catch (CommunicationException e) {
 
                 // log the exception so we know it's there.
-                containerLog.warn(sm.getString("jndiRealm.exception"), e);
+                containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
 
                 // close the connection so we know it will be reopened.
                 if (context != null)
@@ -2022,7 +2022,7 @@ public class JNDIRealm extends RealmBase {
             } catch (ServiceUnavailableException e) {
 
                 // log the exception so we know it's there.
-                containerLog.warn(sm.getString("jndiRealm.exception"), e);
+                containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
 
                 // close the connection so we know it will be reopened.
                 if (context != null)
@@ -2070,9 +2070,12 @@ public class JNDIRealm extends RealmBase {
 
         User user = null;
         List<String> roles = null;
+        Hashtable<?, ?> preservedEnvironment = null;
 
         try {
             if (gssCredential != null && isUseDelegatedCredential()) {
+                // Preserve the current context environment parameters
+                preservedEnvironment = context.getEnvironment();
                 // Set up context
                 context.addToEnvironment(
                         Context.SECURITY_AUTHENTICATION, "GSSAPI");
@@ -2088,24 +2091,12 @@ public class JNDIRealm extends RealmBase {
                 roles = getRoles(context, user);
             }
         } finally {
-            try {
-                context.removeFromEnvironment(
-                        Context.SECURITY_AUTHENTICATION);
-            } catch (NamingException e) {
-                // Ignore
-            }
-            try {
-                context.removeFromEnvironment(
-                        "javax.security.sasl.server.authentication");
-            } catch (NamingException e) {
-                // Ignore
-            }
-            try {
-                context.removeFromEnvironment(
-                        "javax.security.sasl.qop");
-            } catch (NamingException e) {
-                // Ignore
-            }
+            restoreEnvironmentParameter(context,
+                    Context.SECURITY_AUTHENTICATION, preservedEnvironment);
+            restoreEnvironmentParameter(context,
+                    "javax.security.sasl.server.authentication", preservedEnvironment);
+            restoreEnvironmentParameter(context, "javax.security.sasl.qop",
+                    preservedEnvironment);
         }
 
         if (user != null) {
@@ -2116,6 +2107,19 @@ public class JNDIRealm extends RealmBase {
         return null;
     }
 
+    private void restoreEnvironmentParameter(DirContext context,
+            String parameterName, Hashtable<?, ?> preservedEnvironment) {
+        try {
+            context.removeFromEnvironment(parameterName);
+            if (preservedEnvironment != null && preservedEnvironment.containsKey(parameterName)) {
+                context.addToEnvironment(parameterName,
+                        preservedEnvironment.get(parameterName));
+            }
+        } catch (NamingException e) {
+            // Ignore
+        }
+    }
+
     /**
      * Open (if necessary) and return a connection to the configured
      * directory server for this Realm.
@@ -2138,7 +2142,7 @@ public class JNDIRealm extends RealmBase {
             connectionAttempt = 1;
 
             // log the first exception.
-            containerLog.warn(sm.getString("jndiRealm.exception"), e);
+            containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
 
             // Try connecting to the alternate url.
             context = new InitialDirContext(getDirectoryContextEnvironment());
diff --git a/java/org/apache/catalina/realm/LocalStrings.properties b/java/org/apache/catalina/realm/LocalStrings.properties
index 8833840..8401ec5 100644
--- a/java/org/apache/catalina/realm/LocalStrings.properties
+++ b/java/org/apache/catalina/realm/LocalStrings.properties
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# $Id: LocalStrings.properties 1298479 2012-03-08 17:34:53Z schultz $
+# $Id: LocalStrings.properties 1518212 2013-08-28 14:05:19Z markt $
 
 # language
 
@@ -56,6 +56,7 @@ jndiRealm.authenticateFailure=Username {0} NOT successfully authenticated
 jndiRealm.authenticateSuccess=Username {0} successfully authenticated
 jndiRealm.close=Exception closing directory server connection
 jndiRealm.exception=Exception performing authentication
+jndiRealm.exception.retry=Exception performing authentication. Retrying...
 jndiRealm.open=Exception opening directory server connection
 memoryRealm.authenticateFailure=Username {0} NOT successfully authenticated
 memoryRealm.authenticateSuccess=Username {0} successfully authenticated
diff --git a/java/org/apache/catalina/session/FileStore.java b/java/org/apache/catalina/session/FileStore.java
index 5afb268..fa766ab 100644
--- a/java/org/apache/catalina/session/FileStore.java
+++ b/java/org/apache/catalina/session/FileStore.java
@@ -45,7 +45,7 @@ import org.apache.catalina.util.CustomObjectInputStream;
  * saved are still subject to being expired based on inactivity.
  *
  * @author Craig R. McClanahan
- * @version $Id: FileStore.java 1162172 2011-08-26 17:12:33Z markt $
+ * @version $Id: FileStore.java 1514455 2013-08-15 19:37:45Z violetagg $
  */
 
 public final class FileStore extends StoreBase {
@@ -266,6 +266,7 @@ public final class FileStore extends StoreBase {
         ObjectInputStream ois = null;
         Loader loader = null;
         ClassLoader classLoader = null;
+        ClassLoader oldThreadContextCL = Thread.currentThread().getContextClassLoader();
         try {
             fis = new FileInputStream(file.getAbsolutePath());
             bis = new BufferedInputStream(fis);
@@ -274,10 +275,18 @@ public final class FileStore extends StoreBase {
                 loader = container.getLoader();
             if (loader != null)
                 classLoader = loader.getClassLoader();
-            if (classLoader != null)
+            if (classLoader != null) {
+                Thread.currentThread().setContextClassLoader(classLoader);
                 ois = new CustomObjectInputStream(bis, classLoader);
-            else
+            } else {
                 ois = new ObjectInputStream(bis);
+            }
+
+            StandardSession session =
+                    (StandardSession) manager.createEmptySession();
+            session.readObjectData(ois);
+            session.setManager(manager);
+            return (session);
         } catch (FileNotFoundException e) {
             if (manager.getContainer().getLogger().isDebugEnabled())
                 manager.getContainer().getLogger().debug("No persisted data file found");
@@ -298,21 +307,16 @@ public final class FileStore extends StoreBase {
                 }
             }
             throw e;
-        }
-
-        try {
-            StandardSession session =
-                (StandardSession) manager.createEmptySession();
-            session.readObjectData(ois);
-            session.setManager(manager);
-            return (session);
         } finally {
-            // Close the input stream
-            try {
-                ois.close();
-            } catch (IOException f) {
-                // Ignore
+            if (ois != null) {
+                // Close the input stream
+                try {
+                    ois.close();
+                } catch (IOException f) {
+                    // Ignore
+                }
             }
+            Thread.currentThread().setContextClassLoader(oldThreadContextCL);
         }
     }
 
diff --git a/java/org/apache/catalina/session/JDBCStore.java b/java/org/apache/catalina/session/JDBCStore.java
index aa2a55e..b5d1236 100644
--- a/java/org/apache/catalina/session/JDBCStore.java
+++ b/java/org/apache/catalina/session/JDBCStore.java
@@ -51,7 +51,7 @@ import org.apache.tomcat.util.ExceptionUtils;
  * saved are still subject to being expired based on inactivity.
  *
  * @author Bip Thelin
- * @version $Id: JDBCStore.java 1153099 2011-08-02 11:51:20Z kfujino $
+ * @version $Id: JDBCStore.java 1514455 2013-08-15 19:37:45Z violetagg $
  */
 
 public class JDBCStore extends StoreBase {
@@ -620,6 +620,7 @@ public class JDBCStore extends StoreBase {
                     return (null);
                 }
 
+                ClassLoader oldThreadContextCL = Thread.currentThread().getContextClassLoader();
                 try {
                     if (preparedLoadSql == null) {
                         String loadSql = "SELECT " + sessionIdCol + ", "
@@ -642,6 +643,7 @@ public class JDBCStore extends StoreBase {
                             classLoader = loader.getClassLoader();
                         }
                         if (classLoader != null) {
+                            Thread.currentThread().setContextClassLoader(classLoader);
                             ois = new CustomObjectInputStream(bis,
                                     classLoader);
                         } else {
@@ -680,6 +682,7 @@ public class JDBCStore extends StoreBase {
                             // Ignore
                         }
                     }
+                    Thread.currentThread().setContextClassLoader(oldThreadContextCL);
                     release(_conn);
                 }
                 numberOfTries--;
diff --git a/java/org/apache/catalina/session/StandardSession.java b/java/org/apache/catalina/session/StandardSession.java
index c4de3e0..c662563 100644
--- a/java/org/apache/catalina/session/StandardSession.java
+++ b/java/org/apache/catalina/session/StandardSession.java
@@ -76,7 +76,7 @@ import org.apache.tomcat.util.res.StringManager;
  * @author Craig R. McClanahan
  * @author Sean Legassick
  * @author <a href="mailto:jon at latchkey.com">Jon S. Stevens</a>
- * @version $Id: StandardSession.java 1239138 2012-02-01 14:09:59Z markt $
+ * @version $Id: StandardSession.java 1520358 2013-09-05 16:13:06Z markt $
  */
 
 public class StandardSession implements HttpSession, Session, Serializable {
@@ -634,14 +634,14 @@ public class StandardSession implements HttpSession, Session, Serializable {
     @Override
     public boolean isValid() {
 
-        if (this.expiring) {
-            return true;
-        }
-
         if (!this.isValid) {
             return false;
         }
 
+        if (this.expiring) {
+            return true;
+        }
+
         if (ACTIVITY_CHECK && accessCount.get() > 0) {
             return true;
         }
@@ -659,7 +659,7 @@ public class StandardSession implements HttpSession, Session, Serializable {
             }
         }
 
-        return (this.isValid);
+        return this.isValid;
     }
 
 
@@ -753,14 +753,16 @@ public class StandardSession implements HttpSession, Session, Serializable {
      */
     public void expire(boolean notify) {
 
-        // Check to see if expire is in progress or has previously been called
-        if (expiring || !isValid)
+        // Check to see if session has already been invalidated.
+        // Do not check expiring at this point as expire should not return until
+        // isValid is false
+        if (!isValid)
             return;
 
         synchronized (this) {
             // Check again, now we are inside the sync so this code only runs once
-            // Double check locking - expiring and isValid need to be volatile
-            if (expiring || !isValid)
+            // Double check locking - isValid needs to be volatile
+            if (!isValid)
                 return;
 
             if (manager == null)
@@ -834,7 +836,6 @@ public class StandardSession implements HttpSession, Session, Serializable {
             if (ACTIVITY_CHECK) {
                 accessCount.set(0);
             }
-            setValid(false);
 
             // Remove this session from our manager's active sessions
             manager.remove(this, true);
@@ -857,6 +858,7 @@ public class StandardSession implements HttpSession, Session, Serializable {
             }
 
             // We have completed expire of this session
+            setValid(false);
             expiring = false;
 
             // Unbind any objects associated with this session
@@ -1537,7 +1539,7 @@ public class StandardSession implements HttpSession, Session, Serializable {
      * check.
      */
     protected boolean isValidInternal() {
-        return (this.isValid || this.expiring);
+        return this.isValid;
     }
 
     /**
diff --git a/java/org/apache/catalina/session/mbeans-descriptors.xml b/java/org/apache/catalina/session/mbeans-descriptors.xml
index a474589..16a90a0 100644
--- a/java/org/apache/catalina/session/mbeans-descriptors.xml
+++ b/java/org/apache/catalina/session/mbeans-descriptors.xml
@@ -232,11 +232,6 @@
           description="Number of sessions that expired ( doesn't include explicit invalidations )"
                  type="long" />
                  
-    <attribute   name="loaded"
-          description="If the session id is loaded in memory?"
-                 type="boolean"
-           writeable = "false" />
-                 
     <attribute   name="jvmRoute"
           description="Retrieve the JvmRoute for the enclosing Engine"
                  type="java.lang.String"
@@ -395,6 +390,15 @@
            returnType="java.lang.String">
     </operation>
 
+    <operation   name="isLoaded"
+          description="If the session id is loaded in memory?"
+               impact="ACTION"
+           returnType="booelan">
+      <parameter name="sessionId"
+          description="Id of the session"
+                 type="java.lang.String"/>
+    </operation>
+
   </mbean>
 
 </mbeans-descriptors>
diff --git a/java/org/apache/catalina/startup/ContextConfig.java b/java/org/apache/catalina/startup/ContextConfig.java
index 9c6d355..0a144d1 100644
--- a/java/org/apache/catalina/startup/ContextConfig.java
+++ b/java/org/apache/catalina/startup/ContextConfig.java
@@ -109,7 +109,7 @@ import org.xml.sax.SAXParseException;
  *
  * @author Craig R. McClanahan
  * @author Jean-Francois Arcand
- * @version $Id: ContextConfig.java 1488152 2013-05-31 11:07:18Z kkolinko $
+ * @version $Id: ContextConfig.java 1513151 2013-08-12 14:32:01Z markt $
  */
 public class ContextConfig implements LifecycleListener {
 
@@ -1131,7 +1131,7 @@ public class ContextConfig implements LifecycleListener {
             for (int j = 0; j < roles.length; j++) {
                 if (!"*".equals(roles[j]) &&
                     !context.findSecurityRole(roles[j])) {
-                    log.info(sm.getString("contextConfig.role.auth", roles[j]));
+                    log.warn(sm.getString("contextConfig.role.auth", roles[j]));
                     context.addSecurityRole(roles[j]);
                 }
             }
@@ -1143,14 +1143,14 @@ public class ContextConfig implements LifecycleListener {
             Wrapper wrapper = (Wrapper) wrappers[i];
             String runAs = wrapper.getRunAs();
             if ((runAs != null) && !context.findSecurityRole(runAs)) {
-                log.info(sm.getString("contextConfig.role.runas", runAs));
+                log.warn(sm.getString("contextConfig.role.runas", runAs));
                 context.addSecurityRole(runAs);
             }
             String names[] = wrapper.findSecurityReferences();
             for (int j = 0; j < names.length; j++) {
                 String link = wrapper.findSecurityReference(names[j]);
                 if ((link != null) && !context.findSecurityRole(link)) {
-                    log.info(sm.getString("contextConfig.role.link", link));
+                    log.warn(sm.getString("contextConfig.role.link", link));
                     context.addSecurityRole(link);
                 }
             }
@@ -1546,7 +1546,7 @@ public class ContextConfig implements LifecycleListener {
             URL url = fragment.getURL();
             Jar jar = null;
             InputStream is = null;
-            ServletContainerInitializer sci = null;
+            List<ServletContainerInitializer> detectedScis = null;
             try {
                 if ("jar".equals(url.getProtocol())) {
                     jar = JarFactory.newInstance(url);
@@ -1559,7 +1559,7 @@ public class ContextConfig implements LifecycleListener {
                     }
                 }
                 if (is != null) {
-                    sci = getServletContainerInitializer(is);
+                    detectedScis = getServletContainerInitializers(is);
                 }
             } catch (IOException ioe) {
                 log.error(sm.getString(
@@ -1580,42 +1580,44 @@ public class ContextConfig implements LifecycleListener {
                 }
             }
 
-            if (sci == null) {
+            if (detectedScis == null) {
                 continue;
             }
 
-            initializerClassMap.put(sci, new HashSet<Class<?>>());
+            for (ServletContainerInitializer sci : detectedScis) {
+                initializerClassMap.put(sci, new HashSet<Class<?>>());
 
-            HandlesTypes ht = null;
-            try {
-                ht = sci.getClass().getAnnotation(HandlesTypes.class);
-            } catch (Exception e) {
-                if (log.isDebugEnabled()) {
-                    log.info(sm.getString("contextConfig.sci.debug", url), e);
-                } else {
-                    log.info(sm.getString("contextConfig.sci.info", url));
+                HandlesTypes ht = null;
+                try {
+                    ht = sci.getClass().getAnnotation(HandlesTypes.class);
+                } catch (Exception e) {
+                    if (log.isDebugEnabled()) {
+                        log.info(sm.getString("contextConfig.sci.debug", url),
+                                e);
+                    } else {
+                        log.info(sm.getString("contextConfig.sci.info", url));
+                    }
                 }
-            }
-            if (ht != null) {
-                Class<?>[] types = ht.value();
-                if (types != null) {
-                    for (Class<?> type : types) {
-                        if (type.isAnnotation()) {
-                            handlesTypesAnnotations = true;
-                        } else {
-                            handlesTypesNonAnnotations = true;
-                        }
-                        Set<ServletContainerInitializer> scis =
-                            typeInitializerMap.get(type);
-                        if (scis == null) {
-                            scis = new HashSet<ServletContainerInitializer>();
-                            typeInitializerMap.put(type, scis);
+                if (ht != null) {
+                    Class<?>[] types = ht.value();
+                    if (types != null) {
+                        for (Class<?> type : types) {
+                            if (type.isAnnotation()) {
+                                handlesTypesAnnotations = true;
+                            } else {
+                                handlesTypesNonAnnotations = true;
+                            }
+                            Set<ServletContainerInitializer> scis = typeInitializerMap
+                                    .get(type);
+                            if (scis == null) {
+                                scis = new HashSet<ServletContainerInitializer>();
+                                typeInitializerMap.put(type, scis);
+                            }
+                            scis.add(sci);
                         }
-                        scis.add(sci);
                     }
                 }
             }
-
         }
     }
 
@@ -1627,19 +1629,28 @@ public class ContextConfig implements LifecycleListener {
      * @return      The class name
      * @throws IOException
      */
-    protected ServletContainerInitializer getServletContainerInitializer(
+    protected List<ServletContainerInitializer> getServletContainerInitializers(
             InputStream is) throws IOException {
 
-        String className = null;
+        List<ServletContainerInitializer> initializers = new ArrayList<ServletContainerInitializer>();
 
         if (is != null) {
             String line = null;
             try {
-                BufferedReader br =
-                    new BufferedReader(new InputStreamReader(is, "UTF-8"));
-                line = br.readLine();
-                if (line != null && line.trim().length() > 0) {
-                    className = line.trim();
+                BufferedReader br = new BufferedReader(new InputStreamReader(
+                        is, "UTF-8"));
+                while ((line = br.readLine()) != null) {
+                    line = line.trim();
+                    if (line.length() > 0) {
+                        int i = line.indexOf('#');
+                        if (i > -1) {
+                            if (i == 0) {
+                                continue;
+                            }
+                            line = line.substring(0, i).trim();
+                        }
+                        initializers.add(getServletContainerInitializer(line));
+                    }
                 }
             } catch (UnsupportedEncodingException e) {
                 // Should never happen with UTF-8
@@ -1647,11 +1658,16 @@ public class ContextConfig implements LifecycleListener {
             }
         }
 
+        return initializers;
+    }
+
+    protected ServletContainerInitializer getServletContainerInitializer(
+            String className) throws IOException {
         ServletContainerInitializer sci = null;
         try {
-            Class<?> clazz = Class.forName(className,true,
-                    context.getLoader().getClassLoader());
-             sci = (ServletContainerInitializer) clazz.newInstance();
+            Class<?> clazz = Class.forName(className, true, context.getLoader()
+                    .getClassLoader());
+            sci = (ServletContainerInitializer) clazz.newInstance();
         } catch (ClassNotFoundException e) {
             log.error(sm.getString("contextConfig.invalidSci", className), e);
             throw new IOException(e);
diff --git a/java/org/apache/catalina/startup/FailedContext.java b/java/org/apache/catalina/startup/FailedContext.java
index 2ab75f8..1caf751 100644
--- a/java/org/apache/catalina/startup/FailedContext.java
+++ b/java/org/apache/catalina/startup/FailedContext.java
@@ -59,6 +59,7 @@ import org.apache.catalina.mbeans.MBeanUtils;
 import org.apache.catalina.util.CharsetMapper;
 import org.apache.catalina.util.LifecycleMBeanBase;
 import org.apache.juli.logging.Log;
+import org.apache.tomcat.InstanceManager;
 import org.apache.tomcat.JarScanner;
 import org.apache.tomcat.util.http.mapper.Mapper;
 import org.apache.tomcat.util.res.StringManager;
@@ -685,4 +686,10 @@ public class FailedContext extends LifecycleMBeanBase implements Context {
 
     @Override
     public Map<String, String> findPreDestroyMethods() { return null; }
+
+    @Override
+    public InstanceManager getInstanceManager() { return null; }
+
+    @Override
+    public void setInstanceManager(InstanceManager instanceManager) { /* NO-OP */ }
 }
\ No newline at end of file
diff --git a/java/org/apache/catalina/startup/HostConfig.java b/java/org/apache/catalina/startup/HostConfig.java
index 8976535..03fa3aa 100644
--- a/java/org/apache/catalina/startup/HostConfig.java
+++ b/java/org/apache/catalina/startup/HostConfig.java
@@ -73,7 +73,7 @@ import org.apache.tomcat.util.res.StringManager;
  *
  * @author Craig R. McClanahan
  * @author Remy Maucherat
- * @version $Id: HostConfig.java 1482312 2013-05-14 12:02:30Z markt $
+ * @version $Id: HostConfig.java 1508756 2013-07-31 07:50:02Z kfujino $
  */
 public class HostConfig
     implements LifecycleListener {
@@ -97,7 +97,9 @@ public class HostConfig
 
     /**
      * The Java class name of the Context configuration class we should use.
+     * @deprecated Will be removed in Tomcat 8.0.x
      */
+    @Deprecated
     protected String configClass = "org.apache.catalina.startup.ContextConfig";
 
 
@@ -178,7 +180,9 @@ public class HostConfig
 
     /**
      * Return the Context configuration class name.
+     * @deprecated Will be removed in Tomcat 8.0.x
      */
+    @Deprecated
     public String getConfigClass() {
 
         return (this.configClass);
@@ -190,7 +194,9 @@ public class HostConfig
      * Set the Context configuration class name.
      *
      * @param configClass The new Context configuration class name.
+     * @deprecated Will be removed in Tomcat 8.0.x
      */
+    @Deprecated
     public void setConfigClass(String configClass) {
 
         this.configClass = configClass;
@@ -304,6 +310,7 @@ public class HostConfig
                 setCopyXML(((StandardHost) host).isCopyXML());
                 setDeployXML(((StandardHost) host).isDeployXML());
                 setUnpackWARs(((StandardHost) host).isUnpackWARs());
+                setContextClass(((StandardHost) host).getContextClass());
             }
         } catch (ClassCastException e) {
             log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
diff --git a/java/org/apache/catalina/startup/LocalStrings.properties b/java/org/apache/catalina/startup/LocalStrings.properties
index f062d16..360e0f0 100644
--- a/java/org/apache/catalina/startup/LocalStrings.properties
+++ b/java/org/apache/catalina/startup/LocalStrings.properties
@@ -55,9 +55,9 @@ contextConfig.jspFile.error=JSP file {0} must start with a ''/'
 contextConfig.jspFile.warning=WARNING: JSP file {0} must start with a ''/'' in Servlet 2.4
 contextConfig.missingRealm=No Realm has been configured to authenticate against
 contextConfig.resourceJarFail=Failed to processes JAR found at URL [{0}] for static resources to be included in context with name [{0}]
-contextConfig.role.auth=WARNING: Security role name {0} used in an <auth-constraint> without being defined in a <security-role>
-contextConfig.role.link=WARNING: Security role name {0} used in a <role-link> without being defined in a <security-role>
-contextConfig.role.runas=WARNING: Security role name {0} used in a <run-as> without being defined in a <security-role>
+contextConfig.role.auth=Security role name {0} used in an <auth-constraint> without being defined in a <security-role>
+contextConfig.role.link=Security role name {0} used in a <role-link> without being defined in a <security-role>
+contextConfig.role.runas=Security role name {0} used in a <run-as> without being defined in a <security-role>
 contextConfig.sci.debug=Unable to process ServletContainerInitializer for [{0}]. This is most likely due to a class defined in the @HandlesTypes annotation being missing
 contextConfig.sci.info=Unable to process ServletContainerInitializer for [{0}]. This is most likely due to a class defined in the @HandlesTypes annotation being missing. Enable DEBUG level logging for the full stack trace.
 contextConfig.servletContainerInitializerFail=Failed to process JAR found at URL [{0}] for ServletContainerInitializers for context with name [{1}]
diff --git a/java/org/apache/catalina/startup/LocalStrings_es.properties b/java/org/apache/catalina/startup/LocalStrings_es.properties
index 8d4ceae..b15f3a9 100644
--- a/java/org/apache/catalina/startup/LocalStrings_es.properties
+++ b/java/org/apache/catalina/startup/LocalStrings_es.properties
@@ -51,9 +51,9 @@ contextConfig.jspFile.error = El archivo JSP {0} debe de comenzar con ''/'
 contextConfig.jspFile.warning = AVISO\: El archivo JSP {0} debe de comenzar con ''/'' en Servlet 2.4
 contextConfig.missingRealm = Alg\u00FAn reino (realm) no ha sido configurado para realizar la autenticaci\u00F3n
 contextConfig.resourceJarFail = Hallado JAR fallido a los procesos en URL [{0}] para recursos est\u00E1ticos a ser incluidos en contexto con nombre [{0}]
-contextConfig.role.auth = ATENCI\u00D3N\: El nombre de papel de seguridad {0} es usado en un <auth-constraint> sin haber sido definido en <security-role>
-contextConfig.role.link = ATENCI\u00D3N\: El nombre de papel de seguridad {0} es usado en un <role-link> sin haber sido definido en <security-role>
-contextConfig.role.runas = ATENCI\u00D3N\: El nombre de papel de seguridad {0} es usado en un <run-as> sin haber sido definido en <security-role>
+contextConfig.role.auth = El nombre de papel de seguridad {0} es usado en un <auth-constraint> sin haber sido definido en <security-role>
+contextConfig.role.link = El nombre de papel de seguridad {0} es usado en un <role-link> sin haber sido definido en <security-role>
+contextConfig.role.runas = El nombre de papel de seguridad {0} es usado en un <run-as> sin haber sido definido en <security-role>
 contextConfig.servletContainerInitializerFail = Hallado JAR fallido a proceso en URL [{0}] para ServletContainerInitializers para el contexto con nombre [{1}]
 contextConfig.start = "ContextConfig"\: Procesando "START"
 contextConfig.stop = "ContextConfig"\: Procesando "STOP"
diff --git a/java/org/apache/catalina/startup/LocalStrings_fr.properties b/java/org/apache/catalina/startup/LocalStrings_fr.properties
index 321a3ec..f78c2bf 100644
--- a/java/org/apache/catalina/startup/LocalStrings_fr.properties
+++ b/java/org/apache/catalina/startup/LocalStrings_fr.properties
@@ -25,9 +25,9 @@ contextConfig.defaultPosition=S''est produite \u00e0 la ligne {0} colonne {1}
 contextConfig.jspFile.error=Le fichier JSP {0} doit commencer par un ''/''
 contextConfig.jspFile.warning=ATTENTION: Le fichier JSP {0} doit commencer par un  ''/'' dans l''API Servlet 2.4
 contextConfig.missingRealm=Aucun royaume (realm) n''a \u00e9t\u00e9 configur\u00e9 pour r\u00e9aliser l''authentification
-contextConfig.role.auth=ATTENTION: Le nom de r\u00f4le de s\u00e9curit\u00e9 {0} est utilis\u00e9 dans un <auth-constraint> sans avoir \u00e9t\u00e9 d\u00e9fini dans <security-role>
-contextConfig.role.link=ATTENTION: Le nom de r\u00f4le de s\u00e9curit\u00e9 {0} est utilis\u00e9 dans un <role-link> sans avoir \u00e9t\u00e9 d\u00e9fini dans <security-role>
-contextConfig.role.runas=ATTENTION: Le nom de r\u00f4le de s\u00e9curit\u00e9 {0} est utilis\u00e9 dans un <run-as> sans avoir \u00e9t\u00e9 d\u00e9fini dans <security-role>
+contextConfig.role.auth=Le nom de r\u00f4le de s\u00e9curit\u00e9 {0} est utilis\u00e9 dans un <auth-constraint> sans avoir \u00e9t\u00e9 d\u00e9fini dans <security-role>
+contextConfig.role.link=Le nom de r\u00f4le de s\u00e9curit\u00e9 {0} est utilis\u00e9 dans un <role-link> sans avoir \u00e9t\u00e9 d\u00e9fini dans <security-role>
+contextConfig.role.runas=Le nom de r\u00f4le de s\u00e9curit\u00e9 {0} est utilis\u00e9 dans un <run-as> sans avoir \u00e9t\u00e9 d\u00e9fini dans <security-role>
 contextConfig.start="ContextConfig": Traitement du "START"
 contextConfig.stop="ContextConfig": Traitement du "STOP"
 contextConfig.unavailable=Cette application est marqu\u00e9e comme non disponible suite aux erreurs pr\u00e9c\u00e9dentes
diff --git a/java/org/apache/catalina/startup/LocalStrings_ja.properties b/java/org/apache/catalina/startup/LocalStrings_ja.properties
index 0b9a16d..3539f0f 100644
--- a/java/org/apache/catalina/startup/LocalStrings_ja.properties
+++ b/java/org/apache/catalina/startup/LocalStrings_ja.properties
@@ -25,9 +25,9 @@ contextConfig.defaultPosition={0}\u884c\u306e{1}\u5217\u76ee\u3067\u767a\u751f\u
 contextConfig.jspFile.error=JSP\u30d5\u30a1\u30a4\u30eb {0} \u306f''/''\u3067\u59cb\u307e\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
 contextConfig.jspFile.warning=\u8b66\u544a: Servlet 2.4\u3067\u306fJSP\u30d5\u30a1\u30a4\u30eb {0} \u306f''/''\u3067\u59cb\u307e\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
 contextConfig.missingRealm=\u8a8d\u8a3c\u3059\u308b\u305f\u3081\u306b\u30ec\u30eb\u30e0\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093
-contextConfig.role.auth=\u8b66\u544a: <security-role>\u306b\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u306a\u3044\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30ed\u30fc\u30eb\u540d {0} \u304c<auth-constraint>\u306e\u4e2d\u3067\u4f7f\u7528\u3055\u308c\u307e\u3057\u305f
-contextConfig.role.link=\u8b66\u544a: <security-role>\u306b\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u306a\u3044\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30ed\u30fc\u30eb\u540d {0} \u304c<role-link>\u306e\u4e2d\u3067\u4f7f\u7528\u3055\u308c\u307e\u3057\u305f
-contextConfig.role.runas=\u8b66\u544a: <security-role>\u306b\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u306a\u3044\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30ed\u30fc\u30eb\u540d {0} \u304c<run-as>\u306e\u4e2d\u3067\u4f7f\u7528\u3055\u308c\u307e\u3057\u305f
+contextConfig.role.auth=<security-role>\u306b\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u306a\u3044\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30ed\u30fc\u30eb\u540d {0} \u304c<auth-constraint>\u306e\u4e2d\u3067\u4f7f\u7528\u3055\u308c\u307e\u3057\u305f
+contextConfig.role.link=<security-role>\u306b\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u306a\u3044\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30ed\u30fc\u30eb\u540d {0} \u304c<role-link>\u306e\u4e2d\u3067\u4f7f\u7528\u3055\u308c\u307e\u3057\u305f
+contextConfig.role.runas=<security-role>\u306b\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u306a\u3044\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30ed\u30fc\u30eb\u540d {0} \u304c<run-as>\u306e\u4e2d\u3067\u4f7f\u7528\u3055\u308c\u307e\u3057\u305f
 contextConfig.start=ContextConfig: \u51e6\u7406\u3092\u958b\u59cb\u3057\u307e\u3059
 contextConfig.stop=ContextConfig: \u51e6\u7406\u3092\u505c\u6b62\u3057\u307e\u3059
 contextConfig.unavailable=\u524d\u306e\u30a8\u30e9\u30fc\u306e\u305f\u3081\u306b\u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306f\u5229\u7528\u3067\u304d\u306a\u3044\u3088\u3046\u306b\u30de\u30fc\u30af\u3057\u307e\u3059
diff --git a/java/org/apache/catalina/startup/Tomcat.java b/java/org/apache/catalina/startup/Tomcat.java
index 74b73d3..aba1697 100644
--- a/java/org/apache/catalina/startup/Tomcat.java
+++ b/java/org/apache/catalina/startup/Tomcat.java
@@ -18,12 +18,16 @@ package org.apache.catalina.startup;
 
 import java.io.File;
 import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.security.Principal;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Stack;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -187,13 +191,15 @@ public class Tomcat {
         hostname = s;
     }
 
-    /** 
-     * Add a webapp using normal WEB-INF/web.xml if found.
-     * 
-     * @param contextPath
-     * @param baseDir
-     * @return new Context
-     * @throws ServletException 
+    /**
+     * This is equivalent to adding a web application to Tomcat's webapps
+     * directory. The equivalent of the default web.xml will be applied  to the
+     * web application and any WEB-INF/web.xml and META-INF/context.xml packaged
+     * with the application will be processed normally. Normal web fragment and
+     * {@link javax.servlet.ServletContainerInitializer} processing will be
+     * applied.
+     *
+     * @throws ServletException
      */
     public Context addWebapp(String contextPath, 
                                      String baseDir) throws ServletException {
@@ -529,7 +535,8 @@ public class Tomcat {
         ctx.setDocBase(path);
 
         ctx.addLifecycleListener(new DefaultWebXmlListener());
-        
+        ctx.setConfigFile(getWebappConfigFile(path, url));
+
         ContextConfig ctxCfg = new ContextConfig();
         ctx.addLifecycleListener(ctxCfg);
         
@@ -676,16 +683,20 @@ public class Tomcat {
     }
     
     private void silence(Host host, String ctx) {
-        String base = "org.apache.catalina.core.ContainerBase.[default].[";
+        Logger.getLogger(getLoggerName(host, ctx)).setLevel(Level.WARNING);
+    }
+
+    private String getLoggerName(Host host, String ctx) {
+        String loggerName = "org.apache.catalina.core.ContainerBase.[default].[";
         if (host == null) {
-            base += getHost().getName();
+            loggerName += getHost().getName();
         } else {
-            base += host.getName();
+            loggerName += host.getName();
         }
-        base += "].[";
-        base += ctx; 
-        base += "]";
-        Logger.getLogger(base).setLevel(Level.WARNING);
+        loggerName += "].[";
+        loggerName += ctx;
+        loggerName += "]";
+        return loggerName;
     }
     
     /**
@@ -1054,4 +1065,52 @@ public class Tomcat {
         "z", "application/x-compress",
         "zip", "application/zip"
     };
+
+    protected URL getWebappConfigFile(String path, String url) {
+        File docBase = new File(path);
+        if (docBase.isDirectory()) {
+            return getWebappConfigFileFromDirectory(docBase, url);
+        } else {
+            return getWebappConfigFileFromJar(docBase, url);
+        }
+    }
+
+    private URL getWebappConfigFileFromDirectory(File docBase, String url) {
+        URL result = null;
+        File webAppContextXml = new File(docBase, Constants.ApplicationContextXml);
+        if (webAppContextXml.exists()) {
+            try {
+                result = webAppContextXml.toURI().toURL();
+            } catch (MalformedURLException e) {
+                Logger.getLogger(getLoggerName(getHost(), url)).log(Level.WARNING,
+                        "Unable to determine web application context.xml " + docBase, e);
+            }
+        }
+        return result;
+    }
+
+    private URL getWebappConfigFileFromJar(File docBase, String url) {
+        URL result = null;
+        JarFile jar = null;
+        try {
+            jar = new JarFile(docBase);
+            JarEntry entry = jar.getJarEntry(Constants.ApplicationContextXml);
+            if (entry != null) {
+                result = new URL("jar:" + docBase.toURI().toString() + "!/"
+                        + Constants.ApplicationContextXml);
+            }
+        } catch (IOException e) {
+            Logger.getLogger(getLoggerName(getHost(), url)).log(Level.WARNING,
+                    "Unable to determine web application context.xml " + docBase, e);
+        } finally {
+            if (jar != null) {
+                try {
+                    jar.close();
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+        return result;
+    }
 }
diff --git a/java/org/apache/catalina/startup/WebAnnotationSet.java b/java/org/apache/catalina/startup/WebAnnotationSet.java
index 509b495..49a81c4 100644
--- a/java/org/apache/catalina/startup/WebAnnotationSet.java
+++ b/java/org/apache/catalina/startup/WebAnnotationSet.java
@@ -41,7 +41,7 @@ import org.apache.tomcat.util.res.StringManager;
  * classes (<code>/WEB-INF/classes</code> and <code>/WEB-INF/lib</code>).</p>
  *
  * @author Fabien Carrion
- * @version $Id: WebAnnotationSet.java 1492415 2013-06-12 20:41:33Z markt $
+ * @version $Id: WebAnnotationSet.java 1521045 2013-09-09 11:17:46Z markt $
  */
 
 public class WebAnnotationSet {
@@ -369,8 +369,7 @@ public class WebAnnotationSet {
             if (annotation.authenticationType()
                     == Resource.AuthenticationType.CONTAINER) {
                 resource.setAuth("Container");
-            }
-            else if (annotation.authenticationType()
+            } else if (annotation.authenticationType()
                     == Resource.AuthenticationType.APPLICATION) {
                 resource.setAuth("Application");
             }
diff --git a/java/org/apache/catalina/tribes/MembershipService.java b/java/org/apache/catalina/tribes/MembershipService.java
index 33aff18..9387136 100644
--- a/java/org/apache/catalina/tribes/MembershipService.java
+++ b/java/org/apache/catalina/tribes/MembershipService.java
@@ -23,7 +23,7 @@ package org.apache.catalina.tribes;
  * The <code>MembershipService</code> interface is the membership component 
  * at the bottom layer, the IO layer (for layers see the javadoc for the {@link Channel} interface).<br>
  * @author Filip Hanik
- * @version $Id: MembershipService.java 939305 2010-04-29 13:43:39Z kkolinko $
+ * @version $Id: MembershipService.java 1515583 2013-08-19 20:11:47Z markt $
  */
 
 
@@ -67,7 +67,6 @@ public interface MembershipService {
      * the listener will start to receive membership events.
      * @param level - level MBR_RX stops listening for members, level MBR_TX 
      * stops broad casting the server
-     * @throws java.lang.Exception if the service fails to stop
      * @throws java.lang.IllegalArgumentException if the level is incorrect.
      */
 
diff --git a/java/org/apache/catalina/tribes/group/GroupChannel.java b/java/org/apache/catalina/tribes/group/GroupChannel.java
index 8354da5..9523793 100644
--- a/java/org/apache/catalina/tribes/group/GroupChannel.java
+++ b/java/org/apache/catalina/tribes/group/GroupChannel.java
@@ -18,8 +18,9 @@ package org.apache.catalina.tribes.group;
 
 
 import java.io.Serializable;
-import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.apache.catalina.tribes.ByteMessage;
 import org.apache.catalina.tribes.Channel;
@@ -53,7 +54,7 @@ import org.apache.juli.logging.LogFactory;
  * The channel has an chain of interceptors that can modify the message or perform other logic.<br>
  * It manages a complete group, both membership and replication.
  * @author Filip Hanik
- * @version $Id: GroupChannel.java 1437908 2013-01-24 08:57:41Z markt $
+ * @version $Id: GroupChannel.java 1505628 2013-07-22 08:51:27Z kfujino $
  */
 public class GroupChannel extends ChannelInterceptorBase implements ManagedChannel {
     private static final Log log = LogFactory.getLog(GroupChannel.class);
@@ -92,12 +93,12 @@ public class GroupChannel extends ChannelInterceptorBase implements ManagedChann
     /**
      * A list of membership listeners that subscribe to membership announcements
      */
-    protected ArrayList<Object> membershipListeners = new ArrayList<Object>();
+    protected List<Object> membershipListeners = new CopyOnWriteArrayList<Object>();
 
     /**
      * A list of channel listeners that subscribe to incoming messages
      */
-    protected ArrayList<Object> channelListeners = new ArrayList<Object>();
+    protected List<Object> channelListeners = new CopyOnWriteArrayList<Object>();
 
     /**
      * If set to true, the GroupChannel will check to make sure that
diff --git a/java/org/apache/catalina/tribes/group/interceptors/MessageDispatch15Interceptor.java b/java/org/apache/catalina/tribes/group/interceptors/MessageDispatch15Interceptor.java
index d64383e..569a59c 100644
--- a/java/org/apache/catalina/tribes/group/interceptors/MessageDispatch15Interceptor.java
+++ b/java/org/apache/catalina/tribes/group/interceptors/MessageDispatch15Interceptor.java
@@ -83,7 +83,8 @@ public class MessageDispatch15Interceptor extends MessageDispatchInterceptor {
     public void startQueue() {
         if ( run ) return;
         executor = ExecutorFactory.newThreadPool(maxSpareThreads, maxThreads,
-                keepAliveTime, TimeUnit.MILLISECONDS, new TcclThreadFactory());
+                keepAliveTime, TimeUnit.MILLISECONDS,
+                new TcclThreadFactory("MessageDispatch15Interceptor.MessageDispatchThread"));
         run = true;
     }
 
diff --git a/java/org/apache/catalina/tribes/group/interceptors/TcpFailureDetector.java b/java/org/apache/catalina/tribes/group/interceptors/TcpFailureDetector.java
index 05c61d0..12e7e8b 100644
--- a/java/org/apache/catalina/tribes/group/interceptors/TcpFailureDetector.java
+++ b/java/org/apache/catalina/tribes/group/interceptors/TcpFailureDetector.java
@@ -5,9 +5,9 @@
  * The ASF licenses this file to You 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.
@@ -52,37 +52,37 @@ import org.apache.catalina.tribes.membership.StaticMember;
  * <p>
  * The TcpFailureDetector works in two ways. <br>
  * 1. It intercepts memberDisappeared events
- * 2. It catches send errors 
+ * 2. It catches send errors
  * </p>
  *
  * @author Filip Hanik
  * @version 1.0
  */
 public class TcpFailureDetector extends ChannelInterceptorBase {
-    
+
     private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog( TcpFailureDetector.class );
-    
+
     protected static byte[] TCP_FAIL_DETECT = new byte[] {
         79, -89, 115, 72, 121, -126, 67, -55, -97, 111, -119, -128, -95, 91, 7, 20,
         125, -39, 82, 91, -21, -15, 67, -102, -73, 126, -66, -113, -127, 103, 30, -74,
         55, 21, -66, -121, 69, 126, 76, -88, -65, 10, 77, 19, 83, 56, 21, 50,
-        85, -10, -108, -73, 58, -6, 64, 120, -111, 4, 125, -41, 114, -124, -64, -43};      
+        85, -10, -108, -73, 58, -6, 64, 120, -111, 4, 125, -41, 114, -124, -64, -43};
 
     @Deprecated
     protected boolean performConnectTest = true;//Unused - will be removed in Tomcat 8.0.x
 
     protected long connectTimeout = 1000;//1 second default
-    
+
     protected boolean performSendTest = true;
 
     protected boolean performReadTest = false;
-    
+
     protected long readTestTimeout = 5000;//5 seconds
-    
+
     protected Membership membership = null;
-    
+
     protected HashMap<Member, Long> removeSuspects = new HashMap<Member, Long>();
-    
+
     protected HashMap<Member, Long> addSuspects = new HashMap<Member, Long>();
 
     protected int removeSuspectsTimeout = 300; // 5 minutes
@@ -94,7 +94,7 @@ public class TcpFailureDetector extends ChannelInterceptorBase {
         }catch ( ChannelException cx ) {
             FaultyMember[] mbrs = cx.getFaultyMembers();
             for ( int i=0; i<mbrs.length; i++ ) {
-                if ( mbrs[i].getCause()!=null &&  
+                if ( mbrs[i].getCause()!=null &&
                      (!(mbrs[i].getCause() instanceof RemoteProcessException)) ) {//RemoteProcessException's are ok
                     this.memberDisappeared(mbrs[i].getMember());
                 }//end if
@@ -105,20 +105,20 @@ public class TcpFailureDetector extends ChannelInterceptorBase {
 
     @Override
     public void messageReceived(ChannelMessage msg) {
-        //catch incoming 
+        //catch incoming
         boolean process = true;
         if ( okToProcess(msg.getOptions()) ) {
             //check to see if it is a testMessage, if so, process = false
             process = ( (msg.getMessage().getLength() != TCP_FAIL_DETECT.length) ||
                         (!Arrays.equals(TCP_FAIL_DETECT,msg.getMessage().getBytes()) ) );
         }//end if
-            
+
         //ignore the message, it doesnt have the flag set
         if ( process ) super.messageReceived(msg);
         else if ( log.isDebugEnabled() ) log.debug("Received a failure detector packet:"+msg);
     }//messageReceived
-    
-    
+
+
     @Override
     public void memberAdded(Member member) {
         if ( membership == null ) setupMembership();
@@ -146,7 +146,7 @@ public class TcpFailureDetector extends ChannelInterceptorBase {
         if ( membership == null ) setupMembership();
         boolean notify = false;
         boolean shutdown = Arrays.equals(member.getCommand(),Member.SHUTDOWN_PAYLOAD);
-        if ( !shutdown ) 
+        if ( !shutdown )
             if(log.isInfoEnabled())
                 log.info("Received memberDisappeared["+member+"] message. Will verify.");
         synchronized (membership) {
@@ -180,7 +180,7 @@ public class TcpFailureDetector extends ChannelInterceptorBase {
 
         }
     }
-    
+
     @Override
     public boolean hasMembers() {
         if ( membership == null ) setupMembership();
@@ -203,14 +203,14 @@ public class TcpFailureDetector extends ChannelInterceptorBase {
     public Member getLocalMember(boolean incAlive) {
         return super.getLocalMember(incAlive);
     }
-    
+
     @Override
     public void heartbeat() {
         super.heartbeat();
         checkMembers(false);
     }
     public void checkMembers(boolean checkAll) {
-        
+
         try {
             if (membership == null) setupMembership();
             synchronized (membership) {
@@ -220,10 +220,10 @@ public class TcpFailureDetector extends ChannelInterceptorBase {
         }catch ( Exception x ) {
             log.warn("Unable to perform heartbeat on the TcpFailureDetector.",x);
         } finally {
-            
+
         }
     }
-    
+
     protected void performForcedCheck() {
         //update all alive times
         Member[] members = super.getMembers();
@@ -278,9 +278,9 @@ public class TcpFailureDetector extends ChannelInterceptorBase {
             } else {
                 if (removeSuspectsTimeout > 0) {
                     long timeNow = System.currentTimeMillis();
-                    int timeIdle = (int) ((timeNow - removeSuspects.get(m)) / 1000L);
+                    int timeIdle = (int) ((timeNow - removeSuspects.get(m).longValue()) / 1000L);
                     if (timeIdle > removeSuspectsTimeout) {
-                        removeSuspects.remove(m); // remove suspect member 
+                        removeSuspects.remove(m); // remove suspect member
                     }
                 }
             }
@@ -300,26 +300,26 @@ public class TcpFailureDetector extends ChannelInterceptorBase {
             } //end if
         }
     }
-    
+
     protected synchronized void setupMembership() {
         if ( membership == null ) {
             membership = new Membership((MemberImpl)super.getLocalMember(true));
         }
-        
+
     }
-    
+
     protected boolean memberAlive(Member mbr) {
         return memberAlive(mbr,TCP_FAIL_DETECT,performSendTest,performReadTest,readTestTimeout,connectTimeout,getOptionFlag());
     }
-    
-    protected static boolean memberAlive(Member mbr, byte[] msgData, 
+
+    protected static boolean memberAlive(Member mbr, byte[] msgData,
                                          boolean sendTest, boolean readTest,
                                          long readTimeout, long conTimeout,
                                          int optionFlag) {
         //could be a shutdown notification
         if ( Arrays.equals(mbr.getCommand(),Member.SHUTDOWN_PAYLOAD) ) return false;
-        
-        Socket socket = new Socket();        
+
+        Socket socket = new Socket();
         try {
             InetAddress ia = InetAddress.getByAddress(mbr.getHost());
             InetSocketAddress addr = new InetSocketAddress(ia, mbr.getPort());
diff --git a/java/org/apache/catalina/tribes/group/interceptors/TwoPhaseCommitInterceptor.java b/java/org/apache/catalina/tribes/group/interceptors/TwoPhaseCommitInterceptor.java
index 7c6b584..e1bb848 100644
--- a/java/org/apache/catalina/tribes/group/interceptors/TwoPhaseCommitInterceptor.java
+++ b/java/org/apache/catalina/tribes/group/interceptors/TwoPhaseCommitInterceptor.java
@@ -111,11 +111,12 @@ public class TwoPhaseCommitInterceptor extends ChannelInterceptorBase {
     public void setExpire(long expire) {
         this.expire = expire;
     }
-    
+
     @Override
     public void heartbeat() {
         try {
             long now = System.currentTimeMillis();
+            @SuppressWarnings("unchecked")
             Map.Entry<UniqueId,MapEntry>[] entries = messages.entrySet().toArray(new Map.Entry[messages.size()]);
             for (int i=0; i<entries.length; i++ ) {
                 MapEntry entry = entries[i].getValue();
@@ -131,12 +132,12 @@ public class TwoPhaseCommitInterceptor extends ChannelInterceptorBase {
             super.heartbeat();
         }
     }
-    
+
     public static class MapEntry {
         public ChannelMessage msg;
         public UniqueId id;
         public long timestamp;
-        
+
         public MapEntry(ChannelMessage msg, UniqueId id, long timestamp) {
             this.msg = msg;
             this.id = id;
diff --git a/java/org/apache/catalina/tribes/membership/McastService.java b/java/org/apache/catalina/tribes/membership/McastService.java
index 5f67081..3dfe47a 100644
--- a/java/org/apache/catalina/tribes/membership/McastService.java
+++ b/java/org/apache/catalina/tribes/membership/McastService.java
@@ -41,7 +41,7 @@ import org.apache.catalina.tribes.util.UUIDGenerator;
  * If a node fails to send out a heartbeat, the node will be dismissed.
  *
  * @author Filip Hanik
- * @version $Id: McastService.java 1142672 2011-07-04 14:02:36Z kkolinko $
+ * @version $Id: McastService.java 1506793 2013-07-25 01:41:20Z kfujino $
  */
 
 
@@ -697,6 +697,8 @@ public class McastService implements MembershipService,MembershipListener,Messag
         p.setProperty("mcastFrequency","500");
         p.setProperty("tcpListenPort","4000");
         p.setProperty("tcpListenHost","127.0.0.1");
+        p.setProperty("tcpSecurePort","4100");
+        p.setProperty("udpListenPort","4200");
         service.setProperties(p);
         service.start();
         Thread.sleep(60*1000*60);
diff --git a/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java b/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
index 2d8791f..ff70c31 100644
--- a/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
+++ b/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
@@ -53,6 +53,7 @@ import org.apache.juli.logging.LogFactory;
  * @author Filip Hanik
  * @version 1.0
  */
+ at SuppressWarnings("rawtypes")
 public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements RpcCallback, ChannelListener, MembershipListener, Heartbeat {
     private static final long serialVersionUID = 1L;
 
@@ -171,9 +172,10 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
                                  int initialCapacity,
                                  float loadFactor,
                                  int channelSendOptions,
-                                 ClassLoader[] cls) {
+                                 ClassLoader[] cls,
+                                 boolean terminate) {
         super(initialCapacity, loadFactor, 15);
-        init(owner, channel, mapContextName, timeout, channelSendOptions, cls);
+        init(owner, channel, mapContextName, timeout, channelSendOptions, cls, terminate);
 
     }
 
@@ -197,7 +199,8 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
      * @param channelSendOptions int
      * @param cls ClassLoader[]
      */
-    protected void init(MapOwner owner, Channel channel, String mapContextName, long timeout, int channelSendOptions,ClassLoader[] cls) {
+    protected void init(MapOwner owner, Channel channel, String mapContextName,
+            long timeout, int channelSendOptions,ClassLoader[] cls, boolean terminate) {
         log.info("Initializing AbstractReplicatedMap with context name:"+mapContextName);
         this.mapOwner = owner;
         this.externalLoaders = cls;
@@ -227,11 +230,10 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
             broadcast(MapMessage.MSG_START, true);
         } catch (ChannelException x) {
             log.warn("Unable to send map start message.");
-            // remove listener from channel
-            this.rpcChannel.breakdown();
-            this.channel.removeChannelListener(this);
-            this.channel.removeMembershipListener(this);
-            throw new RuntimeException("Unable to start replicated map.",x);
+            if (terminate) {
+                breakdown();
+                throw new RuntimeException("Unable to start replicated map.",x);
+            }
         }
     }
 
@@ -466,6 +468,7 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
      * @param complete boolean
      */
     public void replicate(boolean complete) {
+        @SuppressWarnings("unchecked")
         Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
         while (i.hasNext()) {
             Map.Entry<?,?> e = i.next();
@@ -540,6 +543,7 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
         if (mapmsg.getMsgType() == MapMessage.MSG_STATE || mapmsg.getMsgType() == MapMessage.MSG_STATE_COPY) {
             synchronized (stateMutex) { //make sure we dont do two things at the same time
                 ArrayList<MapMessage> list = new ArrayList<MapMessage>();
+                @SuppressWarnings("unchecked")
                 Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
                 while (i.hasNext()) {
                     Map.Entry<?,?> e = i.next();
@@ -588,6 +592,7 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
         }
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     public void messageReceived(Serializable msg, Member sender) {
         if (! (msg instanceof MapMessage)) return;
@@ -714,6 +719,7 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
         }
         if ( memberAdded ) {
             synchronized (stateMutex) {
+                @SuppressWarnings("unchecked")
                 Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
                 while (i.hasNext()) {
                     Map.Entry<?,?> e = i.next();
@@ -768,6 +774,7 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
             }
         }
 
+        @SuppressWarnings("unchecked")
         Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
         while (i.hasNext()) {
             Map.Entry<?,?> e = i.next();
@@ -948,6 +955,7 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
             for ( int i=0; i<mbrs.length;i++ ) {
                 System.out.println("Mbr["+(i+1)+"="+mbrs[i].getName());
             }
+            @SuppressWarnings("unchecked")
             Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
             int cnt = 0;
 
@@ -978,6 +986,7 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
             return put(key,value,true);
         }
 
+        @SuppressWarnings("unchecked")
         public Object put(Object key, Object value, boolean notify) {
             MapEntry entry = new MapEntry(key,value);
             entry.setBackup(false);
@@ -1007,6 +1016,7 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
          */
         @Override
         public void putAll(Map m) {
+            @SuppressWarnings("unchecked")
             Iterator<Map.Entry<?,?>> i = m.entrySet().iterator();
             while ( i.hasNext() ) {
                 Map.Entry<?,?> entry = i.next();
@@ -1035,6 +1045,7 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
             if ( value == null ) {
                 return super.containsValue(value);
             } else {
+                @SuppressWarnings("unchecked")
                 Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
                 while (i.hasNext()) {
                     Map.Entry<?,?> e = i.next();
@@ -1071,6 +1082,7 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
         @Override
         public Set<MapEntry> entrySet() {
             LinkedHashSet<MapEntry> set = new LinkedHashSet<MapEntry>(super.size());
+            @SuppressWarnings("unchecked")
             Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
             while ( i.hasNext() ) {
                 Map.Entry<?,?> e = i.next();
@@ -1088,6 +1100,7 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
             //todo implement
             //should only return keys where this is active.
             LinkedHashSet<Object> set = new LinkedHashSet<Object>(super.size());
+            @SuppressWarnings("unchecked")
             Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
             while ( i.hasNext() ) {
                 Map.Entry<?,?> e = i.next();
@@ -1105,6 +1118,7 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
             //todo, implement a counter variable instead
             //only count active members in this node
             int counter = 0;
+            @SuppressWarnings("unchecked")
             Iterator<Map.Entry<?,?>> it = super.entrySet().iterator();
             while (it!=null && it.hasNext() ) {
                 Map.Entry<?,?> e = it.next();
@@ -1124,6 +1138,7 @@ public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements
         @Override
         public Collection<Object> values() {
             ArrayList<Object> values = new ArrayList<Object>();
+            @SuppressWarnings("unchecked")
             Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
             while ( i.hasNext() ) {
                 Map.Entry<?,?> e = i.next();
diff --git a/java/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java b/java/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java
index a5c8294..a298653 100644
--- a/java/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java
+++ b/java/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java
@@ -72,37 +72,49 @@ public class LazyReplicatedMap extends AbstractReplicatedMap {
 //              CONSTRUCTORS / DESTRUCTORS
 //------------------------------------------------------------------------------   
     /**
-         * Creates a new map
-         * @param channel The channel to use for communication
-         * @param timeout long - timeout for RPC messags
-         * @param mapContextName String - unique name for this map, to allow multiple maps per channel
-         * @param initialCapacity int - the size of this map, see HashMap
-         * @param loadFactor float - load factor, see HashMap
-         */
-        public LazyReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, int initialCapacity, float loadFactor, ClassLoader[] cls) {
-            super(owner,channel,timeout,mapContextName,initialCapacity,loadFactor, Channel.SEND_OPTIONS_DEFAULT,cls);
-        }
+     * Creates a new map
+     * @param channel The channel to use for communication
+     * @param timeout long - timeout for RPC messags
+     * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+     * @param initialCapacity int - the size of this map, see HashMap
+     * @param loadFactor float - load factor, see HashMap
+     */
+    public LazyReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, int initialCapacity, float loadFactor, ClassLoader[] cls) {
+        super(owner,channel,timeout,mapContextName,initialCapacity,loadFactor, Channel.SEND_OPTIONS_DEFAULT,cls, true);
+    }
+
+    /**
+     * Creates a new map
+     * @param channel The channel to use for communication
+     * @param timeout long - timeout for RPC messags
+     * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+     * @param initialCapacity int - the size of this map, see HashMap
+     */
+    public LazyReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, int initialCapacity, ClassLoader[] cls) {
+        super(owner, channel,timeout,mapContextName,initialCapacity, AbstractReplicatedMap.DEFAULT_LOAD_FACTOR, Channel.SEND_OPTIONS_DEFAULT, cls, true);
+    }
 
-        /**
-         * Creates a new map
-         * @param channel The channel to use for communication
-         * @param timeout long - timeout for RPC messags
-         * @param mapContextName String - unique name for this map, to allow multiple maps per channel
-         * @param initialCapacity int - the size of this map, see HashMap
-         */
-        public LazyReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, int initialCapacity, ClassLoader[] cls) {
-            super(owner, channel,timeout,mapContextName,initialCapacity, AbstractReplicatedMap.DEFAULT_LOAD_FACTOR, Channel.SEND_OPTIONS_DEFAULT, cls);
-        }
+    /**
+     * Creates a new map
+     * @param channel The channel to use for communication
+     * @param timeout long - timeout for RPC messags
+     * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+     */
+    public LazyReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, ClassLoader[] cls) {
+        super(owner, channel,timeout,mapContextName, AbstractReplicatedMap.DEFAULT_INITIAL_CAPACITY,AbstractReplicatedMap.DEFAULT_LOAD_FACTOR,Channel.SEND_OPTIONS_DEFAULT, cls, true);
+    }
 
-        /**
-         * Creates a new map
-         * @param channel The channel to use for communication
-         * @param timeout long - timeout for RPC messags
-         * @param mapContextName String - unique name for this map, to allow multiple maps per channel
-         */
-        public LazyReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, ClassLoader[] cls) {
-            super(owner, channel,timeout,mapContextName, AbstractReplicatedMap.DEFAULT_INITIAL_CAPACITY,AbstractReplicatedMap.DEFAULT_LOAD_FACTOR,Channel.SEND_OPTIONS_DEFAULT, cls);
-        }
+    /**
+     * Creates a new map
+     * @param channel The channel to use for communication
+     * @param timeout long - timeout for RPC messags
+     * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+     * @param terminate boolean - Flag for whether to terminate this map that failed to start.
+     */
+    public LazyReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, ClassLoader[] cls, boolean terminate) {
+        super(owner, channel,timeout,mapContextName, AbstractReplicatedMap.DEFAULT_INITIAL_CAPACITY,
+                AbstractReplicatedMap.DEFAULT_LOAD_FACTOR,Channel.SEND_OPTIONS_DEFAULT, cls, terminate);
+    }
 
 
 //------------------------------------------------------------------------------    
diff --git a/java/org/apache/catalina/tribes/tipis/ReplicatedMap.java b/java/org/apache/catalina/tribes/tipis/ReplicatedMap.java
index 7076c57..d50e17e 100644
--- a/java/org/apache/catalina/tribes/tipis/ReplicatedMap.java
+++ b/java/org/apache/catalina/tribes/tipis/ReplicatedMap.java
@@ -62,7 +62,7 @@ public class ReplicatedMap extends AbstractReplicatedMap {
      * @param loadFactor float - load factor, see HashMap
      */
     public ReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, int initialCapacity,float loadFactor, ClassLoader[] cls) {
-        super(owner,channel, timeout, mapContextName, initialCapacity, loadFactor, Channel.SEND_OPTIONS_DEFAULT, cls);
+        super(owner,channel, timeout, mapContextName, initialCapacity, loadFactor, Channel.SEND_OPTIONS_DEFAULT, cls, true);
     }
 
     /**
@@ -73,7 +73,7 @@ public class ReplicatedMap extends AbstractReplicatedMap {
      * @param initialCapacity int - the size of this map, see HashMap
      */
     public ReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, int initialCapacity, ClassLoader[] cls) {
-        super(owner,channel, timeout, mapContextName, initialCapacity, AbstractReplicatedMap.DEFAULT_LOAD_FACTOR,Channel.SEND_OPTIONS_DEFAULT, cls);
+        super(owner,channel, timeout, mapContextName, initialCapacity, AbstractReplicatedMap.DEFAULT_LOAD_FACTOR,Channel.SEND_OPTIONS_DEFAULT, cls, true);
     }
 
     /**
@@ -83,7 +83,19 @@ public class ReplicatedMap extends AbstractReplicatedMap {
      * @param mapContextName String - unique name for this map, to allow multiple maps per channel
      */
     public ReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, ClassLoader[] cls) {
-        super(owner, channel, timeout, mapContextName,AbstractReplicatedMap.DEFAULT_INITIAL_CAPACITY, AbstractReplicatedMap.DEFAULT_LOAD_FACTOR, Channel.SEND_OPTIONS_DEFAULT, cls);
+        super(owner, channel, timeout, mapContextName,AbstractReplicatedMap.DEFAULT_INITIAL_CAPACITY, AbstractReplicatedMap.DEFAULT_LOAD_FACTOR, Channel.SEND_OPTIONS_DEFAULT, cls, true);
+    }
+
+    /**
+     * Creates a new map
+     * @param channel The channel to use for communication
+     * @param timeout long - timeout for RPC messags
+     * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+     * @param terminate boolean - Flag for whether to terminate this map that failed to start.
+     */
+    public ReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, ClassLoader[] cls, boolean terminate) {
+        super(owner, channel, timeout, mapContextName,AbstractReplicatedMap.DEFAULT_INITIAL_CAPACITY,
+                AbstractReplicatedMap.DEFAULT_LOAD_FACTOR, Channel.SEND_OPTIONS_DEFAULT, cls, terminate);
     }
 
 //------------------------------------------------------------------------------
diff --git a/java/org/apache/catalina/tribes/transport/PooledSender.java b/java/org/apache/catalina/tribes/transport/PooledSender.java
index e2af805..cd0c620 100644
--- a/java/org/apache/catalina/tribes/transport/PooledSender.java
+++ b/java/org/apache/catalina/tribes/transport/PooledSender.java
@@ -148,16 +148,11 @@ public abstract class PooledSender extends AbstractSender implements MultiPointS
         public void setLimit(int limit) {
             this.limit = limit;
         }
-        /**
-         * @return
-         */
+
         public int getInUsePoolSize() {
             return inuse.size();
         }
 
-        /**
-         * @return
-         */
         public int getInPoolSize() {
             return notinuse.size();
         }
diff --git a/java/org/apache/catalina/tribes/util/TcclThreadFactory.java b/java/org/apache/catalina/tribes/util/TcclThreadFactory.java
index a1959f9..880cecc 100644
--- a/java/org/apache/catalina/tribes/util/TcclThreadFactory.java
+++ b/java/org/apache/catalina/tribes/util/TcclThreadFactory.java
@@ -39,9 +39,13 @@ public class TcclThreadFactory implements ThreadFactory {
     private final String namePrefix;
     
     public TcclThreadFactory() {
+        this("pool-" + poolNumber.getAndIncrement() + "-thread-");
+    }
+
+    public TcclThreadFactory(String namePrefix) {
         SecurityManager s = System.getSecurityManager();
         group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
-        namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
+        this.namePrefix = namePrefix;
     }
 
     @Override
diff --git a/java/org/apache/catalina/util/ParameterMap.java b/java/org/apache/catalina/util/ParameterMap.java
index d4633e9..7020a0d 100644
--- a/java/org/apache/catalina/util/ParameterMap.java
+++ b/java/org/apache/catalina/util/ParameterMap.java
@@ -14,17 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
 package org.apache.catalina.util;
 
-
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 import org.apache.tomcat.util.res.StringManager;
 
-
 /**
  * Extended implementation of <strong>HashMap</strong> that includes a
  * <code>locked</code> property.  This class can be used to safely expose
@@ -33,10 +29,9 @@ import org.apache.tomcat.util.res.StringManager;
  * <code>ParmaeterMap</code> instance is not locked.
  *
  * @author Craig R. McClanahan
- * @version $Id: ParameterMap.java 1038846 2010-11-24 22:08:38Z markt $
+ * @version $Id: ParameterMap.java 1525881 2013-09-24 13:22:49Z markt $
  */
-
-public final class ParameterMap<K,V> extends HashMap<K,V> {
+public final class ParameterMap<K,V> extends LinkedHashMap<K,V> {
 
     private static final long serialVersionUID = 1L;
 
diff --git a/java/org/apache/catalina/valves/ErrorReportValve.java b/java/org/apache/catalina/valves/ErrorReportValve.java
index 7681554..c3770cc 100644
--- a/java/org/apache/catalina/valves/ErrorReportValve.java
+++ b/java/org/apache/catalina/valves/ErrorReportValve.java
@@ -30,6 +30,7 @@ import org.apache.catalina.connector.Response;
 import org.apache.catalina.util.RequestUtil;
 import org.apache.catalina.util.ServerInfo;
 import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
 
 /**
  * <p>Implementation of a Valve that outputs HTML error pages.</p>
@@ -44,7 +45,7 @@ import org.apache.tomcat.util.ExceptionUtils;
  * @author <a href="mailto:nicolaken at supereva.it">Nicola Ken Barozzi</a> Aisa
  * @author <a href="mailto:stefano at apache.org">Stefano Mazzocchi</a>
  * @author Yoav Shapira
- * @version $Id: ErrorReportValve.java 1498384 2013-07-01 11:18:54Z violetagg $
+ * @version $Id: ErrorReportValve.java 1514496 2013-08-15 21:11:58Z markt $
  */
 
 public class ErrorReportValve extends ValveBase {
@@ -182,8 +183,11 @@ public class ErrorReportValve extends ValveBase {
         // Do nothing if there is no report for the specified status code and
         // no error message provided
         String report = null;
+        StringManager smClient = StringManager.getManager(
+                Constants.Package, request.getLocales());
+        response.setLocale(smClient.getLocale());
         try {
-            report = sm.getString("http." + statusCode);
+            report = smClient.getString("http." + statusCode);
         } catch (Throwable t) {
             ExceptionUtils.handleThrowable(t);
         }
@@ -191,7 +195,7 @@ public class ErrorReportValve extends ValveBase {
             if (message.length() == 0) {
                 return;
             } else {
-                report = sm.getString("errorReportValve.noDescription");
+                report = smClient.getString("errorReportValve.noDescription");
             }
         }
 
@@ -199,29 +203,29 @@ public class ErrorReportValve extends ValveBase {
 
         sb.append("<html><head><title>");
         sb.append(ServerInfo.getServerInfo()).append(" - ");
-        sb.append(sm.getString("errorReportValve.errorReport"));
+        sb.append(smClient.getString("errorReportValve.errorReport"));
         sb.append("</title>");
         sb.append("<style><!--");
         sb.append(org.apache.catalina.util.TomcatCSS.TOMCAT_CSS);
         sb.append("--></style> ");
         sb.append("</head><body>");
         sb.append("<h1>");
-        sb.append(sm.getString("errorReportValve.statusHeader",
+        sb.append(smClient.getString("errorReportValve.statusHeader",
                                "" + statusCode, message)).append("</h1>");
         sb.append("<HR size=\"1\" noshade=\"noshade\">");
         sb.append("<p><b>type</b> ");
         if (throwable != null) {
-            sb.append(sm.getString("errorReportValve.exceptionReport"));
+            sb.append(smClient.getString("errorReportValve.exceptionReport"));
         } else {
-            sb.append(sm.getString("errorReportValve.statusReport"));
+            sb.append(smClient.getString("errorReportValve.statusReport"));
         }
         sb.append("</p>");
         sb.append("<p><b>");
-        sb.append(sm.getString("errorReportValve.message"));
+        sb.append(smClient.getString("errorReportValve.message"));
         sb.append("</b> <u>");
         sb.append(message).append("</u></p>");
         sb.append("<p><b>");
-        sb.append(sm.getString("errorReportValve.description"));
+        sb.append(smClient.getString("errorReportValve.description"));
         sb.append("</b> <u>");
         sb.append(report);
         sb.append("</u></p>");
@@ -230,7 +234,7 @@ public class ErrorReportValve extends ValveBase {
 
             String stackTrace = getPartialServletStackTrace(throwable);
             sb.append("<p><b>");
-            sb.append(sm.getString("errorReportValve.exception"));
+            sb.append(smClient.getString("errorReportValve.exception"));
             sb.append("</b> <pre>");
             sb.append(RequestUtil.filter(stackTrace));
             sb.append("</pre></p>");
@@ -240,7 +244,7 @@ public class ErrorReportValve extends ValveBase {
             while (rootCause != null && (loops < 10)) {
                 stackTrace = getPartialServletStackTrace(rootCause);
                 sb.append("<p><b>");
-                sb.append(sm.getString("errorReportValve.rootCause"));
+                sb.append(smClient.getString("errorReportValve.rootCause"));
                 sb.append("</b> <pre>");
                 sb.append(RequestUtil.filter(stackTrace));
                 sb.append("</pre></p>");
@@ -250,9 +254,9 @@ public class ErrorReportValve extends ValveBase {
             }
 
             sb.append("<p><b>");
-            sb.append(sm.getString("errorReportValve.note"));
+            sb.append(smClient.getString("errorReportValve.note"));
             sb.append("</b> <u>");
-            sb.append(sm.getString("errorReportValve.rootCauseInLogs",
+            sb.append(smClient.getString("errorReportValve.rootCauseInLogs",
                                    ServerInfo.getServerInfo()));
             sb.append("</u></p>");
 
diff --git a/java/org/apache/catalina/websocket/Constants.java b/java/org/apache/catalina/websocket/Constants.java
index 4cff021..14ccaa5 100644
--- a/java/org/apache/catalina/websocket/Constants.java
+++ b/java/org/apache/catalina/websocket/Constants.java
@@ -18,7 +18,10 @@ package org.apache.catalina.websocket;
 
 /**
  * Constants for this Java package.
+ * 
+ * @deprecated  Will be removed in Tomcat 8.0.x.
  */
+ at Deprecated
 public class Constants {
     public static final String Package = "org.apache.catalina.websocket";
 
diff --git a/java/org/apache/catalina/websocket/MessageInbound.java b/java/org/apache/catalina/websocket/MessageInbound.java
index 3462dbc..5a0fc77 100644
--- a/java/org/apache/catalina/websocket/MessageInbound.java
+++ b/java/org/apache/catalina/websocket/MessageInbound.java
@@ -29,7 +29,10 @@ import org.apache.tomcat.util.res.StringManager;
  * on messages. Applications should extend this class to provide application
  * specific functionality. Applications that wish to operate on a stream basis
  * rather than a message basis should use {@link StreamInbound}.
+ * 
+ * @deprecated  Will be removed in Tomcat 8.0.x.
  */
+ at Deprecated
 public abstract class MessageInbound extends StreamInbound {
 
     private static final StringManager sm =
diff --git a/java/org/apache/catalina/websocket/StreamInbound.java b/java/org/apache/catalina/websocket/StreamInbound.java
index 2cea2f3..0f4829a 100644
--- a/java/org/apache/catalina/websocket/StreamInbound.java
+++ b/java/org/apache/catalina/websocket/StreamInbound.java
@@ -35,7 +35,10 @@ import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
  * on streams. Applications should extend this class to provide application
  * specific functionality. Applications that wish to operate on a message basis
  * rather than a stream basis should use {@link MessageInbound}.
+ * 
+ * @deprecated  Will be removed in Tomcat 8.0.x.
  */
+ at Deprecated
 public abstract class StreamInbound implements UpgradeInbound {
 
     private final ClassLoader applicationClassLoader;
diff --git a/java/org/apache/catalina/websocket/WebSocketServlet.java b/java/org/apache/catalina/websocket/WebSocketServlet.java
index 5b37306..0f4861f 100644
--- a/java/org/apache/catalina/websocket/WebSocketServlet.java
+++ b/java/org/apache/catalina/websocket/WebSocketServlet.java
@@ -42,7 +42,10 @@ import org.apache.tomcat.util.res.StringManager;
  * Provides the base implementation of a Servlet for processing WebSocket
  * connections as per RFC6455. It is expected that applications will extend this
  * implementation and provide application specific functionality.
+ * 
+ * @deprecated  Will be removed in Tomcat 8.0.x.
  */
+ at Deprecated
 public abstract class WebSocketServlet extends HttpServlet {
 
     private static final long serialVersionUID = 1L;
diff --git a/java/org/apache/catalina/websocket/WsFrame.java b/java/org/apache/catalina/websocket/WsFrame.java
index 4573b26..2ecfaaf 100644
--- a/java/org/apache/catalina/websocket/WsFrame.java
+++ b/java/org/apache/catalina/websocket/WsFrame.java
@@ -30,7 +30,10 @@ import org.apache.tomcat.util.res.StringManager;
 /**
  * Represents a complete WebSocket frame with the exception of the payload for
  * non-control frames.
+ * 
+ * @deprecated  Will be removed in Tomcat 8.0.x.
  */
+ at Deprecated
 public class WsFrame {
 
     private static final StringManager sm =
diff --git a/java/org/apache/catalina/websocket/WsHttpServletRequestWrapper.java b/java/org/apache/catalina/websocket/WsHttpServletRequestWrapper.java
index a06f1e3..01c0cfe 100644
--- a/java/org/apache/catalina/websocket/WsHttpServletRequestWrapper.java
+++ b/java/org/apache/catalina/websocket/WsHttpServletRequestWrapper.java
@@ -44,7 +44,10 @@ import org.apache.tomcat.util.res.StringManager;
 /**
  * Wrapper for the HttpServletRequest object that allows the underlying request
  * object to be invalidated.
+ * 
+ * @deprecated  Will be removed in Tomcat 8.0.x.
  */
+ at Deprecated
 public class WsHttpServletRequestWrapper implements HttpServletRequest {
 
     private static final StringManager sm =
diff --git a/java/org/apache/catalina/websocket/WsInputStream.java b/java/org/apache/catalina/websocket/WsInputStream.java
index af077e2..a0d4e9b 100644
--- a/java/org/apache/catalina/websocket/WsInputStream.java
+++ b/java/org/apache/catalina/websocket/WsInputStream.java
@@ -27,7 +27,10 @@ import org.apache.tomcat.util.res.StringManager;
  * makes the payload available for reading as an {@link InputStream}. It only
  * makes the number of bytes declared in the payload length available for
  * reading even if more bytes are available from the socket.
+ * 
+ * @deprecated  Will be removed in Tomcat 8.0.x.
  */
+ at Deprecated
 public class WsInputStream extends InputStream {
 
     private static final StringManager sm =
diff --git a/java/org/apache/catalina/websocket/WsOutbound.java b/java/org/apache/catalina/websocket/WsOutbound.java
index 7e0c922..427506a 100644
--- a/java/org/apache/catalina/websocket/WsOutbound.java
+++ b/java/org/apache/catalina/websocket/WsOutbound.java
@@ -31,13 +31,23 @@ import org.apache.tomcat.util.res.StringManager;
  * that write to the client (or update a buffer that is later written to the
  * client) are synchronized to prevent multiple threads trying to write to the
  * client at the same time.
+ * 
+ * @deprecated  Will be removed in Tomcat 8.0.x.
  */
+ at Deprecated
 public class WsOutbound {
 
     private static final StringManager sm =
             StringManager.getManager(Constants.Package);
     public static final int DEFAULT_BUFFER_SIZE = 8192;
 
+    /**
+     * This state lock is used rather than synchronized methods to allow error
+     * handling to be managed outside of the synchronization else deadlocks may
+     * occur such as https://issues.apache.org/bugzilla/show_bug.cgi?id=55524
+     */
+    private final Object stateLock = new Object();
+    
     private UpgradeOutbound upgradeOutbound;
     private StreamInbound streamInbound;
     private ByteBuffer bb;
@@ -75,22 +85,34 @@ public class WsOutbound {
      * @throws IOException  If a flush is required and an error occurs writing
      *                      the WebSocket frame to the client
      */
-    public synchronized void writeBinaryData(int b) throws IOException {
-        if (closed) {
-            throw new IOException(sm.getString("outbound.closed"));
-        }
-
-        if (bb.position() == bb.capacity()) {
-            doFlush(false);
-        }
-        if (text == null) {
-            text = Boolean.FALSE;
-        } else if (text == Boolean.TRUE) {
-            // Flush the character data
-            flush();
-            text = Boolean.FALSE;
+    public void writeBinaryData(int b) throws IOException {
+        try {
+            synchronized (stateLock) {
+                if (closed) {
+                    throw new IOException(sm.getString("outbound.closed"));
+                }
+        
+                if (bb.position() == bb.capacity()) {
+                    doFlush(false);
+                }
+                if (text == null) {
+                    text = Boolean.FALSE;
+                } else if (text == Boolean.TRUE) {
+                    // Flush the character data
+                    flush();
+                    text = Boolean.FALSE;
+                }
+                bb.put((byte) (b & 0xFF));
+            }
+        } catch (IOException ioe) {
+            // Any IOException is terminal. Make sure the inbound side knows
+            // that something went wrong.
+            // The exception handling needs to be outside of the sync to avoid
+            // possible deadlocks (e.g. BZ55524) when triggering the inbound
+            // close as that will execute user code
+            streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY);
+            throw ioe;
         }
-        bb.put((byte) (b & 0xFF));
     }
 
 
@@ -105,23 +127,35 @@ public class WsOutbound {
      * @throws IOException  If a flush is required and an error occurs writing
      *                      the WebSocket frame to the client
      */
-    public synchronized void writeTextData(char c) throws IOException {
-        if (closed) {
-            throw new IOException(sm.getString("outbound.closed"));
-        }
-
-        if (cb.position() == cb.capacity()) {
-            doFlush(false);
-        }
-
-        if (text == null) {
-            text = Boolean.TRUE;
-        } else if (text == Boolean.FALSE) {
-            // Flush the binary data
-            flush();
-            text = Boolean.TRUE;
+    public void writeTextData(char c) throws IOException {
+        try {
+            synchronized (stateLock) {
+                if (closed) {
+                    throw new IOException(sm.getString("outbound.closed"));
+                }
+        
+                if (cb.position() == cb.capacity()) {
+                    doFlush(false);
+                }
+        
+                if (text == null) {
+                    text = Boolean.TRUE;
+                } else if (text == Boolean.FALSE) {
+                    // Flush the binary data
+                    flush();
+                    text = Boolean.TRUE;
+                }
+                cb.append(c);
+            }
+        } catch (IOException ioe) {
+            // Any IOException is terminal. Make sure the Inbound side knows
+            // that something went wrong.
+            // The exception handling needs to be outside of the sync to avoid
+            // possible deadlocks (e.g. BZ55524) when triggering the inbound
+            // close as that will execute user code
+            streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY);
+            throw ioe;
         }
-        cb.append(c);
     }
 
 
@@ -134,19 +168,30 @@ public class WsOutbound {
      *
      * @throws IOException  If an error occurs writing to the client
      */
-    public synchronized void writeBinaryMessage(ByteBuffer msgBb)
-            throws IOException {
-
-        if (closed) {
-            throw new IOException(sm.getString("outbound.closed"));
-        }
+    public void writeBinaryMessage(ByteBuffer msgBb) throws IOException {
 
-        if (text != null) {
-            // Empty the buffer
-            flush();
+        try {
+            synchronized (stateLock) {
+                if (closed) {
+                    throw new IOException(sm.getString("outbound.closed"));
+                }
+        
+                if (text != null) {
+                    // Empty the buffer
+                    flush();
+                }
+                text = Boolean.FALSE;
+                doWriteBytes(msgBb, true);
+            }
+        } catch (IOException ioe) {
+            // Any IOException is terminal. Make sure the Inbound side knows
+            // that something went wrong.
+            // The exception handling needs to be outside of the sync to avoid
+            // possible deadlocks (e.g. BZ55524) when triggering the inbound
+            // close as that will execute user code
+            streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY);
+            throw ioe;
         }
-        text = Boolean.FALSE;
-        doWriteBytes(msgBb, true);
     }
 
 
@@ -159,19 +204,30 @@ public class WsOutbound {
      *
      * @throws IOException  If an error occurs writing to the client
      */
-    public synchronized void writeTextMessage(CharBuffer msgCb)
-            throws IOException {
+    public void writeTextMessage(CharBuffer msgCb) throws IOException {
 
-        if (closed) {
-            throw new IOException(sm.getString("outbound.closed"));
-        }
-
-        if (text != null) {
-            // Empty the buffer
-            flush();
+        try {
+            synchronized (stateLock) {
+                if (closed) {
+                    throw new IOException(sm.getString("outbound.closed"));
+                }
+        
+                if (text != null) {
+                    // Empty the buffer
+                    flush();
+                }
+                text = Boolean.TRUE;
+                doWriteText(msgCb, true);
+            }
+        } catch (IOException ioe) {
+            // Any IOException is terminal. Make sure the Inbound side knows
+            // that something went wrong.
+            // The exception handling needs to be outside of the sync to avoid
+            // possible deadlocks (e.g. BZ55524) when triggering the inbound
+            // close as that will execute user code
+            streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY);
+            throw ioe;
         }
-        text = Boolean.TRUE;
-        doWriteText(msgCb, true);
     }
 
 
@@ -180,11 +236,23 @@ public class WsOutbound {
      *
      * @throws IOException  If an error occurs writing to the client
      */
-    public synchronized void flush() throws IOException {
-        if (closed) {
-            throw new IOException(sm.getString("outbound.closed"));
+    public void flush() throws IOException {
+        try {
+            synchronized (stateLock) {
+                if (closed) {
+                    throw new IOException(sm.getString("outbound.closed"));
+                }
+                doFlush(true);
+            }
+        } catch (IOException ioe) {
+            // Any IOException is terminal. Make sure the Inbound side knows
+            // that something went wrong.
+            // The exception handling needs to be outside of the sync to avoid
+            // possible deadlocks (e.g. BZ55524) when triggering the inbound
+            // close as that will execute user code
+            streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY);
+            throw ioe;
         }
-        doFlush(true);
     }
 
 
@@ -267,39 +335,50 @@ public class WsOutbound {
      *
      * @throws IOException  If an error occurs writing to the client
      */
-    public synchronized void close(int status, ByteBuffer data)
-            throws IOException {
+    public void close(int status, ByteBuffer data) throws IOException {
 
-        if (closed) {
-            return;
-        }
-
-        // Send any partial data we have
         try {
-            doFlush(false);
-        } finally {
-            closed = true;
-        }
-
-        upgradeOutbound.write(0x88);
-        if (status == 0) {
-            upgradeOutbound.write(0);
-        } else if (data == null || data.position() == data.limit()) {
-            upgradeOutbound.write(2);
-            upgradeOutbound.write(status >>> 8);
-            upgradeOutbound.write(status);
-        } else {
-            upgradeOutbound.write(2 + data.limit() - data.position());
-            upgradeOutbound.write(status >>> 8);
-            upgradeOutbound.write(status);
-            upgradeOutbound.write(data.array(), data.position(),
-                    data.limit() - data.position());
+            synchronized (stateLock) {
+                if (closed) {
+                    return;
+                }
+        
+                // Send any partial data we have
+                try {
+                    doFlush(false);
+                } finally {
+                    closed = true;
+                }
+        
+                upgradeOutbound.write(0x88);
+                if (status == 0) {
+                    upgradeOutbound.write(0);
+                } else if (data == null || data.position() == data.limit()) {
+                    upgradeOutbound.write(2);
+                    upgradeOutbound.write(status >>> 8);
+                    upgradeOutbound.write(status);
+                } else {
+                    upgradeOutbound.write(2 + data.limit() - data.position());
+                    upgradeOutbound.write(status >>> 8);
+                    upgradeOutbound.write(status);
+                    upgradeOutbound.write(data.array(), data.position(),
+                            data.limit() - data.position());
+                }
+                upgradeOutbound.flush();
+        
+                bb = null;
+                cb = null;
+                upgradeOutbound = null;
+            }
+        } catch (IOException ioe) {
+            // Any IOException is terminal. Make sure the Inbound side knows
+            // that something went wrong.
+            // The exception handling needs to be outside of the sync to avoid
+            // possible deadlocks (e.g. BZ55524) when triggering the inbound
+            // close as that will execute user code
+            streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY);
+            throw ioe;
         }
-        upgradeOutbound.flush();
-
-        bb = null;
-        cb = null;
-        upgradeOutbound = null;
     }
 
 
@@ -310,7 +389,7 @@ public class WsOutbound {
      *
      * @throws IOException  If an error occurs writing to the client
      */
-    public synchronized void pong(ByteBuffer data) throws IOException {
+    public void pong(ByteBuffer data) throws IOException {
         sendControlMessage(data, Constants.OPCODE_PONG);
     }
 
@@ -321,7 +400,7 @@ public class WsOutbound {
      *
      * @throws IOException  If an error occurs writing to the client
      */
-    public synchronized void ping(ByteBuffer data) throws IOException {
+    public void ping(ByteBuffer data) throws IOException {
         sendControlMessage(data, Constants.OPCODE_PING);
     }
 
@@ -333,24 +412,36 @@ public class WsOutbound {
      *
      * @throws IOException  If an error occurs writing to the client
      */
-    private synchronized void sendControlMessage(ByteBuffer data, byte opcode) throws IOException {
+    private void sendControlMessage(ByteBuffer data, byte opcode) throws IOException {
 
-        if (closed) {
-            throw new IOException(sm.getString("outbound.closed"));
-        }
-
-        doFlush(false);
-
-        upgradeOutbound.write(0x80 | opcode);
-        if (data == null) {
-            upgradeOutbound.write(0);
-        } else {
-            upgradeOutbound.write(data.limit() - data.position());
-            upgradeOutbound.write(data.array(), data.position(),
-                    data.limit() - data.position());
+        try {
+            synchronized (stateLock) {
+                if (closed) {
+                    throw new IOException(sm.getString("outbound.closed"));
+                }
+        
+                doFlush(false);
+        
+                upgradeOutbound.write(0x80 | opcode);
+                if (data == null) {
+                    upgradeOutbound.write(0);
+                } else {
+                    upgradeOutbound.write(data.limit() - data.position());
+                    upgradeOutbound.write(data.array(), data.position(),
+                            data.limit() - data.position());
+                }
+        
+                upgradeOutbound.flush();
+            }
+        } catch (IOException ioe) {
+            // Any IOException is terminal. Make sure the Inbound side knows
+            // that something went wrong.
+            // The exception handling needs to be outside of the sync to avoid
+            // possible deadlocks (e.g. BZ55524) when triggering the inbound
+            // close as that will execute user code
+            streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY);
+            throw ioe;
         }
-
-        upgradeOutbound.flush();
     }
 
 
@@ -369,60 +460,53 @@ public class WsOutbound {
             throw new IOException(sm.getString("outbound.closed"));
         }
 
-        try {
-            // Work out the first byte
-            int first = 0x00;
-            if (finalFragment) {
-                first = first + 0x80;
-            }
-            if (firstFrame) {
-                if (text.booleanValue()) {
-                    first = first + 0x1;
-                } else {
-                    first = first + 0x2;
-                }
-            }
-            // Continuation frame is OpCode 0
-            upgradeOutbound.write(first);
-    
-            if (buffer.limit() < 126) {
-                upgradeOutbound.write(buffer.limit());
-            } else if (buffer.limit() < 65536) {
-                upgradeOutbound.write(126);
-                upgradeOutbound.write(buffer.limit() >>> 8);
-                upgradeOutbound.write(buffer.limit() & 0xFF);
-            } else {
-                // Will never be more than 2^31-1
-                upgradeOutbound.write(127);
-                upgradeOutbound.write(0);
-                upgradeOutbound.write(0);
-                upgradeOutbound.write(0);
-                upgradeOutbound.write(0);
-                upgradeOutbound.write(buffer.limit() >>> 24);
-                upgradeOutbound.write(buffer.limit() >>> 16);
-                upgradeOutbound.write(buffer.limit() >>> 8);
-                upgradeOutbound.write(buffer.limit() & 0xFF);
-            }
-    
-            // Write the content
-            upgradeOutbound.write(buffer.array(), buffer.arrayOffset(),
-                    buffer.limit());
-            upgradeOutbound.flush();
-    
-            // Reset
-            if (finalFragment) {
-                text = null;
-                firstFrame = true;
+        // Work out the first byte
+        int first = 0x00;
+        if (finalFragment) {
+            first = first + 0x80;
+        }
+        if (firstFrame) {
+            if (text.booleanValue()) {
+                first = first + 0x1;
             } else {
-                firstFrame = false;
+                first = first + 0x2;
             }
-            bb.clear();
-        } catch (IOException ioe) {
-            // Any IOException is terminal. Make sure the Inbound side knows
-            // that something went wrong.
-            streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY);
-            throw ioe;
         }
+        // Continuation frame is OpCode 0
+        upgradeOutbound.write(first);
+
+        if (buffer.limit() < 126) {
+            upgradeOutbound.write(buffer.limit());
+        } else if (buffer.limit() < 65536) {
+            upgradeOutbound.write(126);
+            upgradeOutbound.write(buffer.limit() >>> 8);
+            upgradeOutbound.write(buffer.limit() & 0xFF);
+        } else {
+            // Will never be more than 2^31-1
+            upgradeOutbound.write(127);
+            upgradeOutbound.write(0);
+            upgradeOutbound.write(0);
+            upgradeOutbound.write(0);
+            upgradeOutbound.write(0);
+            upgradeOutbound.write(buffer.limit() >>> 24);
+            upgradeOutbound.write(buffer.limit() >>> 16);
+            upgradeOutbound.write(buffer.limit() >>> 8);
+            upgradeOutbound.write(buffer.limit() & 0xFF);
+        }
+
+        // Write the content
+        upgradeOutbound.write(buffer.array(), buffer.arrayOffset(),
+                buffer.limit());
+        upgradeOutbound.flush();
+
+        // Reset
+        if (finalFragment) {
+            text = null;
+            firstFrame = true;
+        } else {
+            firstFrame = false;
+        }
+        bb.clear();
     }
 
 
diff --git a/java/org/apache/coyote/AbstractProcessor.java b/java/org/apache/coyote/AbstractProcessor.java
index 7e0706f..37b536d 100644
--- a/java/org/apache/coyote/AbstractProcessor.java
+++ b/java/org/apache/coyote/AbstractProcessor.java
@@ -19,7 +19,6 @@ package org.apache.coyote;
 import java.io.IOException;
 import java.util.concurrent.Executor;
 
-import org.apache.coyote.http11.upgrade.UpgradeInbound;
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
 import org.apache.tomcat.util.net.SocketStatus;
@@ -150,6 +149,10 @@ public abstract class AbstractProcessor<S> implements ActionHook, Processor<S> {
     @Override
     public abstract SocketState upgradeDispatch() throws IOException;
 
+    /**
+     * @deprecated  Will be removed in Tomcat 8.0.x.
+     */
+    @Deprecated
     @Override
-    public abstract UpgradeInbound getUpgradeInbound();
+    public abstract org.apache.coyote.http11.upgrade.UpgradeInbound getUpgradeInbound();
 }
diff --git a/java/org/apache/coyote/AbstractProtocol.java b/java/org/apache/coyote/AbstractProtocol.java
index c079a48..cd7ff6d 100644
--- a/java/org/apache/coyote/AbstractProtocol.java
+++ b/java/org/apache/coyote/AbstractProtocol.java
@@ -29,8 +29,8 @@ import javax.management.MBeanServer;
 import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
 
-import org.apache.coyote.http11.upgrade.UpgradeInbound;
-import org.apache.coyote.http11.upgrade.UpgradeProcessor;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
+import org.apache.coyote.http11.upgrade.servlet31.WebConnection;
 import org.apache.juli.logging.Log;
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.modeler.Registry;
@@ -250,6 +250,11 @@ public abstract class AbstractProtocol implements ProtocolHandler,
         endpoint.setMaxHeaderCount(maxHeaderCount);
     }
 
+    public long getConnectionCount() {
+        return endpoint.getConnectionCount();
+    }
+
+
     // ---------------------------------------------------------- Public methods
 
     public synchronized int getNameIndex() {
@@ -549,17 +554,24 @@ public abstract class AbstractProtocol implements ProtocolHandler,
         }
         
 
-        public SocketState process(SocketWrapper<S> socket,
+        @SuppressWarnings("deprecation") // Old HTTP upgrade method has been deprecated
+        public SocketState process(SocketWrapper<S> wrapper,
                 SocketStatus status) {
-            Processor<S> processor = connections.remove(socket.getSocket());
+            S socket = wrapper.getSocket();
+
+            if (socket == null) {
+                // Nothing to do. Socket has been closed.
+                return SocketState.CLOSED;
+            }
 
+            Processor<S> processor = connections.get(socket);
             if (status == SocketStatus.DISCONNECT && processor == null) {
-                //nothing more to be done endpoint requested a close
-                //and there are no object associated with this connection
+                // Nothing to do. Endpoint requested a close and there is no
+                // longer a processor associated with this socket.
                 return SocketState.CLOSED;
             }
 
-            socket.setAsync(false);
+            wrapper.setAsync(false);
 
             try {
                 if (processor == null) {
@@ -569,7 +581,7 @@ public abstract class AbstractProtocol implements ProtocolHandler,
                     processor = createProcessor();
                 }
 
-                initSsl(socket, processor);
+                initSsl(wrapper, processor);
 
                 SocketState state = SocketState.CLOSED;
                 do {
@@ -583,10 +595,12 @@ public abstract class AbstractProtocol implements ProtocolHandler,
                         state = processor.asyncDispatch(status);
                     } else if (processor.isComet()) {
                         state = processor.event(status);
-                    } else if (processor.isUpgrade()) {
+                    } else if (processor.getUpgradeInbound() != null) {
                         state = processor.upgradeDispatch();
+                    } else if (processor.isUpgrade()) {
+                        state = processor.upgradeDispatch(status);
                     } else {
-                        state = processor.process(socket);
+                        state = processor.process(wrapper);
                     }
     
                     if (state != SocketState.CLOSED && processor.isAsync()) {
@@ -594,38 +608,80 @@ public abstract class AbstractProtocol implements ProtocolHandler,
                     }
 
                     if (state == SocketState.UPGRADING) {
+                        // Get the HTTP upgrade handler
+                        HttpUpgradeHandler httpUpgradeHandler =
+                                processor.getHttpUpgradeHandler();
+                        // Release the Http11 processor to be re-used
+                        release(wrapper, processor, false, false);
+                        // Create the upgrade processor
+                        processor = createUpgradeProcessor(
+                                wrapper, httpUpgradeHandler);
+                        // Mark the connection as upgraded
+                        wrapper.setUpgraded(true);
+                        // Associate with the processor with the connection
+                        connections.put(socket, processor);
+                        // Initialise the upgrade handler (which may trigger
+                        // some IO using the new protocol which is why the lines
+                        // above are necessary)
+                        // This cast should be safe. If it fails the error
+                        // handling for the surrounding try/catch will deal with
+                        // it.
+                        httpUpgradeHandler.init((WebConnection) processor);
+                    } else if (state == SocketState.UPGRADING_TOMCAT) {
                         // Get the UpgradeInbound handler
-                        UpgradeInbound inbound = processor.getUpgradeInbound();
+                        org.apache.coyote.http11.upgrade.UpgradeInbound inbound =
+                                processor.getUpgradeInbound();
                         // Release the Http11 processor to be re-used
-                        release(socket, processor, false, false);
+                        release(wrapper, processor, false, false);
                         // Create the light-weight upgrade processor
-                        processor = createUpgradeProcessor(socket, inbound);
+                        processor = createUpgradeProcessor(wrapper, inbound);
                         inbound.onUpgradeComplete();
                     }
+                    if (getLog().isDebugEnabled()) {
+                        getLog().debug("Socket: [" + wrapper +
+                                "], Status in: [" + status +
+                                "], State out: [" + state + "]");
+                    }
                 } while (state == SocketState.ASYNC_END ||
-                        state == SocketState.UPGRADING);
+                        state == SocketState.UPGRADING ||
+                        state == SocketState.UPGRADING_TOMCAT);
 
                 if (state == SocketState.LONG) {
                     // In the middle of processing a request/response. Keep the
                     // socket associated with the processor. Exact requirements
                     // depend on type of long poll
-                    longPoll(socket, processor);
+                    connections.put(socket, processor);
+                    longPoll(wrapper, processor);
                 } else if (state == SocketState.OPEN) {
                     // In keep-alive but between requests. OK to recycle
                     // processor. Continue to poll for the next request.
-                    release(socket, processor, false, true);
+                    connections.remove(socket);
+                    release(wrapper, processor, false, true);
                 } else if (state == SocketState.SENDFILE) {
                     // Sendfile in progress. If it fails, the socket will be
                     // closed. If it works, the socket will be re-added to the
                     // poller
-                    release(socket, processor, false, false);
+                    connections.remove(socket);
+                    release(wrapper, processor, false, false);
                 } else if (state == SocketState.UPGRADED) {
-                    // Need to keep the connection associated with the processor
-                    longPoll(socket, processor);
+                    // Don't add sockets back to the poller if this was a
+                    // non-blocking write otherwise the poller may trigger
+                    // multiple read events which may lead to thread starvation
+                    // in the connector. The write() method will add this socket
+                    // to the poller if necessary.
+                    if (status != SocketStatus.OPEN_WRITE) {
+                        longPoll(wrapper, processor);
+                    }
                 } else {
-                    // Connection closed. OK to recycle the processor.
-                    if (!(processor instanceof UpgradeProcessor)) {
-                        release(socket, processor, true, false);
+                    // Connection closed. OK to recycle the processor. Upgrade
+                    // processors are not recycled.
+                    connections.remove(socket);
+                    if (processor.isUpgrade()) {
+                        processor.getHttpUpgradeHandler().destroy();
+                    } else if (processor instanceof org.apache.coyote.http11.upgrade.UpgradeProcessor) {
+                        // NO-OP
+                    } else {
+                        release(wrapper, processor, true, false);
                     }
                 }
                 return state;
@@ -649,9 +705,13 @@ public abstract class AbstractProtocol implements ProtocolHandler,
                 getLog().error(
                         sm.getString("abstractConnectionHandler.error"), e);
             }
+            // Make sure socket/processor is removed from the list of current
+            // connections
+            connections.remove(socket);
             // Don't try to add upgrade processors back into the pool
-            if (!(processor instanceof UpgradeProcessor)) {
-                release(socket, processor, true, false);
+            if (!(processor instanceof org.apache.coyote.http11.upgrade.UpgradeProcessor)
+                    && !processor.isUpgrade()) {
+                release(wrapper, processor, true, false);
             }
             return SocketState.CLOSED;
         }
@@ -664,10 +724,17 @@ public abstract class AbstractProtocol implements ProtocolHandler,
         protected abstract void release(SocketWrapper<S> socket,
                 Processor<S> processor, boolean socketClosing,
                 boolean addToPoller);
+        /**
+         * @deprecated  Will be removed in Tomcat 8.0.x.
+         */
+        @Deprecated
         protected abstract Processor<S> createUpgradeProcessor(
                 SocketWrapper<S> socket,
-                UpgradeInbound inbound) throws IOException;
-
+                org.apache.coyote.http11.upgrade.UpgradeInbound inbound) throws IOException;
+        protected abstract Processor<S> createUpgradeProcessor(
+                SocketWrapper<S> socket,
+                HttpUpgradeHandler httpUpgradeProcessor) throws IOException;
+        
         protected void register(AbstractProcessor<S> processor) {
             if (getProtocol().getDomain() != null) {
                 synchronized (this) {
diff --git a/java/org/apache/coyote/ActionCode.java b/java/org/apache/coyote/ActionCode.java
index 0f3fbce..fe44842 100644
--- a/java/org/apache/coyote/ActionCode.java
+++ b/java/org/apache/coyote/ActionCode.java
@@ -197,7 +197,12 @@ public enum ActionCode {
     ASYNC_IS_ERROR,
 
     /**
-     * Callback to trigger the HTTP upgrade process.
+     * Callback to trigger Tomcat's proprietary HTTP upgrade process.
+     */
+    UPGRADE_TOMCAT,
+    
+    /**
+     * Callback to trigger the Servlet 3.1 based HTTP upgrade process.
      */
     UPGRADE
 }
diff --git a/java/org/apache/coyote/AsyncStateMachine.java b/java/org/apache/coyote/AsyncStateMachine.java
index d86d12d..98def21 100644
--- a/java/org/apache/coyote/AsyncStateMachine.java
+++ b/java/org/apache/coyote/AsyncStateMachine.java
@@ -200,6 +200,10 @@ public class AsyncStateMachine<S> {
         } else if (state == AsyncState.DISPATCHING) {
             state = AsyncState.DISPATCHED;
             return SocketState.ASYNC_END;
+        } else if (state == AsyncState.STARTED) {
+            // This can occur if an async listener does a dispatch to an async
+            // servlet during onTimeout
+            return SocketState.LONG;
         } else {
             throw new IllegalStateException(
                     sm.getString("asyncStateMachine.invalidAsyncState",
diff --git a/java/org/apache/coyote/Processor.java b/java/org/apache/coyote/Processor.java
index 71597da..92dee36 100644
--- a/java/org/apache/coyote/Processor.java
+++ b/java/org/apache/coyote/Processor.java
@@ -20,7 +20,7 @@ package org.apache.coyote;
 import java.io.IOException;
 import java.util.concurrent.Executor;
 
-import org.apache.coyote.http11.upgrade.UpgradeInbound;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
 import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
 import org.apache.tomcat.util.net.SSLSupport;
 import org.apache.tomcat.util.net.SocketStatus;
@@ -40,9 +40,20 @@ public interface Processor<S> {
     SocketState asyncDispatch(SocketStatus status);
     SocketState asyncPostProcess();
 
-    UpgradeInbound getUpgradeInbound();
+    /**
+     * @deprecated  Will be removed in Tomcat 8.0.x.
+     */
+    @Deprecated
+    org.apache.coyote.http11.upgrade.UpgradeInbound getUpgradeInbound();
+    /**
+     * @deprecated  Will be removed in Tomcat 8.0.x.
+     */
+    @Deprecated
     SocketState upgradeDispatch() throws IOException;
 
+    HttpUpgradeHandler getHttpUpgradeHandler();
+    SocketState upgradeDispatch(SocketStatus status) throws IOException;
+    
     boolean isComet();
     boolean isAsync();
     boolean isUpgrade();
diff --git a/java/org/apache/coyote/Request.java b/java/org/apache/coyote/Request.java
index 8ea54af..a3d9a93 100644
--- a/java/org/apache/coyote/Request.java
+++ b/java/org/apache/coyote/Request.java
@@ -272,7 +272,7 @@ public final class Request {
     }
 
 
-    public void setContentLength(int len) {
+    public void setContentLength(long len) {
         this.contentLength = len;
     }
 
@@ -486,6 +486,7 @@ public final class Request {
         headers.recycle();
         serverNameMB.recycle();
         serverPort=-1;
+        localNameMB.recycle();
         localPort = -1;
         remotePort = -1;
         available = 0;
diff --git a/java/org/apache/coyote/ajp/AbstractAjpProcessor.java b/java/org/apache/coyote/ajp/AbstractAjpProcessor.java
index 5ecbb38..0ca46ce 100644
--- a/java/org/apache/coyote/ajp/AbstractAjpProcessor.java
+++ b/java/org/apache/coyote/ajp/AbstractAjpProcessor.java
@@ -25,6 +25,8 @@ import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import javax.servlet.http.HttpServletResponse;
+
 import org.apache.coyote.AbstractProcessor;
 import org.apache.coyote.ActionCode;
 import org.apache.coyote.AsyncContextCallback;
@@ -33,7 +35,7 @@ import org.apache.coyote.OutputBuffer;
 import org.apache.coyote.Request;
 import org.apache.coyote.RequestInfo;
 import org.apache.coyote.Response;
-import org.apache.coyote.http11.upgrade.UpgradeInbound;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
 import org.apache.juli.logging.Log;
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.buf.ByteChunk;
@@ -215,6 +217,12 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
 
 
     /**
+     * Should any response body be swallowed and not sent to the client.
+     */
+    private boolean swallowResponse = false;
+
+
+    /**
      * Finished response.
      */
     protected boolean finished = false;
@@ -459,7 +467,7 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
             ((AtomicBoolean) param).set(asyncStateMachine.isAsyncTimingOut());
         } else if (actionCode == ActionCode.ASYNC_IS_ERROR) {
             ((AtomicBoolean) param).set(asyncStateMachine.isAsyncError());
-        } else if (actionCode == ActionCode.UPGRADE) {
+        } else if (actionCode == ActionCode.UPGRADE_TOMCAT) {
             // HTTP connections only. Unsupported for AJP.
             // NOOP
         }  else {
@@ -475,6 +483,7 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
         try {
             rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
             error = !adapter.asyncDispatch(request, response, status);
+            resetTimeouts();
         } catch (InterruptedIOException e) {
             error = true;
         } catch (Throwable t) {
@@ -533,8 +542,28 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
     }
 
 
+    /**
+     * @deprecated  Will be removed in Tomcat 8.0.x.
+     */
+    @Deprecated
+    @Override
+    public org.apache.coyote.http11.upgrade.UpgradeInbound getUpgradeInbound() {
+        // Can't throw exception as this is used to test if connection has been
+        // upgraded using Tomcat's proprietary HTTP upgrade mechanism.
+        return null;
+    }
+
+
+    @Override
+    public SocketState upgradeDispatch(SocketStatus status) throws IOException {
+        // Should never reach this code but in case we do...
+        throw new IOException(
+                sm.getString("ajpprocessor.httpupgrade.notsupported"));
+    }
+
+
     @Override
-    public UpgradeInbound getUpgradeInbound() {
+    public HttpUpgradeHandler getHttpUpgradeHandler() {
         // Should never reach this code but in case we do...
         throw new IllegalStateException(
                 sm.getString("ajpprocessor.httpupgrade.notsupported"));
@@ -562,6 +591,7 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
         request.recycle();
         response.recycle();
         certificates.recycle();
+        swallowResponse = false;
         bytesWritten = 0;
     }
 
@@ -571,10 +601,21 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
     // Methods called by action()
     protected abstract void actionInternal(ActionCode actionCode, Object param);
 
+    
+    // Methods called by asyncDispatch
+    /**
+     * Provides a mechanism for those connector implementations (currently only
+     * NIO) that need to reset timeouts from Async timeouts to standard HTTP
+     * timeouts once async processing completes.
+     */
+    protected abstract void resetTimeouts();
+
+    
     // Methods called by prepareResponse()
     protected abstract void output(byte[] src, int offset, int length)
             throws IOException;
 
+    
     // Methods used by SocketInputBuffer
     protected abstract boolean receive() throws IOException;
 
@@ -652,6 +693,7 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
         // Set this every time in case limit has been changed via JMX
         headers.setLimit(endpoint.getMaxHeaderCount());
 
+        boolean contentLengthSet = false;
         int hCount = requestHeaderMessage.getInt();
         for(int i = 0 ; i < hCount ; i++) {
             String hName = null;
@@ -686,10 +728,15 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
 
             if (hId == Constants.SC_REQ_CONTENT_LENGTH ||
                     (hId == -1 && tmpMB.equalsIgnoreCase("Content-Length"))) {
-                // just read the content-length header, so set it
                 long cl = vMB.getLong();
-                if(cl < Integer.MAX_VALUE)
-                    request.setContentLength( (int)cl );
+                if (contentLengthSet) {
+                    response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+                    error = true;
+                } else {
+                    contentLengthSet = true;
+                    // Set the content-length header for the request
+                    request.setContentLength(cl);
+                }
             } else if (hId == Constants.SC_REQ_CONTENT_TYPE ||
                     (hId == -1 && tmpMB.equalsIgnoreCase("Content-Type"))) {
                 // just read the content-type header, so set it
@@ -941,15 +988,33 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
         responseMessage.reset();
         responseMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS);
 
+        // Responses with certain status codes are not permitted to include a
+        // response body.
+        int statusCode = response.getStatus();
+        if (statusCode < 200 || statusCode == 204 || statusCode == 205 ||
+                statusCode == 304) {
+            // No entity body
+            swallowResponse = true;
+        }
+
+        // Responses to HEAD requests are not permitted to incude a response
+        // body.
+        MessageBytes methodMB = request.method();
+        if (methodMB.equals("HEAD")) {
+            // No entity body
+            swallowResponse = true;
+        }
+
         // HTTP header contents
-        responseMessage.appendInt(response.getStatus());
+        responseMessage.appendInt(statusCode);
         String message = null;
         if (org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER &&
                 HttpMessages.isSafeInHttpHeader(response.getMessage())) {
             message = response.getMessage();
         }
         if (message == null){
-            message = HttpMessages.getMessage(response.getStatus());
+            message = HttpMessages.getInstance(
+                    response.getLocale()).getMessage(response.getStatus());
         }
         if (message == null) {
             // mod_jk + httpd 2.x fails with a null status message - bug 45026
@@ -1106,27 +1171,29 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
                 }
             }
 
-            int len = chunk.getLength();
-            // 4 - hardcoded, byte[] marshaling overhead
-            // Adjust allowed size if packetSize != default (Constants.MAX_PACKET_SIZE)
-            int chunkSize = Constants.MAX_SEND_SIZE + packetSize - Constants.MAX_PACKET_SIZE;
-            int off = 0;
-            while (len > 0) {
-                int thisTime = len;
-                if (thisTime > chunkSize) {
-                    thisTime = chunkSize;
+            if (!swallowResponse) {
+                int len = chunk.getLength();
+                // 4 - hardcoded, byte[] marshaling overhead
+                // Adjust allowed size if packetSize != default (Constants.MAX_PACKET_SIZE)
+                int chunkSize = Constants.MAX_SEND_SIZE + packetSize - Constants.MAX_PACKET_SIZE;
+                int off = 0;
+                while (len > 0) {
+                    int thisTime = len;
+                    if (thisTime > chunkSize) {
+                        thisTime = chunkSize;
+                    }
+                    len -= thisTime;
+                    responseMessage.reset();
+                    responseMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
+                    responseMessage.appendBytes(chunk.getBytes(), chunk.getOffset() + off, thisTime);
+                    responseMessage.end();
+                    output(responseMessage.getBuffer(), 0, responseMessage.getLen());
+
+                    off += thisTime;
                 }
-                len -= thisTime;
-                responseMessage.reset();
-                responseMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
-                responseMessage.appendBytes(chunk.getBytes(), chunk.getOffset() + off, thisTime);
-                responseMessage.end();
-                output(responseMessage.getBuffer(), 0, responseMessage.getLen());
-
-                off += thisTime;
-            }
 
-            bytesWritten += chunk.getLength();
+                bytesWritten += chunk.getLength();
+            }
             return chunk.getLength();
         }
 
diff --git a/java/org/apache/coyote/ajp/AbstractAjpProtocol.java b/java/org/apache/coyote/ajp/AbstractAjpProtocol.java
index ee0e2a7..0f4fd0a 100644
--- a/java/org/apache/coyote/ajp/AbstractAjpProtocol.java
+++ b/java/org/apache/coyote/ajp/AbstractAjpProtocol.java
@@ -18,7 +18,7 @@ package org.apache.coyote.ajp;
 
 import org.apache.coyote.AbstractProtocol;
 import org.apache.coyote.Processor;
-import org.apache.coyote.http11.upgrade.UpgradeInbound;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
 import org.apache.tomcat.util.net.SocketWrapper;
 import org.apache.tomcat.util.res.StringManager;
 
@@ -86,13 +86,23 @@ public abstract class AbstractAjpProtocol extends AbstractProtocol {
         protected void longPoll(SocketWrapper<S> socket,
                 Processor<S> processor) {
             // Same requirements for all AJP connectors
-            connections.put(socket.getSocket(), processor);
             socket.setAsync(true);
         }
 
+        /**
+         * @deprecated  Will be removed in Tomcat 8.0.x.
+         */
+        @Deprecated
         @Override
         protected P createUpgradeProcessor(SocketWrapper<S> socket,
-                UpgradeInbound inbound) {
+                org.apache.coyote.http11.upgrade.UpgradeInbound inbound) {
+            // TODO should fail - throw IOE
+            return null;
+        }
+        
+        @Override
+        protected P createUpgradeProcessor(SocketWrapper<S> socket,
+                HttpUpgradeHandler httpUpgradeHandler) {
             // TODO should fail - throw IOE
             return null;
         }
diff --git a/java/org/apache/coyote/ajp/AjpAprProcessor.java b/java/org/apache/coyote/ajp/AjpAprProcessor.java
index e609281..31b54bf 100644
--- a/java/org/apache/coyote/ajp/AjpAprProcessor.java
+++ b/java/org/apache/coyote/ajp/AjpAprProcessor.java
@@ -261,22 +261,28 @@ public class AjpAprProcessor extends AbstractAjpProcessor<Long> {
         if (actionCode == ActionCode.ASYNC_COMPLETE) {
             if (asyncStateMachine.asyncComplete()) {
                 ((AprEndpoint)endpoint).processSocketAsync(this.socket,
-                        SocketStatus.OPEN);
+                        SocketStatus.OPEN_READ);
             }
+
         } else if (actionCode == ActionCode.ASYNC_SETTIMEOUT) {
             if (param == null) return;
             long timeout = ((Long)param).longValue();
             socket.setTimeout(timeout);
+
         } else if (actionCode == ActionCode.ASYNC_DISPATCH) {
             if (asyncStateMachine.asyncDispatch()) {
                 ((AprEndpoint)endpoint).processSocketAsync(this.socket,
-                        SocketStatus.OPEN);
+                        SocketStatus.OPEN_READ);
             }
         }
     }
 
 
-    // ------------------------------------------------------ Protected Methods
+    @Override
+    protected void resetTimeouts() {
+        // NO-OP. The AJP APR/native connector only uses the timeout value on
+        //        time SocketWrapper for async timeouts.
+    }
 
 
     @Override
diff --git a/java/org/apache/coyote/ajp/AjpAprProtocol.java b/java/org/apache/coyote/ajp/AjpAprProtocol.java
index 947e8f6..c9aa4d9 100644
--- a/java/org/apache/coyote/ajp/AjpAprProtocol.java
+++ b/java/org/apache/coyote/ajp/AjpAprProtocol.java
@@ -136,7 +136,7 @@ public class AjpAprProtocol extends AbstractAjpProtocol {
                 ((AprEndpoint)proto.endpoint).getPoller().add(
                         socket.getSocket().longValue(),
                         proto.endpoint.getKeepAliveTimeout(),
-                        AprEndpoint.Poller.FLAGS_READ);
+                        true, false);
             }
         }
 
diff --git a/java/org/apache/coyote/ajp/AjpNioProcessor.java b/java/org/apache/coyote/ajp/AjpNioProcessor.java
index 5966f40..9e76a5c 100644
--- a/java/org/apache/coyote/ajp/AjpNioProcessor.java
+++ b/java/org/apache/coyote/ajp/AjpNioProcessor.java
@@ -253,25 +253,44 @@ public class AjpNioProcessor extends AbstractAjpProcessor<NioChannel> {
         if (actionCode == ActionCode.ASYNC_COMPLETE) {
             if (asyncStateMachine.asyncComplete()) {
                 ((NioEndpoint)endpoint).processSocket(this.socket,
-                        SocketStatus.OPEN, false);
+                        SocketStatus.OPEN_READ, false);
             }
+
         } else if (actionCode == ActionCode.ASYNC_SETTIMEOUT) {
             if (param == null) return;
             long timeout = ((Long)param).longValue();
             final KeyAttachment ka = (KeyAttachment)socket.getAttachment(false);
-            if (keepAliveTimeout > 0) {
-                ka.setTimeout(timeout);
-            }
+            ka.setTimeout(timeout);
+
         } else if (actionCode == ActionCode.ASYNC_DISPATCH) {
             if (asyncStateMachine.asyncDispatch()) {
                 ((NioEndpoint)endpoint).processSocket(this.socket,
-                        SocketStatus.OPEN, true);
+                        SocketStatus.OPEN_READ, true);
             }
         }
     }
 
 
-    // ------------------------------------------------------ Protected Methods
+    @Override
+    protected void resetTimeouts() {
+        // The NIO connector uses the timeout configured on the wrapper in the
+        // poller. Therefore, it needs to be reset once asycn processing has
+        // finished.
+        final KeyAttachment attach = (KeyAttachment)socket.getAttachment(false);
+        if (!error && attach != null &&
+                asyncStateMachine.isAsyncDispatching()) {
+            long soTimeout = endpoint.getSoTimeout();
+
+            //reset the timeout
+            if (keepAliveTimeout > 0) {
+                attach.setTimeout(keepAliveTimeout);
+            } else {
+                attach.setTimeout(soTimeout);
+            }
+        }
+
+    }
+
 
     @Override
     protected void output(byte[] src, int offset, int length)
@@ -284,7 +303,7 @@ public class AjpNioProcessor extends AbstractAjpProcessor<NioChannel> {
         
         NioEndpoint.KeyAttachment att = (NioEndpoint.KeyAttachment)socket.getAttachment(false);
         if ( att == null ) throw new IOException("Key must be cancelled");
-        long writeTimeout = att.getTimeout();
+        long writeTimeout = att.getWriteTimeout();
         Selector selector = null;
         try {
             selector = pool.get();
diff --git a/java/org/apache/coyote/ajp/AjpProcessor.java b/java/org/apache/coyote/ajp/AjpProcessor.java
index 95769c3..42e8981 100644
--- a/java/org/apache/coyote/ajp/AjpProcessor.java
+++ b/java/org/apache/coyote/ajp/AjpProcessor.java
@@ -271,23 +271,30 @@ public class AjpProcessor extends AbstractAjpProcessor<Socket> {
         if (actionCode == ActionCode.ASYNC_COMPLETE) {
             if (asyncStateMachine.asyncComplete()) {
                 ((JIoEndpoint)endpoint).processSocketAsync(this.socket,
-                        SocketStatus.OPEN);
+                        SocketStatus.OPEN_READ);
             }
+
         } else if (actionCode == ActionCode.ASYNC_SETTIMEOUT) {
             if (param == null) return;
             long timeout = ((Long)param).longValue();
             // if we are not piggy backing on a worker thread, set the timeout
             socket.setTimeout(timeout);
+
         } else if (actionCode == ActionCode.ASYNC_DISPATCH) {
             if (asyncStateMachine.asyncDispatch()) {
                 ((JIoEndpoint)endpoint).processSocketAsync(this.socket,
-                        SocketStatus.OPEN);
+                        SocketStatus.OPEN_READ);
             }
         }
     }
 
 
-    // ------------------------------------------------------ Protected Methods
+    @Override
+    protected void resetTimeouts() {
+        // NO-OP. The AJP BIO connector only uses the timeout value on the
+        //        SocketWrapper for async timeouts.
+    }
+
 
     @Override
     protected void output(byte[] src, int offset, int length)
diff --git a/java/org/apache/coyote/http11/AbstractHttp11Processor.java b/java/org/apache/coyote/http11/AbstractHttp11Processor.java
index 3753836..d02ec22 100644
--- a/java/org/apache/coyote/http11/AbstractHttp11Processor.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11Processor.java
@@ -36,7 +36,7 @@ import org.apache.coyote.http11.filters.IdentityOutputFilter;
 import org.apache.coyote.http11.filters.SavedRequestInputFilter;
 import org.apache.coyote.http11.filters.VoidInputFilter;
 import org.apache.coyote.http11.filters.VoidOutputFilter;
-import org.apache.coyote.http11.upgrade.UpgradeInbound;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
 import org.apache.juli.logging.Log;
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.buf.Ascii;
@@ -258,11 +258,21 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
 
     /**
      * Listener to which data available events are passed once the associated
-     * connection has completed the HTTP upgrade process.
+     * connection has completed the proprietary Tomcat HTTP upgrade process.
+     * 
+     * @deprecated  Will be removed in Tomcat 8.0.x.
      */
-    protected UpgradeInbound upgradeInbound = null;
+    @Deprecated
+    protected org.apache.coyote.http11.upgrade.UpgradeInbound upgradeInbound = null;
 
 
+    /**
+     * Instance of the new protocol to use after the HTTP connection has been
+     * upgraded using the Servlet 3.1 based upgrade process.
+     */
+    protected HttpUpgradeHandler httpUpgradeHandler = null;
+    
+    
     public AbstractHttp11Processor(AbstractEndpoint endpoint) {
         super(endpoint);
         userDataHelper = new UserDataHelper(getLog());
@@ -684,13 +694,14 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
     /**
      * Initialize standard input and output filters.
      */
-    protected void initializeFilters(int maxTrailerSize) {
+    protected void initializeFilters(int maxTrailerSize, int maxExtensionSize) {
         // Create and add the identity filters.
         getInputBuffer().addFilter(new IdentityInputFilter());
         getOutputBuffer().addFilter(new IdentityOutputFilter());
 
         // Create and add the chunked filters.
-        getInputBuffer().addFilter(new ChunkedInputFilter(maxTrailerSize));
+        getInputBuffer().addFilter(
+                new ChunkedInputFilter(maxTrailerSize, maxExtensionSize));
         getOutputBuffer().addFilter(new ChunkedOutputFilter());
 
         // Create and add the void filters.
@@ -743,6 +754,7 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
      * @param param Action parameter
      */
     @Override
+    @SuppressWarnings("deprecation") // Inbound/Outbound based upgrade mechanism
     public final void action(ActionCode actionCode, Object param) {
 
         if (actionCode == ActionCode.CLOSE) {
@@ -819,6 +831,7 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
             InputFilter savedBody = new SavedRequestInputFilter(body);
             savedBody.setRequest(request);
 
+            @SuppressWarnings("unchecked")
             AbstractInputBuffer<S> internalBuffer = (AbstractInputBuffer<S>)
                 request.getInputBuffer();
             internalBuffer.addActiveFilter(savedBody);
@@ -843,8 +856,12 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
             ((AtomicBoolean) param).set(asyncStateMachine.isAsyncTimingOut());
         } else if (actionCode == ActionCode.ASYNC_IS_ERROR) {
             ((AtomicBoolean) param).set(asyncStateMachine.isAsyncError());
+        } else if (actionCode == ActionCode.UPGRADE_TOMCAT) {
+            upgradeInbound = (org.apache.coyote.http11.upgrade.UpgradeInbound) param;
+            // Stop further HTTP output
+            getOutputBuffer().finished = true;
         } else if (actionCode == ActionCode.UPGRADE) {
-            upgradeInbound = (UpgradeInbound) param;
+            httpUpgradeHandler = (HttpUpgradeHandler) param;
             // Stop further HTTP output
             getOutputBuffer().finished = true;
         } else {
@@ -922,7 +939,8 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
         }
 
         while (!error && keepAlive && !comet && !isAsync() &&
-                upgradeInbound == null && !endpoint.isPaused()) {
+                upgradeInbound == null &&
+                httpUpgradeHandler == null && !endpoint.isPaused()) {
 
             // Parsing the request header
             try {
@@ -1104,6 +1122,8 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
             return SocketState.LONG;
         } else if (isUpgrade()) {
             return SocketState.UPGRADING;
+        } else if (getUpgradeInbound() != null) {
+            return SocketState.UPGRADING_TOMCAT;
         } else {
             if (sendfileInProgress) {
                 return SocketState.SENDFILE;
@@ -1277,10 +1297,20 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
 
         // Parse content-length header
         long contentLength = request.getContentLengthLong();
-        if (contentLength >= 0 && !contentDelimitation) {
-            getInputBuffer().addActiveFilter
-                (inputFilters[Constants.IDENTITY_FILTER]);
-            contentDelimitation = true;
+        if (contentLength >= 0) {
+            if (contentDelimitation) {
+                // contentDelimitation being true at this point indicates that
+                // chunked encoding is being used but chunked encoding should
+                // not be used with a content length. RFC 2616, section 4.4,
+                // bullet 3 states Content-Length must be ignored in this case -
+                // so remove it.
+                headers.removeHeader("content-length");
+                request.setContentLength(-1);
+            } else {
+                getInputBuffer().addActiveFilter
+                        (inputFilters[Constants.IDENTITY_FILTER]);
+                contentDelimitation = true;
+            }
         }
 
         MessageBytes valueMB = headers.getValue("host");
@@ -1454,8 +1484,12 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
             }
         }
 
-        // Add date header
-        headers.setValue("Date").setString(FastHttpDateFormat.getCurrentDate());
+        // Add date header unless application has already set one (e.g. in a
+        // Caching Filter)
+        if (headers.getValue("Date") == null) {
+            headers.setValue("Date").setString(
+                    FastHttpDateFormat.getCurrentDate());
+        }
 
         // FIXME: Add transfer encoding header
 
@@ -1625,13 +1659,6 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
 
 
     @Override
-    public boolean isUpgrade() {
-        return upgradeInbound != null;
-    }
-
-
-
-    @Override
     public SocketState upgradeDispatch() throws IOException {
         // Should never reach this code but in case we do...
         // TODO
@@ -1640,12 +1667,36 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
     }
 
 
+    /**
+     * @deprecated  Will be removed in Tomcat 8.0.x.
+     */
+    @Deprecated
     @Override
-    public UpgradeInbound getUpgradeInbound() {
+    public org.apache.coyote.http11.upgrade.UpgradeInbound getUpgradeInbound() {
         return upgradeInbound;
     }
 
 
+    @Override
+    public boolean isUpgrade() {
+        return httpUpgradeHandler != null;
+    }
+    
+    
+    @Override
+    public SocketState upgradeDispatch(SocketStatus status) throws IOException {
+        // Should never reach this code but in case we do...
+        throw new IOException(
+                sm.getString("ajpprocessor.httpupgrade.notsupported"));
+    }
+
+
+    @Override
+    public HttpUpgradeHandler getHttpUpgradeHandler() {
+        return httpUpgradeHandler;
+    }
+    
+    
     /**
      * Provides a mechanism for those connector implementations (currently only
      * NIO) that need to reset timeouts from Async timeouts to standard HTTP
@@ -1712,6 +1763,7 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
             asyncStateMachine.recycle();
         }
         upgradeInbound = null;
+        httpUpgradeHandler = null;
         remoteAddr = null;
         remoteHost = null;
         localAddr = null;
diff --git a/java/org/apache/coyote/http11/AbstractHttp11Protocol.java b/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
index 85b8371..accee1e 100644
--- a/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
@@ -153,6 +153,16 @@ public abstract class AbstractHttp11Protocol extends AbstractProtocol {
 
 
     /**
+     * Maximum size of extension information in chunked encoding
+     */
+    private int maxExtensionSize = 8192;
+    public int getMaxExtensionSize() { return maxExtensionSize; }
+    public void setMaxExtensionSize(int maxExtensionSize) {
+        this.maxExtensionSize = maxExtensionSize;
+    }
+
+
+    /**
      * This field indicates if the protocol is treated as if it is secure. This
      * normally means https is being used but can be used to fake https e.g
      * behind a reverse proxy.
diff --git a/java/org/apache/coyote/http11/AbstractOutputBuffer.java b/java/org/apache/coyote/http11/AbstractOutputBuffer.java
index 20ed768..349c2c3 100644
--- a/java/org/apache/coyote/http11/AbstractOutputBuffer.java
+++ b/java/org/apache/coyote/http11/AbstractOutputBuffer.java
@@ -354,7 +354,8 @@ public abstract class AbstractOutputBuffer<S> implements OutputBuffer{
             message = response.getMessage();
         }
         if (message == null) {
-            write(HttpMessages.getMessage(status)); 
+            write(HttpMessages.getInstance(
+                    response.getLocale()).getMessage(status));
         } else {
             write(message);
         }
diff --git a/java/org/apache/coyote/http11/Http11AprProcessor.java b/java/org/apache/coyote/http11/Http11AprProcessor.java
index 22613aa..81ee3d5 100644
--- a/java/org/apache/coyote/http11/Http11AprProcessor.java
+++ b/java/org/apache/coyote/http11/Http11AprProcessor.java
@@ -58,7 +58,7 @@ public class Http11AprProcessor extends AbstractHttp11Processor<Long> {
 
 
     public Http11AprProcessor(int headerBufferSize, AprEndpoint endpoint,
-            int maxTrailerSize) {
+            int maxTrailerSize, int maxExtensionSize) {
 
         super(endpoint);
 
@@ -68,7 +68,7 @@ public class Http11AprProcessor extends AbstractHttp11Processor<Long> {
         outputBuffer = new InternalAprOutputBuffer(response, headerBufferSize);
         response.setOutputBuffer(outputBuffer);
 
-        initializeFilters(maxTrailerSize);
+        initializeFilters(maxTrailerSize, maxExtensionSize);
     }
 
 
@@ -455,13 +455,13 @@ public class Http11AprProcessor extends AbstractHttp11Processor<Long> {
             comet = false;
         } else if (actionCode == ActionCode.COMET_CLOSE) {
             ((AprEndpoint)endpoint).processSocketAsync(this.socket,
-                    SocketStatus.OPEN);
+                    SocketStatus.OPEN_READ);
         } else if (actionCode == ActionCode.COMET_SETTIMEOUT) {
             //no op
         } else if (actionCode == ActionCode.ASYNC_COMPLETE) {
             if (asyncStateMachine.asyncComplete()) {
                 ((AprEndpoint)endpoint).processSocketAsync(this.socket,
-                        SocketStatus.OPEN);
+                        SocketStatus.OPEN_READ);
             }
         } else if (actionCode == ActionCode.ASYNC_SETTIMEOUT) {
             if (param==null) {
@@ -472,7 +472,7 @@ public class Http11AprProcessor extends AbstractHttp11Processor<Long> {
         } else if (actionCode == ActionCode.ASYNC_DISPATCH) {
             if (asyncStateMachine.asyncDispatch()) {
                 ((AprEndpoint)endpoint).processSocketAsync(this.socket,
-                        SocketStatus.OPEN);
+                        SocketStatus.OPEN_READ);
             }
         }
 
diff --git a/java/org/apache/coyote/http11/Http11AprProtocol.java b/java/org/apache/coyote/http11/Http11AprProtocol.java
index 06b6d37..903792f 100644
--- a/java/org/apache/coyote/http11/Http11AprProtocol.java
+++ b/java/org/apache/coyote/http11/Http11AprProtocol.java
@@ -20,13 +20,14 @@ import java.io.IOException;
 
 import org.apache.coyote.AbstractProtocol;
 import org.apache.coyote.Processor;
-import org.apache.coyote.http11.upgrade.UpgradeAprProcessor;
-import org.apache.coyote.http11.upgrade.UpgradeInbound;
+import org.apache.coyote.http11.upgrade.AprProcessor;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.AprEndpoint;
 import org.apache.tomcat.util.net.AprEndpoint.Handler;
+import org.apache.tomcat.util.net.AprEndpoint.Poller;
 import org.apache.tomcat.util.net.SocketStatus;
 import org.apache.tomcat.util.net.SocketWrapper;
 
@@ -82,9 +83,6 @@ public class Http11AprProtocol extends AbstractHttp11Protocol {
     public void setPollerSize(int pollerSize) { endpoint.setMaxConnections(pollerSize); }
     public int getPollerSize() { return endpoint.getMaxConnections(); }
 
-    public void setPollerThreadCount(int pollerThreadCount) { ((AprEndpoint)endpoint).setPollerThreadCount(pollerThreadCount); }
-    public int getPollerThreadCount() { return ((AprEndpoint)endpoint).getPollerThreadCount(); }
-    
     public int getSendfileSize() { return ((AprEndpoint)endpoint).getSendfileSize(); }
     public void setSendfileSize(int sendfileSize) { ((AprEndpoint)endpoint).setSendfileSize(sendfileSize); }
     
@@ -249,8 +247,7 @@ public class Http11AprProtocol extends AbstractHttp11Protocol {
             if (addToPoller && proto.endpoint.isRunning()) {
                 ((AprEndpoint)proto.endpoint).getPoller().add(
                         socket.getSocket().longValue(),
-                        proto.endpoint.getKeepAliveTimeout(),
-                        AprEndpoint.Poller.FLAGS_READ);
+                        proto.endpoint.getKeepAliveTimeout(), true, false);
             }
         }
 
@@ -260,10 +257,10 @@ public class Http11AprProtocol extends AbstractHttp11Protocol {
             // NOOP for APR
         }
 
+        @SuppressWarnings("deprecation") // Inbound/Outbound based upgrade
         @Override
         protected void longPoll(SocketWrapper<Long> socket,
                 Processor<Long> processor) {
-            connections.put(socket.getSocket(), processor);
 
             if (processor.isAsync()) {
                 // Async
@@ -271,22 +268,31 @@ public class Http11AprProtocol extends AbstractHttp11Protocol {
             } else if (processor.isComet()) {
                 // Comet
                 if (proto.endpoint.isRunning()) {
-                    ((AprEndpoint) proto.endpoint).getCometPoller().add(
+                    socket.setComet(true);
+                    ((AprEndpoint) proto.endpoint).getPoller().add(
                             socket.getSocket().longValue(),
-                            proto.endpoint.getSoTimeout(),
-                            AprEndpoint.Poller.FLAGS_READ);
+                            proto.endpoint.getSoTimeout(), true, false);
                 } else {
                     // Process a STOP directly
                     ((AprEndpoint) proto.endpoint).processSocket(
                             socket.getSocket().longValue(),
                             SocketStatus.STOP);
                 }
-            } else {
+            } else if (processor.isUpgrade()) {
                 // Upgraded
+                Poller p = ((AprEndpoint) proto.endpoint).getPoller();
+                if (p == null) {
+                    // Connector has been stopped
+                    release(socket, processor, true, false);
+                } else {
+                    p.add(socket.getSocket().longValue(), -1, true, false);
+                }
+            } else {
+                // Tomcat 7 proprietary upgrade
                 ((AprEndpoint) proto.endpoint).getPoller().add(
                         socket.getSocket().longValue(),
                         processor.getUpgradeInbound().getReadTimeout(),
-                        AprEndpoint.Poller.FLAGS_READ);
+                        true, false);
             }
         }
 
@@ -294,7 +300,7 @@ public class Http11AprProtocol extends AbstractHttp11Protocol {
         protected Http11AprProcessor createProcessor() {
             Http11AprProcessor processor = new Http11AprProcessor(
                     proto.getMaxHttpHeaderSize(), (AprEndpoint)proto.endpoint,
-                    proto.getMaxTrailerSize());
+                    proto.getMaxTrailerSize(), proto.getMaxExtensionSize());
             processor.setAdapter(proto.adapter);
             processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());
             processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
@@ -314,11 +320,26 @@ public class Http11AprProtocol extends AbstractHttp11Protocol {
             return processor;
         }
 
+        /**
+         * @deprecated  Will be removed in Tomcat 8.0.x.
+         */
+        @Deprecated
+        @Override
+        protected Processor<Long> createUpgradeProcessor(
+                SocketWrapper<Long> socket,
+                org.apache.coyote.http11.upgrade.UpgradeInbound inbound)
+                throws IOException {
+            return new org.apache.coyote.http11.upgrade.UpgradeAprProcessor(
+                    socket, inbound);
+        }
+        
         @Override
         protected Processor<Long> createUpgradeProcessor(
-                SocketWrapper<Long> socket, UpgradeInbound inbound)
+                SocketWrapper<Long> socket,
+                HttpUpgradeHandler httpUpgradeProcessor)
                 throws IOException {
-            return new UpgradeAprProcessor(socket, inbound);
+            return new AprProcessor(socket, httpUpgradeProcessor,
+                    (AprEndpoint) proto.endpoint);
         }
     }
 }
diff --git a/java/org/apache/coyote/http11/Http11NioProcessor.java b/java/org/apache/coyote/http11/Http11NioProcessor.java
index 8828815..5e80527 100644
--- a/java/org/apache/coyote/http11/Http11NioProcessor.java
+++ b/java/org/apache/coyote/http11/Http11NioProcessor.java
@@ -63,7 +63,7 @@ public class Http11NioProcessor extends AbstractHttp11Processor<NioChannel> {
 
 
     public Http11NioProcessor(int maxHttpHeaderSize, NioEndpoint endpoint,
-            int maxTrailerSize) {
+            int maxTrailerSize, int maxExtensionSize) {
 
         super(endpoint);
 
@@ -73,7 +73,7 @@ public class Http11NioProcessor extends AbstractHttp11Processor<NioChannel> {
         outputBuffer = new InternalNioOutputBuffer(response, maxHttpHeaderSize);
         response.setOutputBuffer(outputBuffer);
 
-        initializeFilters(maxTrailerSize);
+        initializeFilters(maxTrailerSize, maxExtensionSize);
     }
 
 
@@ -478,7 +478,7 @@ public class Http11NioProcessor extends AbstractHttp11Processor<NioChannel> {
         } else if (actionCode == ActionCode.ASYNC_COMPLETE) {
             if (asyncStateMachine.asyncComplete()) {
                 ((NioEndpoint)endpoint).processSocket(this.socket.getSocket(),
-                        SocketStatus.OPEN, true);
+                        SocketStatus.OPEN_READ, true);
             }
         } else if (actionCode == ActionCode.ASYNC_SETTIMEOUT) {
             if (param==null) {
@@ -494,7 +494,7 @@ public class Http11NioProcessor extends AbstractHttp11Processor<NioChannel> {
         } else if (actionCode == ActionCode.ASYNC_DISPATCH) {
             if (asyncStateMachine.asyncDispatch()) {
                 ((NioEndpoint)endpoint).processSocket(this.socket.getSocket(),
-                        SocketStatus.OPEN, true);
+                        SocketStatus.OPEN_READ, true);
             }
         }
     }
diff --git a/java/org/apache/coyote/http11/Http11NioProtocol.java b/java/org/apache/coyote/http11/Http11NioProtocol.java
index 5196c91..a10278a 100644
--- a/java/org/apache/coyote/http11/Http11NioProtocol.java
+++ b/java/org/apache/coyote/http11/Http11NioProtocol.java
@@ -22,8 +22,8 @@ import java.util.Iterator;
 
 import org.apache.coyote.AbstractProtocol;
 import org.apache.coyote.Processor;
-import org.apache.coyote.http11.upgrade.UpgradeInbound;
-import org.apache.coyote.http11.upgrade.UpgradeNioProcessor;
+import org.apache.coyote.http11.upgrade.NioProcessor;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.net.AbstractEndpoint;
@@ -242,7 +242,6 @@ public class Http11NioProtocol extends AbstractHttp11JsseProtocol {
         @Override
         protected void longPoll(SocketWrapper<NioChannel> socket,
                 Processor<NioChannel> processor) {
-            connections.put(socket.getSocket(), processor);
 
             if (processor.isAsync()) {
                 socket.setAsync(true);
@@ -260,7 +259,7 @@ public class Http11NioProtocol extends AbstractHttp11JsseProtocol {
         public Http11NioProcessor createProcessor() {
             Http11NioProcessor processor = new Http11NioProcessor(
                     proto.getMaxHttpHeaderSize(), (NioEndpoint)proto.endpoint,
-                    proto.getMaxTrailerSize());
+                    proto.getMaxTrailerSize(), proto.getMaxExtensionSize());
             processor.setAdapter(proto.adapter);
             processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());
             processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
@@ -279,11 +278,26 @@ public class Http11NioProtocol extends AbstractHttp11JsseProtocol {
             return processor;
         }
 
+        /**
+         * @deprecated  Will be removed in Tomcat 8.0.x.
+         */
+        @Deprecated
+        @Override
+        protected Processor<NioChannel> createUpgradeProcessor(
+                SocketWrapper<NioChannel> socket,
+                org.apache.coyote.http11.upgrade.UpgradeInbound inbound)
+                throws IOException {
+            return new org.apache.coyote.http11.upgrade.UpgradeNioProcessor(
+                    socket, inbound,
+                    ((Http11NioProtocol) getProtocol()).getEndpoint().getSelectorPool());
+        }
+        
         @Override
         protected Processor<NioChannel> createUpgradeProcessor(
-                SocketWrapper<NioChannel> socket, UpgradeInbound inbound)
+                SocketWrapper<NioChannel> socket,
+                HttpUpgradeHandler httpUpgradeProcessor)
                 throws IOException {
-            return new UpgradeNioProcessor(socket, inbound,
+            return new NioProcessor(socket, httpUpgradeProcessor,
                     ((Http11NioProtocol) getProtocol()).getEndpoint().getSelectorPool());
         }
     }
diff --git a/java/org/apache/coyote/http11/Http11Processor.java b/java/org/apache/coyote/http11/Http11Processor.java
index 15c40bb..ff5912d 100644
--- a/java/org/apache/coyote/http11/Http11Processor.java
+++ b/java/org/apache/coyote/http11/Http11Processor.java
@@ -50,7 +50,7 @@ public class Http11Processor extends AbstractHttp11Processor<Socket> {
 
 
     public Http11Processor(int headerBufferSize, JIoEndpoint endpoint,
-            int maxTrailerSize) {
+            int maxTrailerSize, int maxExtensionSize) {
 
         super(endpoint);
         
@@ -60,7 +60,7 @@ public class Http11Processor extends AbstractHttp11Processor<Socket> {
         outputBuffer = new InternalOutputBuffer(response, headerBufferSize);
         response.setOutputBuffer(outputBuffer);
 
-        initializeFilters(maxTrailerSize);
+        initializeFilters(maxTrailerSize, maxExtensionSize);
     }
 
 
@@ -357,7 +357,7 @@ public class Http11Processor extends AbstractHttp11Processor<Socket> {
         } else if (actionCode == ActionCode.ASYNC_COMPLETE) {
             if (asyncStateMachine.asyncComplete()) {
                 ((JIoEndpoint) endpoint).processSocketAsync(this.socket,
-                        SocketStatus.OPEN);
+                        SocketStatus.OPEN_READ);
             }
         } else if (actionCode == ActionCode.ASYNC_SETTIMEOUT) {
             if (param == null) return;
@@ -367,7 +367,7 @@ public class Http11Processor extends AbstractHttp11Processor<Socket> {
         } else if (actionCode == ActionCode.ASYNC_DISPATCH) {
             if (asyncStateMachine.asyncDispatch()) {
                 ((JIoEndpoint) endpoint).processSocketAsync(this.socket,
-                        SocketStatus.OPEN);
+                        SocketStatus.OPEN_READ);
             }
         }
     }
diff --git a/java/org/apache/coyote/http11/Http11Protocol.java b/java/org/apache/coyote/http11/Http11Protocol.java
index 5e7772a..26a8e50 100644
--- a/java/org/apache/coyote/http11/Http11Protocol.java
+++ b/java/org/apache/coyote/http11/Http11Protocol.java
@@ -21,8 +21,8 @@ import java.net.Socket;
 
 import org.apache.coyote.AbstractProtocol;
 import org.apache.coyote.Processor;
-import org.apache.coyote.http11.upgrade.UpgradeBioProcessor;
-import org.apache.coyote.http11.upgrade.UpgradeInbound;
+import org.apache.coyote.http11.upgrade.BioProcessor;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
 import org.apache.juli.logging.Log;
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.JIoEndpoint;
@@ -157,14 +157,14 @@ public class Http11Protocol extends AbstractHttp11JsseProtocol {
         @Override
         protected void longPoll(SocketWrapper<Socket> socket,
                 Processor<Socket> processor) {
-            connections.put(socket.getSocket(), processor);
+            // NO-OP
         }
 
         @Override
         protected Http11Processor createProcessor() {
             Http11Processor processor = new Http11Processor(
                     proto.getMaxHttpHeaderSize(), (JIoEndpoint)proto.endpoint,
-                    proto.getMaxTrailerSize());
+                    proto.getMaxTrailerSize(),proto.getMaxExtensionSize());
             processor.setAdapter(proto.adapter);
             processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());
             processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
@@ -185,11 +185,25 @@ public class Http11Protocol extends AbstractHttp11JsseProtocol {
             return processor;
         }
 
+        /**
+         * @deprecated  Will be removed in Tomcat 8.0.x.
+         */
+        @Deprecated
+        @Override
+        protected Processor<Socket> createUpgradeProcessor(
+                SocketWrapper<Socket> socket,
+                org.apache.coyote.http11.upgrade.UpgradeInbound inbound)
+                throws IOException {
+            return new org.apache.coyote.http11.upgrade.UpgradeBioProcessor(
+                    socket, inbound);
+        }
+        
         @Override
         protected Processor<Socket> createUpgradeProcessor(
-                SocketWrapper<Socket> socket, UpgradeInbound inbound)
+                SocketWrapper<Socket> socket,
+                HttpUpgradeHandler httpUpgradeProcessor)
                 throws IOException {
-            return new UpgradeBioProcessor(socket, inbound);
+            return new BioProcessor(socket, httpUpgradeProcessor);
         }
     }
 }
diff --git a/java/org/apache/coyote/http11/InternalAprOutputBuffer.java b/java/org/apache/coyote/http11/InternalAprOutputBuffer.java
index 94b8a89..54b7c4d 100644
--- a/java/org/apache/coyote/http11/InternalAprOutputBuffer.java
+++ b/java/org/apache/coyote/http11/InternalAprOutputBuffer.java
@@ -63,7 +63,7 @@ public class InternalAprOutputBuffer extends AbstractOutputBuffer<Long> {
         finished = false;
 
         // Cause loading of HttpMessages
-        HttpMessages.getMessage(200);
+        HttpMessages.getInstance(response.getLocale()).getMessage(200);
 
     }
 
diff --git a/java/org/apache/coyote/http11/InternalNioOutputBuffer.java b/java/org/apache/coyote/http11/InternalNioOutputBuffer.java
index 29aa881..961b96c 100644
--- a/java/org/apache/coyote/http11/InternalNioOutputBuffer.java
+++ b/java/org/apache/coyote/http11/InternalNioOutputBuffer.java
@@ -61,7 +61,7 @@ public class InternalNioOutputBuffer extends AbstractOutputBuffer<NioChannel> {
         finished = false;
         
         // Cause loading of HttpMessages
-        HttpMessages.getMessage(200);
+        HttpMessages.getInstance(response.getLocale()).getMessage(200);
 
     }
 
@@ -152,7 +152,7 @@ public class InternalNioOutputBuffer extends AbstractOutputBuffer<NioChannel> {
         int written = 0;
         NioEndpoint.KeyAttachment att = (NioEndpoint.KeyAttachment)socket.getAttachment(false);
         if ( att == null ) throw new IOException("Key must be cancelled");
-        long writeTimeout = att.getTimeout();
+        long writeTimeout = att.getWriteTimeout();
         Selector selector = null;
         try {
             selector = pool.get();
diff --git a/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java b/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
index 50deee6..ff79e9c 100644
--- a/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
+++ b/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
@@ -118,9 +118,23 @@ public class ChunkedInputFilter implements InputFilter {
      */
     private Request request;
     
+
+    /**
+     * Limit for extension size.
+     */
+    private final long maxExtensionSize;
+
+
+    /**
+     * Size of extensions processed for this request.
+     */
+    private long extensionSize;
+
+
     // ----------------------------------------------------------- Constructors
-    public ChunkedInputFilter(int maxTrailerSize) {
+    public ChunkedInputFilter(int maxTrailerSize, int maxExtensionSize) {
         this.trailingHeaders.setLimit(maxTrailerSize);
+        this.maxExtensionSize = maxExtensionSize;
     }
 
     // ---------------------------------------------------- InputBuffer Methods
@@ -250,6 +264,7 @@ public class ChunkedInputFilter implements InputFilter {
         endChunk = false;
         needCRLFParse = false;
         trailingHeaders.recycle();
+        extensionSize = 0;
     }
 
 
@@ -299,7 +314,7 @@ public class ChunkedInputFilter implements InputFilter {
         int result = 0;
         boolean eol = false;
         boolean readDigit = false;
-        boolean trailer = false;
+        boolean extension = false;
 
         while (!eol) {
 
@@ -312,8 +327,9 @@ public class ChunkedInputFilter implements InputFilter {
                 parseCRLF(false);
                 eol = true;
             } else if (buf[pos] == Constants.SEMI_COLON) {
-                trailer = true;
-            } else if (!trailer) { 
+                extension = true;
+                extensionSize++;
+            } else if (!extension) {
                 //don't read data after the trailer
                 int charValue = HexUtils.getDec(buf[pos]);
                 if (charValue != -1) {
@@ -325,6 +341,12 @@ public class ChunkedInputFilter implements InputFilter {
                     //in the chunked header
                     return false;
                 }
+            } else {
+                // extension
+                extensionSize++;
+                if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
+                    throw new IOException("maxExtensionSize exceeded");
+                }
             }
 
             // Parsing the CRLF increments pos
diff --git a/java/org/apache/coyote/http11/upgrade/AbstractProcessor.java b/java/org/apache/coyote/http11/upgrade/AbstractProcessor.java
new file mode 100644
index 0000000..377abce
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/AbstractProcessor.java
@@ -0,0 +1,187 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.coyote.http11.upgrade;
+
+import java.io.IOException;
+import java.util.concurrent.Executor;
+
+import org.apache.coyote.Processor;
+import org.apache.coyote.Request;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
+import org.apache.coyote.http11.upgrade.servlet31.WebConnection;
+import org.apache.juli.logging.Log;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+import org.apache.tomcat.util.net.SSLSupport;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+import org.apache.tomcat.util.res.StringManager;
+
+public abstract class AbstractProcessor<S>
+        implements Processor<S>, WebConnection {
+
+    protected static final StringManager sm =
+            StringManager.getManager(Constants.Package);
+    protected abstract Log getLog();
+
+    private final HttpUpgradeHandler httpUpgradeHandler;
+    private final AbstractServletInputStream upgradeServletInputStream;
+    private final AbstractServletOutputStream upgradeServletOutputStream;
+
+    protected AbstractProcessor (HttpUpgradeHandler httpUpgradeHandler,
+            AbstractServletInputStream upgradeServletInputStream,
+            AbstractServletOutputStream upgradeServletOutputStream) {
+        this.httpUpgradeHandler = httpUpgradeHandler;
+        this.upgradeServletInputStream = upgradeServletInputStream;
+        this.upgradeServletOutputStream = upgradeServletOutputStream;
+    }
+
+
+    // --------------------------------------------------- AutoCloseable methods
+
+    @Override
+    public void close() throws Exception {
+        upgradeServletInputStream.close();
+        upgradeServletOutputStream.close();
+    }
+
+
+    // --------------------------------------------------- WebConnection methods
+
+    @Override
+    public AbstractServletInputStream getInputStream() throws IOException {
+        return upgradeServletInputStream;
+    }
+
+    @Override
+    public AbstractServletOutputStream getOutputStream() throws IOException {
+        return upgradeServletOutputStream;
+    }
+
+
+    // ------------------------------------------- Implemented Processor methods
+
+    @Override
+    public final boolean isUpgrade() {
+        return true;
+    }
+
+    @Override
+    public HttpUpgradeHandler getHttpUpgradeHandler() {
+        return httpUpgradeHandler;
+    }
+
+    @Override
+    public final SocketState upgradeDispatch(SocketStatus status)
+            throws IOException {
+
+        if (status == SocketStatus.OPEN_READ) {
+            upgradeServletInputStream.onDataAvailable();
+        } else if (status == SocketStatus.OPEN_WRITE) {
+            upgradeServletOutputStream.onWritePossible();
+        } else if (status == SocketStatus.STOP) {
+            try {
+                upgradeServletInputStream.close();
+            } catch (IOException ioe) {
+                getLog().debug(sm.getString(
+                        "abstractProcessor.isCloseFail", ioe));
+            }
+            try {
+                upgradeServletOutputStream.close();
+            } catch (IOException ioe) {
+                getLog().debug(sm.getString(
+                        "abstractProcessor.osCloseFail", ioe));
+            }
+            return SocketState.CLOSED;
+        } else {
+            // Unexpected state
+            return SocketState.CLOSED;
+        }
+        if (upgradeServletInputStream.isCloseRequired() ||
+                upgradeServletOutputStream.isCloseRequired()) {
+            return SocketState.CLOSED;
+        }
+        return SocketState.UPGRADED;
+    }
+
+    @Override
+    public final void recycle(boolean socketClosing) {
+        // Currently a NO-OP as upgrade processors are not recycled.
+    }
+
+    
+    // ------------------ Processor methods for Inbound/Outbound based mechanism
+    
+    @Override
+    @Deprecated
+    public UpgradeInbound getUpgradeInbound() {
+        return null;
+    }
+
+    @Override
+    public SocketState upgradeDispatch() throws IOException {
+        return null;
+    }
+
+
+    // ---------------------------- Processor methods that are NO-OP for upgrade
+
+    @Override
+    public final Executor getExecutor() {
+        return null;
+    }
+
+    @Override
+    public final SocketState process(SocketWrapper<S> socketWrapper)
+            throws IOException {
+        return null;
+    }
+
+    @Override
+    public final SocketState event(SocketStatus status) throws IOException {
+        return null;
+    }
+
+    @Override
+    public final SocketState asyncDispatch(SocketStatus status) {
+        return null;
+    }
+
+    @Override
+    public final SocketState asyncPostProcess() {
+        return null;
+    }
+
+    @Override
+    public final boolean isComet() {
+        return false;
+    }
+
+    @Override
+    public final boolean isAsync() {
+        return false;
+    }
+
+    @Override
+    public final Request getRequest() {
+        return null;
+    }
+
+    @Override
+    public final void setSslSupport(SSLSupport sslSupport) {
+        // NOOP
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/AbstractServletInputStream.java b/java/org/apache/coyote/http11/upgrade/AbstractServletInputStream.java
new file mode 100644
index 0000000..9e70c70
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/AbstractServletInputStream.java
@@ -0,0 +1,198 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.coyote.http11.upgrade;
+
+import java.io.IOException;
+
+import javax.servlet.ServletInputStream;
+
+import org.apache.coyote.http11.upgrade.servlet31.ReadListener;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Implements the new Servlet 3.1 methods for {@link ServletInputStream}.
+ */
+public abstract class AbstractServletInputStream extends ServletInputStream {
+
+    protected static final StringManager sm =
+            StringManager.getManager(Constants.Package);
+
+    private volatile boolean closeRequired = false;
+    // Start in blocking-mode
+    private volatile Boolean ready = Boolean.TRUE;
+    private volatile ReadListener listener = null;
+
+
+    /**
+     * New Servlet 3.1 method.
+     */
+    public final boolean isFinished() {
+        if (listener == null) {
+            throw new IllegalStateException(
+                    sm.getString("upgrade.sis.isFinished.ise"));
+        }
+        // The only way to finish an HTTP Upgrade connection is to close the
+        // socket.
+        return false;
+    }
+
+
+    /**
+     * New Servlet 3.1 method.
+     */
+    public final boolean isReady() {
+        if (listener == null) {
+            throw new IllegalStateException(
+                    sm.getString("upgrade.sis.isReady.ise"));
+        }
+
+        // If we already know the current state, return it.
+        if (ready != null) {
+            return ready.booleanValue();
+        }
+
+        try {
+            ready = Boolean.valueOf(doIsReady());
+        } catch (IOException e) {
+            listener.onError(e);
+            ready = Boolean.FALSE;
+        }
+        return ready.booleanValue();
+    }
+
+
+    /**
+     * New Servlet 3.1 method.
+     */
+    public final void setReadListener(ReadListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException(
+                    sm.getString("upgrade.sis.readListener.null"));
+        }
+        this.listener = listener;
+        // Switching to non-blocking. Don't know if data is available.
+        ready = null;
+    }
+
+
+    @Override
+    public final int read() throws IOException {
+        preReadChecks();
+
+        return readInternal();
+    }
+
+
+    @Override
+    public final int readLine(byte[] b, int off, int len) throws IOException {
+        preReadChecks();
+
+        if (len <= 0) {
+            return 0;
+        }
+        int count = 0, c;
+
+        while ((c = readInternal()) != -1) {
+            b[off++] = (byte) c;
+            count++;
+            if (c == '\n' || count == len) {
+                break;
+            }
+        }
+        return count > 0 ? count : -1;
+    }
+
+
+    @Override
+    public final int read(byte[] b, int off, int len) throws IOException {
+        preReadChecks();
+
+        try {
+            return doRead(listener == null, b, off, len);
+        } catch (IOException ioe) {
+            closeRequired = true;
+            throw ioe;
+        }
+    }
+
+
+
+    @Override
+    public void close() throws IOException {
+        closeRequired = true;
+        doClose();
+    }
+
+
+    private void preReadChecks() {
+        if (listener != null && (ready == null || !ready.booleanValue())) {
+            throw new IllegalStateException(
+                    sm.getString("upgrade.sis.read.ise"));
+        }
+        // No longer know if data is available
+        ready = null;
+    }
+
+
+    private int readInternal() throws IOException {
+        // Single byte reads for non-blocking need special handling so all
+        // single byte reads run through this method.
+        ReadListener readListener = this.listener;
+        byte[] b = new byte[1];
+        int result;
+        try {
+            result = doRead(readListener == null, b, 0, 1);
+        } catch (IOException ioe) {
+            closeRequired = true;
+            throw ioe;
+        }
+        if (result == 0) {
+            return -1;
+        } else if (result == -1) {
+            // Will never happen with a network socket. An IOException will be
+            // thrown when the client closes the connection.
+            // Echo back the -1 to be safe.
+            return -1;
+        } else {
+            return b[0] & 0xFF;
+        }
+    }
+
+
+    protected final void onDataAvailable() throws IOException {
+        ready = Boolean.TRUE;
+        listener.onDataAvailable();
+    }
+
+
+    protected final boolean isCloseRequired() {
+        return closeRequired;
+    }
+
+
+    protected abstract boolean doIsReady() throws IOException;
+
+    /**
+     * Abstract method to be overridden by concrete implementations. The base
+     * class will ensure that there are no concurrent calls to this method for
+     * the same socket.
+     */
+    protected abstract int doRead(boolean block, byte[] b, int off, int len)
+            throws IOException;
+
+    protected abstract void doClose() throws IOException;
+}
diff --git a/java/org/apache/coyote/http11/upgrade/AbstractServletOutputStream.java b/java/org/apache/coyote/http11/upgrade/AbstractServletOutputStream.java
new file mode 100644
index 0000000..0f2f532
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/AbstractServletOutputStream.java
@@ -0,0 +1,177 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.coyote.http11.upgrade;
+
+import java.io.IOException;
+
+import javax.servlet.ServletOutputStream;
+
+import org.apache.coyote.http11.upgrade.servlet31.WriteListener;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Implements the new Servlet 3.1 methods for {@link ServletOutputStream}.
+ */
+public abstract class AbstractServletOutputStream extends ServletOutputStream {
+
+    protected static final StringManager sm =
+            StringManager.getManager(Constants.Package);
+
+    private final Object fireListenerLock = new Object();
+    private final Object writeLock = new Object();
+
+    private volatile boolean closeRequired = false;
+    // Start in blocking-mode
+    private volatile WriteListener listener = null;
+    private volatile boolean fireListener = false;
+    private volatile byte[] buffer;
+
+    /**
+     * New Servlet 3.1 method.
+     */
+    public final boolean isReady() {
+        if (listener == null) {
+            throw new IllegalStateException(
+                    sm.getString("upgrade.sos.canWrite.is"));
+        }
+
+        // Make sure isReady() and onWritePossible() have a consistent view of
+        // buffer and fireListener when determining if the listener should fire
+        synchronized (fireListenerLock) {
+            boolean result = (buffer == null);
+            fireListener = !result;
+            return result;
+        }
+    }
+
+    /**
+     * New Servlet 3.1 method.
+     */
+    public final void setWriteListener(WriteListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException(
+                    sm.getString("upgrade.sos.writeListener.null"));
+        }
+        this.listener = listener;
+    }
+
+    protected final boolean isCloseRequired() {
+        return closeRequired;
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        synchronized (writeLock) {
+            preWriteChecks();
+            writeInternal(new byte[] { (byte) b }, 0, 1);
+        }
+    }
+
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        synchronized (writeLock) {
+            preWriteChecks();
+            writeInternal(b, off, len);
+        }
+    }
+
+
+    @Override
+    public void close() throws IOException {
+        closeRequired = true;
+        doClose();
+    }
+
+    private void preWriteChecks() {
+        if (buffer != null) {
+            throw new IllegalStateException(
+                    sm.getString("upgrade.sis.write.ise"));
+        }
+    }
+
+
+    /**
+     * Must hold writeLock to call this method.
+     */
+    private void writeInternal(byte[] b, int off, int len) throws IOException {
+        if (listener == null) {
+            // Simple case - blocking IO
+            doWrite(true, b, off, len);
+        } else {
+            // Non-blocking IO
+            // If the non-blocking read does not complete, doWrite() will add
+            // the socket back into the poller. The poller may trigger a new
+            // write event before this method has finished updating buffer. The
+            // writeLock sync makes sure that buffer is updated before the next
+            // write executes.
+            int written = doWrite(false, b, off, len);
+            if (written < len) {
+                // TODO: - Reuse the buffer
+                //       - Only reallocate if it gets too big (>8k?)
+                buffer = new byte[len - written];
+                System.arraycopy(b, off + written, buffer, 0, len - written);
+            } else {
+                buffer = null;
+            }
+        }
+    }
+
+
+    protected final void onWritePossible() throws IOException {
+        synchronized (writeLock) {
+            try {
+                writeInternal(buffer, 0, buffer.length);
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                listener.onError(t);
+                if (t instanceof IOException) {
+                    throw (IOException) t;
+                } else {
+                    throw new IOException(t);
+                }
+            }
+
+           // Make sure isReady() and onWritePossible() have a consistent view of
+            // buffer and fireListener when determining if the listener should fire
+            boolean fire = false;
+
+            synchronized (fireListenerLock) {
+                if (buffer == null && fireListener) {
+                    fireListener = false;
+                    fire = true;
+                }
+            }
+            if (fire) {
+                listener.onWritePossible();
+            }
+        }
+    }
+
+    /**
+     * Abstract method to be overridden by concrete implementations. The base
+     * class will ensure that there are no concurrent calls to this method for
+     * the same socket.
+     */
+    protected abstract int doWrite(boolean block, byte[] b, int off, int len)
+            throws IOException;
+
+    protected abstract void doFlush() throws IOException;
+
+    protected abstract void doClose() throws IOException;
+}
diff --git a/java/org/apache/coyote/http11/upgrade/AprProcessor.java b/java/org/apache/coyote/http11/upgrade/AprProcessor.java
new file mode 100644
index 0000000..4e19a41
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/AprProcessor.java
@@ -0,0 +1,42 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.coyote.http11.upgrade;
+
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.util.net.AprEndpoint;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class AprProcessor extends AbstractProcessor<Long> {
+
+    private static final Log log = LogFactory.getLog(AprProcessor.class);
+    @Override
+    protected Log getLog() {return log;}
+
+    private static final int INFINITE_TIMEOUT = -1;
+
+    public AprProcessor(SocketWrapper<Long> wrapper,
+            HttpUpgradeHandler httpUpgradeProcessor, AprEndpoint endpoint) {
+        super(httpUpgradeProcessor,
+                new AprServletInputStream(wrapper),
+                new AprServletOutputStream(wrapper, endpoint));
+
+        Socket.timeoutSet(wrapper.getSocket().longValue(), INFINITE_TIMEOUT);
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/AprServletInputStream.java b/java/org/apache/coyote/http11/upgrade/AprServletInputStream.java
new file mode 100644
index 0000000..3c339e2
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/AprServletInputStream.java
@@ -0,0 +1,114 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.coyote.http11.upgrade;
+
+import java.io.IOException;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
+
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.jni.Status;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class AprServletInputStream extends AbstractServletInputStream {
+
+    private final SocketWrapper<Long> wrapper;
+    private final long socket;
+    private volatile boolean eagain = false;
+    private volatile boolean closed = false;
+
+
+    public AprServletInputStream(SocketWrapper<Long> wrapper) {
+        this.wrapper = wrapper;
+        this.socket = wrapper.getSocket().longValue();
+    }
+
+
+    @Override
+    protected int doRead(boolean block, byte[] b, int off, int len)
+            throws IOException {
+
+        Lock readLock = wrapper.getBlockingStatusReadLock();
+        WriteLock writeLock = wrapper.getBlockingStatusWriteLock();
+
+        boolean readDone = false;
+        int result = 0;
+        try {
+            readLock.lock();
+            if (wrapper.getBlockingStatus() == block) {
+                if (closed) {
+                    throw new IOException(sm.getString("apr.closed"));
+                }
+                result = Socket.recv(socket, b, off, len);
+                readDone = true;
+            }
+        } finally {
+            readLock.unlock();
+        }
+
+        if (!readDone) {
+            try {
+                writeLock.lock();
+                wrapper.setBlockingStatus(block);
+                // Set the current settings for this socket
+                Socket.optSet(socket, Socket.APR_SO_NONBLOCK, (block ? 0 : 1));
+                // Downgrade the lock
+                try {
+                    readLock.lock();
+                    writeLock.unlock();
+                    if (closed) {
+                        throw new IOException(sm.getString("apr.closed"));
+                    }
+                    result = Socket.recv(socket, b, off, len);
+                } finally {
+                    readLock.unlock();
+                }
+            } finally {
+                // Should have been released above but may not have been on some
+                // exception paths
+                if (writeLock.isHeldByCurrentThread()) {
+                    writeLock.unlock();
+                }
+            }
+        }
+
+        if (result > 0) {
+            eagain = false;
+            return result;
+        } else if (-result == Status.EAGAIN) {
+            eagain = true;
+            return 0;
+        } else {
+            throw new IOException(sm.getString("apr.read.error",
+                    Integer.valueOf(-result)));
+        }
+    }
+
+
+    @Override
+    protected boolean doIsReady() {
+        return !eagain;
+    }
+
+
+    @Override
+    protected void doClose() throws IOException {
+        closed = true;
+        // AbstractProcessor needs to trigger the close as multiple closes for
+        // APR/native sockets will cause problems.
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/AprServletOutputStream.java b/java/org/apache/coyote/http11/upgrade/AprServletOutputStream.java
new file mode 100644
index 0000000..fb15fd1
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/AprServletOutputStream.java
@@ -0,0 +1,162 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.coyote.http11.upgrade;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
+
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.jni.Status;
+import org.apache.tomcat.util.net.AprEndpoint;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class AprServletOutputStream extends AbstractServletOutputStream {
+
+    private static final int SSL_OUTPUT_BUFFER_SIZE = 8192;
+
+    private final AprEndpoint endpoint;
+    private final SocketWrapper<Long> wrapper;
+    private final long socket;
+    private volatile boolean closed = false;
+    private final ByteBuffer sslOutputBuffer;
+
+    public AprServletOutputStream(SocketWrapper<Long> wrapper,
+            AprEndpoint endpoint) {
+        this.endpoint = endpoint;
+        this.wrapper = wrapper;
+        this.socket = wrapper.getSocket().longValue();
+        if (endpoint.isSSLEnabled()) {
+            sslOutputBuffer = ByteBuffer.allocateDirect(SSL_OUTPUT_BUFFER_SIZE);
+            sslOutputBuffer.position(SSL_OUTPUT_BUFFER_SIZE);
+        } else {
+            sslOutputBuffer = null;
+        }
+    }
+
+
+    @Override
+    protected int doWrite(boolean block, byte[] b, int off, int len)
+            throws IOException {
+
+        Lock readLock = wrapper.getBlockingStatusReadLock();
+        WriteLock writeLock = wrapper.getBlockingStatusWriteLock();
+
+        try {
+            readLock.lock();
+            if (wrapper.getBlockingStatus() == block) {
+                if (closed) {
+                    throw new IOException(sm.getString("apr.closed"));
+                }
+                return doWriteInternal(b, off, len);
+            }
+        } finally {
+            readLock.unlock();
+        }
+
+        try {
+            writeLock.lock();
+            // Set the current settings for this socket
+            wrapper.setBlockingStatus(block);
+            if (block) {
+                Socket.timeoutSet(socket, endpoint.getSoTimeout() * 1000);
+            } else {
+                Socket.timeoutSet(socket, 0);
+            }
+
+            // Downgrade the lock
+            try {
+                readLock.lock();
+                writeLock.unlock();
+                if (closed) {
+                    throw new IOException(sm.getString("apr.closed"));
+                }
+                return doWriteInternal(b, off, len);
+            } finally {
+                readLock.unlock();
+            }
+        } finally {
+            // Should have been released above but may not have been on some
+            // exception paths
+            if (writeLock.isHeldByCurrentThread()) {
+                writeLock.unlock();
+            }
+        }
+    }
+
+
+    private int doWriteInternal(byte[] b, int off, int len) throws IOException {
+
+        int start = off;
+        int left = len;
+        int written;
+
+        do {
+            if (endpoint.isSSLEnabled()) {
+                if (sslOutputBuffer.remaining() == 0) {
+                    // Buffer was fully written last time around
+                    sslOutputBuffer.clear();
+                    if (left < SSL_OUTPUT_BUFFER_SIZE) {
+                        sslOutputBuffer.put(b, start, left);
+                    } else {
+                        sslOutputBuffer.put(b, start, SSL_OUTPUT_BUFFER_SIZE);
+                    }
+                    sslOutputBuffer.flip();
+                } else {
+                    // Buffer still has data from previous attempt to write
+                    // APR + SSL requires that exactly the same parameters are
+                    // passed when re-attempting the write
+                }
+                written = Socket.sendb(socket, sslOutputBuffer, start, left);
+                if (written > 0) {
+                    sslOutputBuffer.position(
+                            sslOutputBuffer.position() + written);
+                }
+            } else {
+                written = Socket.send(socket, b, start, left);
+            }
+            if (Status.APR_STATUS_IS_EAGAIN(-written)) {
+                written = 0;
+            } else if (written < 0) {
+                throw new IOException(sm.getString("apr.write.error",
+                        Integer.valueOf(-written)));
+            }
+            start += written;
+            left -= written;
+        } while (written > 0 && left > 0);
+
+        if (left > 0) {
+            endpoint.getPoller().add(socket, -1, false, true);
+        }
+        return len - left;
+    }
+
+
+    @Override
+    protected void doFlush() throws IOException {
+        // TODO Auto-generated method stub
+    }
+
+
+    @Override
+    protected void doClose() throws IOException {
+        closed = true;
+        // AbstractProcessor needs to trigger the close as multiple closes for
+        // APR/native sockets will cause problems.
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/BioProcessor.java b/java/org/apache/coyote/http11/upgrade/BioProcessor.java
new file mode 100644
index 0000000..e08840a
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/BioProcessor.java
@@ -0,0 +1,42 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.coyote.http11.upgrade;
+
+import java.io.IOException;
+import java.net.Socket;
+
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class BioProcessor extends AbstractProcessor<Socket> {
+
+    private static final Log log = LogFactory.getLog(BioProcessor.class);
+    @Override
+    protected Log getLog() {return log;}
+
+    private static final int INFINITE_TIMEOUT = 0;
+
+    public BioProcessor(SocketWrapper<Socket> wrapper,
+            HttpUpgradeHandler httpUpgradeProcessor) throws IOException {
+        super(httpUpgradeProcessor, new BioServletInputStream(wrapper),
+                new BioServletOutputStream(wrapper));
+
+        wrapper.getSocket().setSoTimeout(INFINITE_TIMEOUT);
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/BioServletInputStream.java b/java/org/apache/coyote/http11/upgrade/BioServletInputStream.java
new file mode 100644
index 0000000..a2048c6
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/BioServletInputStream.java
@@ -0,0 +1,50 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.coyote.http11.upgrade;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Socket;
+
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class BioServletInputStream extends AbstractServletInputStream {
+
+    private final InputStream inputStream;
+
+    public BioServletInputStream(SocketWrapper<Socket> wrapper)
+            throws IOException {
+        inputStream = wrapper.getSocket().getInputStream();
+    }
+
+    @Override
+    protected int doRead(boolean block, byte[] b, int off, int len)
+            throws IOException {
+        return inputStream.read(b, off, len);
+    }
+
+    @Override
+    protected boolean doIsReady() {
+        // Always returns true for BIO
+        return true;
+    }
+
+    @Override
+    protected void doClose() throws IOException {
+        inputStream.close();
+    }
+}
diff --git a/java/org/apache/tomcat/util/net/SocketStatus.java b/java/org/apache/coyote/http11/upgrade/BioServletOutputStream.java
similarity index 50%
copy from java/org/apache/tomcat/util/net/SocketStatus.java
copy to java/org/apache/coyote/http11/upgrade/BioServletOutputStream.java
index bf53c2b..da5ae78 100644
--- a/java/org/apache/tomcat/util/net/SocketStatus.java
+++ b/java/org/apache/coyote/http11/upgrade/BioServletOutputStream.java
@@ -14,14 +14,37 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
+package org.apache.coyote.http11.upgrade;
 
-package org.apache.tomcat.util.net;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Socket;
 
-/**
- * Someone, please change the enum name.
- *
- * @author remm
- */
-public enum SocketStatus {
-    OPEN, STOP, TIMEOUT, DISCONNECT, ERROR
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class BioServletOutputStream extends AbstractServletOutputStream {
+
+    private final OutputStream os;
+
+    public BioServletOutputStream(SocketWrapper<Socket> wrapper)
+            throws IOException {
+        os = wrapper.getSocket().getOutputStream();
+    }
+
+    @Override
+    protected int doWrite(boolean block, byte[] b, int off, int len)
+            throws IOException {
+        os.write(b, off, len);
+        return len;
+    }
+
+    @Override
+    protected void doFlush() throws IOException {
+        os.flush();
+    }
+
+    @Override
+    protected void doClose() throws IOException {
+        os.close();
+    }
 }
diff --git a/java/org/apache/coyote/http11/upgrade/LocalStrings.properties b/java/org/apache/coyote/http11/upgrade/LocalStrings.properties
index 410cc5c..c8ff408 100644
--- a/java/org/apache/coyote/http11/upgrade/LocalStrings.properties
+++ b/java/org/apache/coyote/http11/upgrade/LocalStrings.properties
@@ -13,8 +13,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-apr.read.error=Error [{0}] reading data from the APR/native socket.
-apr.write.error=Error [{0}] writing data to the APR/native socket.
+abstractProcessor.isCloseFail=Failed to close input stream associated with upgraded connection
+abstractProcessor.osCloseFail=Failed to close output stream associated with upgraded connection
 
-nio.eof.error=Unexpected EOF read on the socket
+upgrade.sis.isFinished.ise=It is illegal to call isFinished() when the ServletInputStream is not in non-blocking mode (i.e. setReadListener() must be called first)
+upgrade.sis.isReady.ise=It is illegal to call isReady() when the ServletInputStream is not in non-blocking mode (i.e. setReadListener() must be called first)
+upgrade.sis.readListener.null=It is illegal to pass null to setReadListener()
+upgrade.sis.read.ise=It is illegal to call any of the read() methods in non-blocking mode without first checking that there is data available by calling isReady()
+upgrade.sos.canWrite.ise=It is illegal to call canWrite() when the ServletOutputStream is not in non-blocking mode (i.e. setWriteListener() must be called first)
+upgrade.sos.writeListener.null=It is illegal to pass null to setWriteListener()
+upgrade.sis.write.ise=It is illegal to call any of the write() methods in non-blocking mode without first checking that there is space available by calling isReady()
+
+apr.read.error=Unexpected error [{0}] reading data from the APR/native socket.
+apr.write.error=Unexpected error [{0}] writing data to the APR/native socket.
+apr.closed=The socket associated with this connection has been closed.
 
+nio.eof.error=Unexpected EOF read on the socket
diff --git a/java/org/apache/coyote/http11/upgrade/NioProcessor.java b/java/org/apache/coyote/http11/upgrade/NioProcessor.java
new file mode 100644
index 0000000..af1e18e
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/NioProcessor.java
@@ -0,0 +1,42 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.coyote.http11.upgrade;
+
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.NioChannel;
+import org.apache.tomcat.util.net.NioSelectorPool;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class NioProcessor extends AbstractProcessor<NioChannel> {
+
+    private static final Log log = LogFactory.getLog(NioProcessor.class);
+    @Override
+    protected Log getLog() {return log;}
+
+    private static final int INFINITE_TIMEOUT = -1;
+
+    public NioProcessor(SocketWrapper<NioChannel> wrapper,
+            HttpUpgradeHandler httpUpgradeProcessor, NioSelectorPool pool) {
+        super(httpUpgradeProcessor,
+                new NioServletInputStream(wrapper, pool),
+                new NioServletOutputStream(wrapper, pool));
+
+        wrapper.setTimeout(INFINITE_TIMEOUT);
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/NioServletInputStream.java b/java/org/apache/coyote/http11/upgrade/NioServletInputStream.java
new file mode 100644
index 0000000..2af62b9
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/NioServletInputStream.java
@@ -0,0 +1,140 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.coyote.http11.upgrade;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.Selector;
+
+import org.apache.tomcat.util.net.NioChannel;
+import org.apache.tomcat.util.net.NioEndpoint;
+import org.apache.tomcat.util.net.NioSelectorPool;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class NioServletInputStream extends AbstractServletInputStream {
+
+    private final NioChannel channel;
+    private final NioSelectorPool pool;
+
+    public NioServletInputStream(SocketWrapper<NioChannel> wrapper,
+            NioSelectorPool pool) {
+        this.channel = wrapper.getSocket();
+        this.pool = pool;
+    }
+
+    @Override
+    protected boolean doIsReady() throws IOException {
+        ByteBuffer readBuffer = channel.getBufHandler().getReadBuffer();
+
+        if (readBuffer.remaining() > 0) {
+            return true;
+        }
+
+        readBuffer.clear();
+        fillReadBuffer(false);
+
+        boolean isReady = readBuffer.position() > 0;
+        readBuffer.flip();
+        return isReady;
+    }
+
+    @Override
+    protected int doRead(boolean block, byte[] b, int off, int len)
+            throws IOException {
+
+        ByteBuffer readBuffer = channel.getBufHandler().getReadBuffer();
+        int remaining = readBuffer.remaining();
+
+        // Is there enough data in the read buffer to satisfy this request?
+        if (remaining >= len) {
+            readBuffer.get(b, off, len);
+            return len;
+        }
+
+        // Copy what data there is in the read buffer to the byte array
+        int leftToWrite = len;
+        int newOffset = off;
+        if (remaining > 0) {
+            readBuffer.get(b, off, remaining);
+            leftToWrite -= remaining;
+            newOffset += remaining;
+        }
+
+        // Fill the read buffer as best we can
+        readBuffer.clear();
+        int nRead = fillReadBuffer(block);
+
+        // Full as much of the remaining byte array as possible with the data
+        // that was just read
+        if (nRead > 0) {
+            readBuffer.flip();
+            if (nRead > leftToWrite) {
+                readBuffer.get(b, newOffset, leftToWrite);
+                leftToWrite = 0;
+            } else {
+                readBuffer.get(b, newOffset, nRead);
+                leftToWrite -= nRead;
+            }
+        } else if (nRead == 0) {
+            readBuffer.flip();
+        } else if (nRead == -1) {
+            // TODO i18n
+            throw new EOFException();
+        }
+
+        return len - leftToWrite;
+    }
+
+
+
+    @Override
+    protected void doClose() throws IOException {
+        channel.close();
+    }
+
+
+    private int fillReadBuffer(boolean block) throws IOException {
+        int nRead;
+        if (block) {
+            Selector selector = null;
+            try {
+                selector = pool.get();
+            } catch ( IOException x ) {
+                // Ignore
+            }
+            try {
+                NioEndpoint.KeyAttachment att =
+                        (NioEndpoint.KeyAttachment) channel.getAttachment(false);
+                if (att == null) {
+                    throw new IOException("Key must be cancelled.");
+                }
+                nRead = pool.read(channel.getBufHandler().getReadBuffer(),
+                        channel, selector, att.getTimeout());
+            } catch (EOFException eof) {
+                nRead = -1;
+            } finally {
+                if (selector != null) {
+                    pool.put(selector);
+                }
+            }
+        } else {
+            nRead = channel.read(channel.getBufHandler().getReadBuffer());
+        }
+        return nRead;
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/NioServletOutputStream.java b/java/org/apache/coyote/http11/upgrade/NioServletOutputStream.java
new file mode 100644
index 0000000..f41c446
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/NioServletOutputStream.java
@@ -0,0 +1,138 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.coyote.http11.upgrade;
+
+import java.io.IOException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+
+import org.apache.tomcat.util.net.NioChannel;
+import org.apache.tomcat.util.net.NioEndpoint;
+import org.apache.tomcat.util.net.NioSelectorPool;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class NioServletOutputStream extends AbstractServletOutputStream {
+
+    private final NioChannel channel;
+    private final NioSelectorPool pool;
+    private final int maxWrite;
+
+
+    public NioServletOutputStream(
+            SocketWrapper<NioChannel> wrapper, NioSelectorPool pool) {
+        channel = wrapper.getSocket();
+        this.pool = pool;
+        maxWrite = channel.getBufHandler().getWriteBuffer().capacity();
+    }
+
+
+    @Override
+    protected int doWrite(boolean block, byte[] b, int off, int len)
+            throws IOException {
+        int leftToWrite = len;
+        int count = 0;
+        int offset = off;
+
+        while (leftToWrite > 0) {
+            int writeThisLoop;
+            int writtenThisLoop;
+
+            if (leftToWrite > maxWrite) {
+                writeThisLoop = maxWrite;
+            } else {
+                writeThisLoop = leftToWrite;
+            }
+
+            writtenThisLoop = doWriteInternal(block, b, offset, writeThisLoop);
+            count += writtenThisLoop;
+            leftToWrite -= writtenThisLoop;
+
+            if (writtenThisLoop < writeThisLoop) {
+                break;
+            }
+        }
+
+        return count;
+    }
+
+    private int doWriteInternal (boolean block, byte[] b, int off, int len)
+            throws IOException {
+        channel.getBufHandler().getWriteBuffer().clear();
+        channel.getBufHandler().getWriteBuffer().put(b, off, len);
+        channel.getBufHandler().getWriteBuffer().flip();
+
+        int written = 0;
+        NioEndpoint.KeyAttachment att =
+                (NioEndpoint.KeyAttachment) channel.getAttachment(false);
+        if (att == null) {
+            throw new IOException("Key must be cancelled");
+        }
+        long writeTimeout = att.getWriteTimeout();
+        Selector selector = null;
+        try {
+            selector = pool.get();
+        } catch ( IOException x ) {
+            //ignore
+        }
+        try {
+            written = pool.write(channel.getBufHandler().getWriteBuffer(),
+                    channel, selector, writeTimeout, block);
+        } finally {
+            if (selector != null) {
+                pool.put(selector);
+            }
+        }
+        if (written < len) {
+            channel.getPoller().add(channel, SelectionKey.OP_WRITE);
+        }
+        return written;
+    }
+
+
+    @Override
+    protected void doFlush() throws IOException {
+        NioEndpoint.KeyAttachment att =
+                (NioEndpoint.KeyAttachment) channel.getAttachment(false);
+        if (att == null) {
+            throw new IOException("Key must be cancelled");
+        }
+        long writeTimeout = att.getWriteTimeout();
+        Selector selector = null;
+        try {
+            selector = pool.get();
+        } catch ( IOException x ) {
+            //ignore
+        }
+        try {
+            do {
+                if (channel.flush(true, selector, writeTimeout)) {
+                    break;
+                }
+            } while (true);
+        } finally {
+            if (selector != null) {
+                pool.put(selector);
+            }
+        }
+    }
+
+
+    @Override
+    protected void doClose() throws IOException {
+        channel.close();
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeAprProcessor.java b/java/org/apache/coyote/http11/upgrade/UpgradeAprProcessor.java
index 593bab5..56280fb 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeAprProcessor.java
+++ b/java/org/apache/coyote/http11/upgrade/UpgradeAprProcessor.java
@@ -22,6 +22,10 @@ import org.apache.tomcat.jni.Socket;
 import org.apache.tomcat.jni.Status;
 import org.apache.tomcat.util.net.SocketWrapper;
 
+/**
+ * @deprecated  Will be removed in Tomcat 8.0.x.
+ */
+ at Deprecated
 public class UpgradeAprProcessor extends UpgradeProcessor<Long> {
 
     private final long socket;
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeBioProcessor.java b/java/org/apache/coyote/http11/upgrade/UpgradeBioProcessor.java
index 2a7a10b..7560f0c 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeBioProcessor.java
+++ b/java/org/apache/coyote/http11/upgrade/UpgradeBioProcessor.java
@@ -23,6 +23,10 @@ import java.net.Socket;
 
 import org.apache.tomcat.util.net.SocketWrapper;
 
+/**
+ * @deprecated  Will be removed in Tomcat 8.0.x.
+ */
+ at Deprecated
 public class UpgradeBioProcessor extends UpgradeProcessor<Socket> {
 
     private final InputStream inputStream;
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeInbound.java b/java/org/apache/coyote/http11/upgrade/UpgradeInbound.java
index 3e9e60b..5ec2ff8 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeInbound.java
+++ b/java/org/apache/coyote/http11/upgrade/UpgradeInbound.java
@@ -23,7 +23,10 @@ import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
 /**
  * Receives notification that there is data to be read on the upgraded
  * connection and processes it.
+ * 
+ * @deprecated  Will be removed in Tomcat 8.0.x.
  */
+ at Deprecated
 public interface UpgradeInbound {
 
     void setUpgradeProcessor(UpgradeProcessor<?> processor);
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeNioProcessor.java b/java/org/apache/coyote/http11/upgrade/UpgradeNioProcessor.java
index 035a879..9daa76f 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeNioProcessor.java
+++ b/java/org/apache/coyote/http11/upgrade/UpgradeNioProcessor.java
@@ -26,6 +26,10 @@ import org.apache.tomcat.util.net.NioEndpoint;
 import org.apache.tomcat.util.net.NioSelectorPool;
 import org.apache.tomcat.util.net.SocketWrapper;
 
+/**
+ * @deprecated  Will be removed in Tomcat 8.0.x.
+ */
+ at Deprecated
 public class UpgradeNioProcessor extends UpgradeProcessor<NioChannel> {
 
     private final NioChannel nioChannel;
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java b/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
index 813bea8..627baaf 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
+++ b/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
@@ -22,7 +22,10 @@ import java.io.OutputStream;
 
 /**
  * Allows data to be written to the upgraded connection.
+ * 
+ * @deprecated  Will be removed in Tomcat 8.0.x.
  */
+ at Deprecated
 public class UpgradeOutbound extends OutputStream {
 
     @Override
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java b/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java
index 2b6a1a1..5fc9691 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java
+++ b/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java
@@ -21,12 +21,17 @@ import java.util.concurrent.Executor;
 
 import org.apache.coyote.Processor;
 import org.apache.coyote.Request;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
 import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
 import org.apache.tomcat.util.net.SSLSupport;
 import org.apache.tomcat.util.net.SocketStatus;
 import org.apache.tomcat.util.net.SocketWrapper;
 import org.apache.tomcat.util.res.StringManager;
 
+/**
+ * @deprecated  Will be removed in Tomcat 8.0.x.
+ */
+ at Deprecated
 public abstract class UpgradeProcessor<S> implements Processor<S> {
 
     protected static final StringManager sm =
@@ -84,15 +89,29 @@ public abstract class UpgradeProcessor<S> implements Processor<S> {
     }
 
     @Override
-    public final boolean isUpgrade() {
-        return true;
+    public final void recycle(boolean socketClosing) {
+        // Currently a NO-OP as upgrade processors are not recycled.
     }
 
+    
+    // Servlet 3.1 based HTTP upgrade mechanism. NO-OPs for the proprietary
+    // Tomcat upgrade mechanism.
     @Override
-    public final void recycle(boolean socketClosing) {
-        // Currently a NO-OP as upgrade processors are not recycled.
+    public HttpUpgradeHandler getHttpUpgradeHandler() {
+        return null;
     }
 
+    @Override
+    public SocketState upgradeDispatch(SocketStatus status) throws IOException {
+        return null;
+    }
+
+    @Override
+    public boolean isUpgrade() {
+        return false;
+    }
+    
+    
     // NO-OP methods for upgrade
     @Override
     public final Executor getExecutor() {
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeInbound.java b/java/org/apache/coyote/http11/upgrade/servlet31/HttpUpgradeHandler.java
similarity index 51%
copy from java/org/apache/coyote/http11/upgrade/UpgradeInbound.java
copy to java/org/apache/coyote/http11/upgrade/servlet31/HttpUpgradeHandler.java
index 3e9e60b..6daf693 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeInbound.java
+++ b/java/org/apache/coyote/http11/upgrade/servlet31/HttpUpgradeHandler.java
@@ -6,7 +6,7 @@
  * (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
+ *     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,
@@ -14,31 +14,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.coyote.http11.upgrade;
-
-import java.io.IOException;
-
-import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+package org.apache.coyote.http11.upgrade.servlet31;
 
 /**
- * Receives notification that there is data to be read on the upgraded
- * connection and processes it.
+ * Interface between the HTTP upgrade process and the new protocol.
  */
-public interface UpgradeInbound {
-
-    void setUpgradeProcessor(UpgradeProcessor<?> processor);
-
-    void onUpgradeComplete();
-
-    SocketState onData() throws IOException;
-
-    void setUpgradeOutbound(UpgradeOutbound upgradeOutbound);
+public interface HttpUpgradeHandler {
 
     /**
-     * Allow the upgraded protocol to define the read timeout to be used with
-     * the upgraded connection.
+     * This method is called once the request/response pair where the upgrade
+     * is initiated has completed processing and is the point where control of
+     * the connection passes from the container to the
+     * {@link HttpUpgradeHandler}.
      *
-     * @return  The read timeout in milliseconds or -1 for infinite
+     * @param connection    The connection that has been upgraded
+     */
+    void init(WebConnection connection);
+
+    /**
+     * This method is called after the upgraded connection has been closed.
      */
-    int getReadTimeout();
+    void destroy();
 }
diff --git a/java/org/apache/coyote/http11/upgrade/servlet31/ReadListener.java b/java/org/apache/coyote/http11/upgrade/servlet31/ReadListener.java
new file mode 100644
index 0000000..472b695
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/servlet31/ReadListener.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.coyote.http11.upgrade.servlet31;
+
+import java.io.IOException;
+
+/**
+ * Receives notification of read events when using non-blocking IO.
+ */
+public interface ReadListener extends java.util.EventListener{
+
+    /**
+     * Invoked when data is available to read. The container will invoke this
+     * method the first time for a request as soon as there is data to read.
+     * Subsequent invocations will only occur if a call to {@link
+     * org.apache.coyote.http11.upgrade.AbstractServletInputStream#isReady()}
+     * has returned false and data has subsequently become available to read.
+     *
+     * @throws IOException
+     */
+    public abstract void onDataAvailable() throws IOException;
+
+    /**
+     * Invoked when the request body has been fully read.
+     *
+     * @throws IOException
+     */
+    public abstract void onAllDataRead() throws IOException;
+
+    /**
+     * Invoked if an error occurs while reading the request body.
+     *
+     * @param throwable The exception that occurred
+     */
+    public abstract void onError(java.lang.Throwable throwable);
+}
diff --git a/java/org/apache/coyote/http11/upgrade/servlet31/WebConnection.java b/java/org/apache/coyote/http11/upgrade/servlet31/WebConnection.java
new file mode 100644
index 0000000..a54c42a
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/servlet31/WebConnection.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.coyote.http11.upgrade.servlet31;
+
+import java.io.IOException;
+
+import org.apache.coyote.http11.upgrade.AbstractServletInputStream;
+import org.apache.coyote.http11.upgrade.AbstractServletOutputStream;
+
+/**
+ * The interface used by a {@link HttpUpgradeHandler} to interact with an upgraded
+ * HTTP connection.
+ */
+public interface WebConnection {
+
+    /**
+     * Provides access to the {@link AbstractServletInputStream} for reading
+     * data from the client.
+     */
+    AbstractServletInputStream getInputStream() throws IOException;
+
+    /**
+     * Provides access to the {@link AbstractServletOutputStream} for writing
+     * data to the client.
+     */
+    AbstractServletOutputStream getOutputStream() throws IOException;
+    
+    /**
+     * The Servlet 3.1 interface extends AutoCloseable but that is not available
+     * in Java 6 so this is the single method from that interface.
+     */
+    void close() throws Exception;
+}
\ No newline at end of file
diff --git a/java/org/apache/coyote/http11/upgrade/servlet31/WriteListener.java b/java/org/apache/coyote/http11/upgrade/servlet31/WriteListener.java
new file mode 100644
index 0000000..6eee652
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/servlet31/WriteListener.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.coyote.http11.upgrade.servlet31;
+
+import java.io.IOException;
+
+/**
+ * Receives notification of write events when using non-blocking IO.
+ */
+public interface WriteListener extends java.util.EventListener{
+
+    /**
+     * Invoked when it it possible to write data without blocking. The container
+     * will invoke this method the first time for a request as soon as data can
+     * be written. Subsequent invocations will only occur if a call to {@link
+     * org.apache.coyote.http11.upgrade.AbstractServletOutputStream#isReady()}
+     * has returned false and it has since become possible to write data.
+     *
+     * @throws IOException
+     */
+    public void onWritePossible() throws IOException;
+
+    /**
+     * Invoked if an error occurs while writing the response.
+     *
+     * @param throwable
+     */
+    public void onError(java.lang.Throwable throwable);
+}
\ No newline at end of file
diff --git a/java/org/apache/jasper/JspC.java b/java/org/apache/jasper/JspC.java
index 85034cf..64cae35 100644
--- a/java/org/apache/jasper/JspC.java
+++ b/java/org/apache/jasper/JspC.java
@@ -1257,8 +1257,6 @@ public class JspC extends Task implements Options {
 
     /**
      * Executes the compilation.
-     *
-     * @throws JasperException If an error occurs
      */
     @Override
     public void execute() {
diff --git a/java/org/apache/jasper/compiler/Generator.java b/java/org/apache/jasper/compiler/Generator.java
index b903ad8..aef7c90 100644
--- a/java/org/apache/jasper/compiler/Generator.java
+++ b/java/org/apache/jasper/compiler/Generator.java
@@ -821,7 +821,7 @@ class Generator {
          *            attributes that aren't EL expressions)
          */
         private String attributeValue(Node.JspAttribute attr, boolean encode,
-                Class<?> expectedType) {
+                Class<?> expectedType, boolean isXml) {
             String v = attr.getValue();
             if (!attr.isNamedAttribute() && (v == null))
                 return "";
@@ -834,7 +834,7 @@ class Generator {
                 return v;
             } else if (attr.isELInterpreterInput()) {
                 v = elInterpreter.interpreterCall(ctxt, this.isTagFile, v,
-                        expectedType, attr.getEL().getMapName(), false);
+                        expectedType, attr.getEL().getMapName(), isXml);
                 if (encode) {
                     return "org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode("
                             + v + ", request.getCharacterEncoding())";
@@ -879,7 +879,8 @@ class Generator {
                             + "URLEncode(" + quote(n.getTextAttribute("name"))
                             + ", request.getCharacterEncoding())");
                     out.print("+ \"=\" + ");
-                    out.print(attributeValue(n.getValue(), true, String.class));
+                    out.print(attributeValue(n.getValue(), true, String.class,
+                            n.getRoot().isXmlSyntax()));
 
                     // The separator is '&' after the second use
                     separator = "\"&\"";
@@ -950,7 +951,8 @@ class Generator {
                 pageParam = generateNamedAttributeValue(page
                         .getNamedAttributeNode());
             } else {
-                pageParam = attributeValue(page, false, String.class);
+                pageParam = attributeValue(page, false, String.class,
+                        n.getRoot().isXmlSyntax());
             }
 
             // If any of the params have their values specified by
@@ -1036,7 +1038,8 @@ class Generator {
                 pageParam = generateNamedAttributeValue(page
                         .getNamedAttributeNode());
             } else {
-                pageParam = attributeValue(page, false, String.class);
+                pageParam = attributeValue(page, false, String.class,
+                        n.getRoot().isXmlSyntax());
             }
 
             // If any of the params have their values specified by
@@ -1143,7 +1146,8 @@ class Generator {
                         + "_jspx_page_context.findAttribute(\""
                         + name
                         + "\"), \"" + property + "\",");
-                out.print(attributeValue(value, false, null));
+                out.print(attributeValue(value, false, null,
+                        n.getRoot().isXmlSyntax()));
                 out.println(");");
             } else if (value.isELInterpreterInput()) {
                 // We've got to resolve the very call to the interpreter
@@ -1188,7 +1192,8 @@ class Generator {
                         + "_jspx_page_context.findAttribute(\""
                         + name
                         + "\"), \"" + property + "\", ");
-                out.print(attributeValue(value, false, null));
+                out.print(attributeValue(value, false, null,
+                        n.getRoot().isXmlSyntax()));
                 out.println(", null, null, false);");
             }
 
@@ -1320,7 +1325,7 @@ class Generator {
                                     .getNamedAttributeNode());
                         } else {
                             binaryName = attributeValue(beanName, false,
-                                    String.class);
+                                    String.class, n.getRoot().isXmlSyntax());
                         }
                     } else {
                         // Implies klass is not null
@@ -1429,20 +1434,24 @@ class Generator {
                         // We want something of the form
                         // out.println( "<param name=\"blah\"
                         // value=\"" + ... + "\">" );
-                        out.printil("out.write( \"<param name=\\\""
-                                + escape(name)
-                                + "\\\" value=\\\"\" + "
-                                + attributeValue(n.getValue(), false,
-                                        String.class) + " + \"\\\">\" );");
+                        out.printil("out.write( \"<param name=\\\"" +
+                                escape(name) +
+                                "\\\" value=\\\"\" + " +
+                                attributeValue(n.getValue(), false,
+                                        String.class,
+                                        n.getRoot().isXmlSyntax()) +
+                                " + \"\\\">\" );");
                         out.printil("out.write(\"\\n\");");
                     } else {
                         // We want something of the form
                         // out.print( " blah=\"" + ... + "\"" );
-                        out.printil("out.write( \" "
-                                + escape(name)
-                                + "=\\\"\" + "
-                                + attributeValue(n.getValue(), false,
-                                        String.class) + " + \"\\\"\" );");
+                        out.printil("out.write( \" " +
+                                escape(name) +
+                                "=\\\"\" + " +
+                                attributeValue(n.getValue(), false,
+                                        String.class,
+                                        n.getRoot().isXmlSyntax()) +
+                                " + \"\\\"\" );");
                     }
 
                     n.setEndJavaLine(out.getJavaLine());
@@ -1469,7 +1478,8 @@ class Generator {
                     widthStr = generateNamedAttributeValue(width
                             .getNamedAttributeNode());
                 } else {
-                    widthStr = attributeValue(width, false, String.class);
+                    widthStr = attributeValue(width, false, String.class,
+                            n.getRoot().isXmlSyntax());
                 }
             }
 
@@ -1479,7 +1489,8 @@ class Generator {
                     heightStr = generateNamedAttributeValue(height
                             .getNamedAttributeNode());
                 } else {
-                    heightStr = attributeValue(height, false, String.class);
+                    heightStr = attributeValue(height, false, String.class,
+                            n.getRoot().isXmlSyntax());
                 }
             }
 
@@ -1834,8 +1845,9 @@ class Generator {
                     out.print("=");
                     if (jspAttrs[i].isELInterpreterInput()) {
                         out.print("\\\"\" + ");
-                        out.print(attributeValue(jspAttrs[i], false,
-                                String.class));
+                        String debug = attributeValue(jspAttrs[i], false,
+                                String.class, n.getRoot().isXmlSyntax());
+                        out.print(debug);
                         out.print(" + \"\\\"");
                     } else {
                         out.print(DOUBLE_QUOTE);
@@ -1883,7 +1895,8 @@ class Generator {
                     if (omitAttr == null) {
                         omit = "false";
                     } else {
-                        omit = attributeValue(omitAttr, false, boolean.class);
+                        omit = attributeValue(omitAttr, false, boolean.class,
+                                n.getRoot().isXmlSyntax());
                         if ("true".equals(omit)) {
                             continue;
                         }
@@ -1899,7 +1912,8 @@ class Generator {
                                 " + \"\\\"\")";
                     }
                 } else {
-                    value = attributeValue(attrs[i], false, Object.class);
+                    value = attributeValue(attrs[i], false, Object.class,
+                            n.getRoot().isXmlSyntax());
                     nvp = " + \" " + attrs[i].getName() + "=\\\"\" + " +
                             value + " + \"\\\"\"";
                 }
@@ -1909,7 +1923,7 @@ class Generator {
             // Write begin tag, using XML-style 'name' attribute as the
             // element name
             String elemName = attributeValue(n.getNameAttribute(), false,
-                    String.class);
+                    String.class, n.getRoot().isXmlSyntax());
             out.printin("out.write(\"<\"");
             out.print(" + " + elemName);
 
diff --git a/java/org/apache/jasper/compiler/JspDocumentParser.java b/java/org/apache/jasper/compiler/JspDocumentParser.java
index 60b28d4..753d7f4 100644
--- a/java/org/apache/jasper/compiler/JspDocumentParser.java
+++ b/java/org/apache/jasper/compiler/JspDocumentParser.java
@@ -5,9 +5,9 @@
  * The ASF licenses this file to You 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.
@@ -71,7 +71,7 @@ class JspDocumentParser
      * Outermost (in the nesting hierarchy) node whose body is declared to be
      * scriptless. If a node's body is declared to be scriptless, all its
      * nested nodes must be scriptless, too.
-     */ 
+     */
     private Node scriptlessBodyNode;
 
     private Locator locator;
@@ -242,7 +242,7 @@ class JspDocumentParser
      * Receives notification of the start of an element.
      *
      * This method assigns the given tag attributes to one of 3 buckets:
-     * 
+     *
      * - "xmlns" attributes that represent (standard or custom) tag libraries.
      * - "xmlns" attributes that do not represent tag libraries.
      * - all remaining attributes.
@@ -272,11 +272,8 @@ class JspDocumentParser
             return;
         }
 
-        String currentPrefix = getPrefix(current.getQName());
-        
         // jsp:text must not have any subelements
-        if (JSP_URI.equals(uri) && TEXT_ACTION.equals(current.getLocalName())
-                && "jsp".equals(currentPrefix)) {
+        if (current instanceof Node.JspText) {
             throw new SAXParseException(
                 Localizer.getMessage("jsp.error.text.has_subelement"),
                 locator);
@@ -288,7 +285,7 @@ class JspDocumentParser
         if (attrs != null) {
             /*
              * Notice that due to a bug in the underlying SAX parser, the
-             * attributes must be enumerated in descending order. 
+             * attributes must be enumerated in descending order.
              */
             boolean isTaglib = false;
             for (int i = attrs.getLength() - 1; i >= 0; i--) {
@@ -437,7 +434,7 @@ class JspDocumentParser
      * invoke this method with chunks of it.  This is a problem when we try
      * to determine if the text contains only whitespaces, or when we are
      * looking for an EL expression string.  Therefore it is necessary to
-     * buffer and concatenate the chunks and process the concatenated text 
+     * buffer and concatenate the chunks and process the concatenated text
      * later (at beginTag and endTag)
      *
      * @param buf The characters
@@ -670,7 +667,7 @@ class JspDocumentParser
                         if (!(child instanceof Node.NamedAttribute)) {
                             throw new SAXParseException(Localizer.getMessage(
                                     "jasper.error.emptybodycontent.nonempty",
-                                    current.qName), locator); 
+                                    current.qName), locator);
                         }
                     }
                 }
@@ -785,7 +782,7 @@ class JspDocumentParser
     }
 
     /*
-     * Receives notification of the start of a Namespace mapping. 
+     * Receives notification of the start of a Namespace mapping.
      */
     @Override
     public void startPrefixMapping(String prefix, String uri)
@@ -795,7 +792,7 @@ class JspDocumentParser
         if (directivesOnly && !(JSP_URI.equals(uri))) {
             return;
         }
-        
+
         try {
             taglibInfo = getTaglibInfo(prefix, uri);
         } catch (JasperException je) {
@@ -816,7 +813,7 @@ class JspDocumentParser
     }
 
     /*
-     * Receives notification of the end of a Namespace mapping. 
+     * Receives notification of the end of a Namespace mapping.
      */
     @Override
     public void endPrefixMapping(String prefix) throws SAXException {
@@ -1435,7 +1432,7 @@ class JspDocumentParser
         //factory.setFeature(
         //    "http://xml.org/sax/features/validation",
         //    validating);
-        
+
         // Configure the parser
         SAXParser saxParser = factory.newSAXParser();
         XMLReader xmlReader = saxParser.getXMLReader();
diff --git a/java/org/apache/jasper/compiler/TagFileProcessor.java b/java/org/apache/jasper/compiler/TagFileProcessor.java
index e575977..2c013cb 100644
--- a/java/org/apache/jasper/compiler/TagFileProcessor.java
+++ b/java/org/apache/jasper/compiler/TagFileProcessor.java
@@ -533,9 +533,9 @@ class TagFileProcessor {
 
         JspCompilationContext ctxt = compiler.getCompilationContext();
         JspRuntimeContext rctxt = ctxt.getRuntimeContext();
-        JspServletWrapper wrapper = rctxt.getWrapper(wrapperUri);
 
         synchronized (rctxt) {
+            JspServletWrapper wrapper = rctxt.getWrapper(wrapperUri);
             if (wrapper == null) {
                 wrapper = new JspServletWrapper(ctxt.getServletContext(), ctxt
                         .getOptions(), tagFilePath, tagInfo, ctxt
diff --git a/java/org/apache/jasper/compiler/TagPluginManager.java b/java/org/apache/jasper/compiler/TagPluginManager.java
index c41e5ac..d641f5a 100644
--- a/java/org/apache/jasper/compiler/TagPluginManager.java
+++ b/java/org/apache/jasper/compiler/TagPluginManager.java
@@ -5,9 +5,9 @@
  * The ASF licenses this file to You 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.
@@ -45,7 +45,6 @@ public class TagPluginManager {
     private boolean initialized = false;
     private HashMap<String, TagPlugin> tagPlugins = null;
     private ServletContext ctxt;
-    private PageInfo pageInfo;
 
     public TagPluginManager(ServletContext ctxt) {
         this.ctxt = ctxt;
@@ -59,19 +58,9 @@ public class TagPluginManager {
             return;
         }
 
-        this.pageInfo = pageInfo;
-
-        page.visit(new Node.Visitor() {
-            @Override
-            public void visit(Node.CustomTag n)
-                    throws JasperException {
-                invokePlugin(n);
-                visitBody(n);
-            }
-        });
-
+        page.visit(new NodeVisitor(this, pageInfo));
     }
- 
+
     private void init(ErrorDispatcher err) throws JasperException {
         if (initialized)
             return;
@@ -175,12 +164,12 @@ public class TagPluginManager {
     }
 
     /**
-     * Invoke tag plugin for the given custom tag, if a plugin exists for 
+     * Invoke tag plugin for the given custom tag, if a plugin exists for
      * the custom tag's tag handler.
      *
      * The given custom tag node will be manipulated by the plugin.
      */
-    private void invokePlugin(Node.CustomTag n) {
+    private void invokePlugin(Node.CustomTag n, PageInfo pageInfo) {
         TagPlugin tagPlugin = tagPlugins.get(n.getTagHandlerClass().getName());
         if (tagPlugin == null) {
             return;
@@ -191,8 +180,24 @@ public class TagPluginManager {
         tagPlugin.doTag(tagPluginContext);
     }
 
-    static class TagPluginContextImpl implements TagPluginContext {
-        private Node.CustomTag node;
+    private static class NodeVisitor extends Node.Visitor {
+        private TagPluginManager manager;
+        private PageInfo pageInfo;
+
+        public NodeVisitor(TagPluginManager manager, PageInfo pageInfo) {
+            this.manager = manager;
+            this.pageInfo = pageInfo;
+        }
+
+        @Override
+        public void visit(Node.CustomTag n) throws JasperException {
+            manager.invokePlugin(n, pageInfo);
+            visitBody(n);
+        }
+    }
+
+    private static class TagPluginContextImpl implements TagPluginContext {
+        private final Node.CustomTag node;
         private Node.Nodes curNodes;
         private PageInfo pageInfo;
         private HashMap<String, Object> pluginAttributes;
@@ -291,7 +296,7 @@ public class TagPluginManager {
 
         @Override
         public void generateBody() {
-            // Since we'll generate the body anyway, this is really a nop, 
+            // Since we'll generate the body anyway, this is really a nop,
             // except for the fact that it lets us put the Java sources the
             // plugins produce in the correct order (w.r.t the body).
             curNodes = node.getAtETag();
diff --git a/java/org/apache/juli/ClassLoaderLogManager.java b/java/org/apache/juli/ClassLoaderLogManager.java
index e4ead3b..a8c1319 100644
--- a/java/org/apache/juli/ClassLoaderLogManager.java
+++ b/java/org/apache/juli/ClassLoaderLogManager.java
@@ -22,6 +22,7 @@ import java.io.FileInputStream;
 import java.io.FilePermission;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URL;
 import java.net.URLClassLoader;
 import java.security.AccessControlException;
 import java.security.AccessController;
@@ -43,8 +44,14 @@ import java.util.logging.Logger;
 
 /**
  * Per classloader LogManager implementation.
+ *
+ * For light debugging, set the system property
+ * <code>org.apache.juli.ClassLoaderLogManager.debug=true</code>.
+ * Short configuration information will be sent to <code>System.err</code>.
  */
 public class ClassLoaderLogManager extends LogManager {
+    public static final String DEBUG_PROPERTY =
+            ClassLoaderLogManager.class.getName() + ".debug";
 
     private final class Cleaner extends Thread {
         
@@ -415,9 +422,23 @@ public class ClassLoaderLogManager extends LogManager {
         // Special case for URL classloaders which are used in containers: 
         // only look in the local repositories to avoid redefining loggers 20 times
         try {
-            if ((classLoader instanceof URLClassLoader) 
-                    && (((URLClassLoader) classLoader).findResource("logging.properties") != null)) {
-                is = classLoader.getResourceAsStream("logging.properties");
+            if (classLoader instanceof URLClassLoader) {
+                URL logConfig = ((URLClassLoader)classLoader).findResource("logging.properties");
+
+                if(null != logConfig) {
+                    if(Boolean.getBoolean(DEBUG_PROPERTY))
+                        System.err.println(getClass().getName()
+                                           + ".readConfiguration(): "
+                                           + "Found logging.properties at "
+                                           + logConfig);
+
+                    is = classLoader.getResourceAsStream("logging.properties");
+                } else {
+                    if(Boolean.getBoolean(DEBUG_PROPERTY))
+                        System.err.println(getClass().getName()
+                                           + ".readConfiguration(): "
+                                           + "Found no logging.properties");
+                }
             }
         } catch (AccessControlException ace) {
             // No permission to configure logging in context
diff --git a/java/org/apache/juli/JdkLoggerFormatter.java b/java/org/apache/juli/JdkLoggerFormatter.java
index 75af095..d4bb729 100644
--- a/java/org/apache/juli/JdkLoggerFormatter.java
+++ b/java/org/apache/juli/JdkLoggerFormatter.java
@@ -41,6 +41,9 @@ import java.util.logging.LogRecord;
  * @author Costin Manolache
  */
 public class JdkLoggerFormatter extends Formatter {
+
+    private static final String LINE_SEP = System.getProperty("line.separator");
+
     // values from JDK Level
     public static final int LOG_LEVEL_TRACE  = 400;
     public static final int LOG_LEVEL_DEBUG  = 500;
@@ -65,7 +68,6 @@ public class JdkLoggerFormatter extends Formatter {
         StringBuilder buf = new StringBuilder();
         
         buf.append(time);
-        buf.append(" ");
         
         // pad to 8 to make it more readable 
         for( int i=0; i<8-buf.length(); i++ ) { buf.append(" "); }
@@ -84,7 +86,8 @@ public class JdkLoggerFormatter extends Formatter {
 
         // Append the name of the log instance if so configured
         buf.append(name);
-        
+        buf.append(" ");
+
         // pad to 20 chars 
         for( int i=0; i<8-buf.length(); i++ ) { buf.append(" "); }
                 
@@ -93,7 +96,7 @@ public class JdkLoggerFormatter extends Formatter {
         
         // Append stack trace if not null
         if(t != null) {
-            buf.append(" \n");
+            buf.append(LINE_SEP);
             
             java.io.StringWriter sw= new java.io.StringWriter(1024);
             java.io.PrintWriter pw= new java.io.PrintWriter(sw);
@@ -102,7 +105,7 @@ public class JdkLoggerFormatter extends Formatter {
             buf.append(sw.toString());
         }
         
-        buf.append("\n");
+        buf.append(LINE_SEP);
         // Print to the appropriate destination
         return buf.toString();
     }
diff --git a/java/org/apache/naming/resources/DirContextURLConnection.java b/java/org/apache/naming/resources/DirContextURLConnection.java
index 87a6aec..3c84eee 100644
--- a/java/org/apache/naming/resources/DirContextURLConnection.java
+++ b/java/org/apache/naming/resources/DirContextURLConnection.java
@@ -5,15 +5,15 @@
  * The ASF licenses this file to You 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.apache.naming.resources;
 
@@ -47,13 +47,13 @@ import org.apache.tomcat.util.http.FastHttpDateFormat;
 /**
  * Connection to a JNDI directory context.
  * <p/>
- * Note: All the object attribute names are the WebDAV names, not the HTTP 
+ * Note: All the object attribute names are the WebDAV names, not the HTTP
  * names, so this class overrides some methods from URLConnection to do the
- * queries using the right names. Content handler is also not used; the 
+ * queries using the right names. Content handler is also not used; the
  * content is directly returned.
- * 
+ *
  * @author <a href="mailto:remm at apache.org">Remy Maucherat</a>
- * @version $Revision: 1452791 $
+ * @version $Revision: 1511932 $
  */
 public class DirContextURLConnection extends URLConnection {
 
@@ -65,7 +65,7 @@ public class DirContextURLConnection extends URLConnection {
     }
 
     // ----------------------------------------------------------- Constructors
-    
+
     public DirContextURLConnection(DirContext context, URL url) {
         super(url);
         if (context == null)
@@ -76,53 +76,60 @@ public class DirContextURLConnection extends URLConnection {
     }
         this.context = context;
     }
-    
-    
+
+
     // ----------------------------------------------------- Instance Variables
-    
-    
+
+
     /**
      * Directory context.
      */
     protected DirContext context;
-    
-    
+
+
     /**
      * Associated resource.
      */
     protected Resource resource;
-    
-    
+
+
     /**
      * Associated DirContext.
      */
     protected DirContext collection;
-    
-    
+
+
     /**
      * Other unknown object.
      */
     protected Object object;
-    
-    
+
+
     /**
      * Attributes.
      */
     protected Attributes attributes;
-    
-    
+
+
     /**
      * Date.
      */
     protected long date;
-    
-    
+
+
     /**
      * Permission
      */
     protected Permission permission;
 
 
+    /**
+     * Cache the path as it there is some processing required - particularly if
+     * the context is a ProxyDirContext.
+     */
+    private String path = null;
+
+
     // ------------------------------------------------------------- Properties
 
 
@@ -130,20 +137,19 @@ public class DirContextURLConnection extends URLConnection {
      * Connect to the DirContext, and retrieve the bound object, as well as
      * its attributes. If no object is bound with the name specified in the
      * URL, then an IOException is thrown.
-     * 
+     *
      * @throws IOException Object not found
      */
     @Override
-    public void connect()
-        throws IOException {
-        
+    public void connect() throws IOException {
+
         if (!connected) {
-            
+
             try {
                 date = System.currentTimeMillis();
-                String path = URL_DECODER.convert(getURL().getFile(), false);
+                path = URL_DECODER.convert(getURL().getFile(), false);
                 if (context instanceof ProxyDirContext) {
-                    ProxyDirContext proxyDirContext = 
+                    ProxyDirContext proxyDirContext =
                         (ProxyDirContext) context;
                     String hostName = proxyDirContext.getHostName();
                     String contextPath = proxyDirContext.getContextPath();
@@ -168,14 +174,12 @@ public class DirContextURLConnection extends URLConnection {
             } catch (NamingException e) {
                 // Object not found
             }
-            
+
             connected = true;
-            
         }
-        
     }
-    
-    
+
+
     /**
      * Return the content length value.
      */
@@ -183,8 +187,8 @@ public class DirContextURLConnection extends URLConnection {
     public int getContentLength() {
         return getHeaderFieldInt(ResourceAttributes.CONTENT_LENGTH, -1);
     }
-    
-    
+
+
     /**
      * Return the content type value.
      */
@@ -192,8 +196,8 @@ public class DirContextURLConnection extends URLConnection {
     public String getContentType() {
         return getHeaderField(ResourceAttributes.CONTENT_TYPE);
     }
-    
-    
+
+
     /**
      * Return the last modified date.
      */
@@ -201,8 +205,8 @@ public class DirContextURLConnection extends URLConnection {
     public long getDate() {
         return date;
     }
-    
-    
+
+
     /**
      * Return the last modified date.
      */
@@ -221,7 +225,7 @@ public class DirContextURLConnection extends URLConnection {
         if (attributes == null)
             return 0;
 
-        Attribute lastModified = 
+        Attribute lastModified =
             attributes.get(ResourceAttributes.LAST_MODIFIED);
         if (lastModified != null) {
             try {
@@ -234,7 +238,7 @@ public class DirContextURLConnection extends URLConnection {
 
         return 0;
     }
-    
+
 
     protected String getHeaderValueAsString(Object headerValue) {
         if (headerValue == null) return null;
@@ -291,8 +295,8 @@ public class DirContextURLConnection extends URLConnection {
       return Collections.unmodifiableMap(headerFields);
 
     }
-    
-    
+
+
     /**
      * Returns the name of the specified header field.
      */
@@ -307,7 +311,7 @@ public class DirContextURLConnection extends URLConnection {
                 // Ignore
             }
         }
-        
+
         if (attributes == null)
             return (null);
 
@@ -327,63 +331,61 @@ public class DirContextURLConnection extends URLConnection {
         }
 
         return (null);
-        
+
     }
-    
-    
+
+
     /**
      * Get object content.
      */
     @Override
     public Object getContent()
         throws IOException {
-        
+
         if (!connected)
             connect();
-        
+
         if (resource != null)
             return getInputStream();
         if (collection != null)
             return collection;
         if (object != null)
             return object;
-        
+
         throw new FileNotFoundException(
                 getURL() == null ? "null" : getURL().toString());
-        
+
     }
-    
-    
+
+
     /**
      * Get object content.
      */
-    @SuppressWarnings("rawtypes") // overridden method uses raw type Class[]
     @Override
-    public Object getContent(Class[] classes)
+    public Object getContent(@SuppressWarnings("rawtypes") Class[] classes)
         throws IOException {
-        
+
         Object obj = getContent();
-        
+
         for (int i = 0; i < classes.length; i++) {
             if (classes[i].isInstance(obj))
                 return obj;
         }
-        
+
         return null;
-        
+
     }
-    
-    
+
+
     /**
      * Get input stream.
      */
     @Override
-    public InputStream getInputStream() 
-        throws IOException {
-        
+    public InputStream getInputStream() throws IOException {
+
         if (!connected)
             connect();
-        
+
         if (resource == null) {
             throw new FileNotFoundException(
                     getURL() == null ? "null" : getURL().toString());
@@ -391,17 +393,15 @@ public class DirContextURLConnection extends URLConnection {
 
         // Reopen resource
         try {
-            resource = (Resource) context.lookup(
-                    URL_DECODER.convert(getURL().getFile(), false));
+            resource = (Resource) context.lookup(path);
         } catch (NamingException e) {
             // Ignore
         }
-        
+
         return (resource.streamContent());
-        
     }
-    
-    
+
+
     /**
      * Get the Permission for this URL
      */
@@ -413,26 +413,26 @@ public class DirContextURLConnection extends URLConnection {
 
 
     // --------------------------------------------------------- Public Methods
-    
-    
+
+
     /**
      * List children of this collection. The names given are relative to this
      * URI's path. The full uri of the children is then : path + "/" + name.
      */
     public Enumeration<String> list()
         throws IOException {
-        
+
         if (!connected) {
             connect();
         }
-        
+
         if ((resource == null) && (collection == null)) {
             throw new FileNotFoundException(
                     getURL() == null ? "null" : getURL().toString());
         }
-        
+
         Vector<String> result = new Vector<String>();
-        
+
         if (collection != null) {
             try {
                 NamingEnumeration<NameClassPair> enumeration =
@@ -449,10 +449,10 @@ public class DirContextURLConnection extends URLConnection {
                         getURL() == null ? "null" : getURL().toString());
             }
         }
-        
+
         return result.elements();
-        
+
     }
-    
-    
+
+
 }
diff --git a/java/org/apache/naming/resources/WARDirContext.java b/java/org/apache/naming/resources/WARDirContext.java
index 8733a3f..14521da 100644
--- a/java/org/apache/naming/resources/WARDirContext.java
+++ b/java/org/apache/naming/resources/WARDirContext.java
@@ -49,7 +49,7 @@ import org.apache.naming.NamingEntry;
  * WAR Directory Context implementation.
  *
  * @author Remy Maucherat
- * @version $Id: WARDirContext.java 1392099 2012-09-30 19:48:36Z markt $
+ * @version $Id: WARDirContext.java 1521057 2013-09-09 11:39:49Z markt $
  */
 
 public class WARDirContext extends BaseDirContext {
@@ -228,22 +228,14 @@ public class WARDirContext extends BaseDirContext {
     }
 
     /**
-     * Unbinds the named object. Removes the terminal atomic name in name 
-     * from the target context--that named by all but the terminal atomic 
-     * part of name.
-     * <p>
-     * This method is idempotent. It succeeds even if the terminal atomic 
-     * name is not bound in the target context, but throws 
-     * NameNotFoundException if any of the intermediate contexts do not exist. 
+     * {@inheritDoc}
      * 
-     * @param name the name to bind; may not be empty
-     * @exception NameNotFoundException if an intermediate context does not 
-     * exist
-     * @exception NamingException if a naming exception is encountered
+     * This method is not supported in this implementation
+     * 
+     * @throws OperationNotSupportedException for this implementation
      */
     @Override
-    public void unbind(String name)
-        throws NamingException {
+    public void unbind(String name) throws NamingException {
         throw new OperationNotSupportedException();
     }
 
@@ -298,33 +290,14 @@ public class WARDirContext extends BaseDirContext {
 
 
     /**
-     * Destroys the named context and removes it from the namespace. Any 
-     * attributes associated with the name are also removed. Intermediate 
-     * contexts are not destroyed.
-     * <p>
-     * This method is idempotent. It succeeds even if the terminal atomic 
-     * name is not bound in the target context, but throws 
-     * NameNotFoundException if any of the intermediate contexts do not exist. 
+     * {@inheritDoc}
      * 
-     * In a federated naming system, a context from one naming system may be 
-     * bound to a name in another. One can subsequently look up and perform 
-     * operations on the foreign context using a composite name. However, an 
-     * attempt destroy the context using this composite name will fail with 
-     * NotContextException, because the foreign context is not a "subcontext" 
-     * of the context in which it is bound. Instead, use unbind() to remove 
-     * the binding of the foreign context. Destroying the foreign context 
-     * requires that the destroySubcontext() be performed on a context from 
-     * the foreign context's "native" naming system.
+     * This method is not supported in this implementation
      * 
-     * @param name the name of the context to be destroyed; may not be empty
-     * @exception NameNotFoundException if an intermediate context does not 
-     * exist
-     * @exception javax.naming.NotContextException if the name is bound but does
-     * not name a context, or does not name a context of the appropriate type
+     * @throws OperationNotSupportedException for this implementation
      */
     @Override
-    public void destroySubcontext(String name)
-        throws NamingException {
+    public void destroySubcontext(String name) throws NamingException {
         throw new OperationNotSupportedException();
     }
 
diff --git a/java/org/apache/tomcat/InstanceManager.java b/java/org/apache/tomcat/InstanceManager.java
index d638eab..a80dab9 100644
--- a/java/org/apache/tomcat/InstanceManager.java
+++ b/java/org/apache/tomcat/InstanceManager.java
@@ -21,10 +21,14 @@ import java.lang.reflect.InvocationTargetException;
 import javax.naming.NamingException;
 
 /**
- * @version $Id: InstanceManager.java 1200164 2011-11-10 05:46:02Z kkolinko $
+ * @version $Id: InstanceManager.java 1514663 2013-08-16 11:51:28Z markt $
  */
 public interface InstanceManager {
 
+    public Object newInstance(Class<?> clazz)
+            throws IllegalAccessException, InvocationTargetException, NamingException,
+                InstantiationException;
+
     public Object newInstance(String className)
         throws IllegalAccessException, InvocationTargetException, NamingException,
             InstantiationException, ClassNotFoundException;
diff --git a/java/org/apache/tomcat/util/bcel/classfile/Constant.java b/java/org/apache/tomcat/util/bcel/classfile/Constant.java
index 494befb..34579e1 100644
--- a/java/org/apache/tomcat/util/bcel/classfile/Constant.java
+++ b/java/org/apache/tomcat/util/bcel/classfile/Constant.java
@@ -29,7 +29,7 @@ import org.apache.tomcat.util.bcel.util.BCELComparator;
  * in the constant pool of a class file. The classes keep closely to
  * the JVM specification.
  *
- * @version $Id: Constant.java 1377534 2012-08-26 22:26:10Z markt $
+ * @version $Id: Constant.java 1507021 2013-07-25 15:44:24Z markt $
  * @author  <A HREF="mailto:m.dahm at gmx.de">M. Dahm</A>
  */
 public abstract class Constant implements Cloneable, Serializable {
@@ -122,7 +122,7 @@ public abstract class Constant implements Cloneable, Serializable {
             case Constants.CONSTANT_NameAndType:
                 return new ConstantNameAndType(file);
             case Constants.CONSTANT_Utf8:
-                return new ConstantUtf8(file);
+                return ConstantUtf8.getInstance(file);
             case Constants.CONSTANT_MethodHandle:
                 return new ConstantMethodHandle(file);
             case Constants.CONSTANT_MethodType:
diff --git a/java/org/apache/tomcat/util/bcel/classfile/ConstantUtf8.java b/java/org/apache/tomcat/util/bcel/classfile/ConstantUtf8.java
index 518c21b..c016ccf 100644
--- a/java/org/apache/tomcat/util/bcel/classfile/ConstantUtf8.java
+++ b/java/org/apache/tomcat/util/bcel/classfile/ConstantUtf8.java
@@ -17,7 +17,11 @@
 package org.apache.tomcat.util.bcel.classfile;
 
 import java.io.DataInput;
+import java.io.DataInputStream;
 import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
 
 import org.apache.tomcat.util.bcel.Constants;
 
@@ -26,15 +30,49 @@ import org.apache.tomcat.util.bcel.Constants;
  * <A HREF="org.apache.tomcat.util.bcel.classfile.Constant.html">Constant</A> class 
  * and represents a reference to a Utf8 encoded string.
  *
- * @version $Id: ConstantUtf8.java 1377533 2012-08-26 22:22:59Z markt $
+ * @version $Id: ConstantUtf8.java 1507021 2013-07-25 15:44:24Z markt $
  * @author  <A HREF="mailto:m.dahm at gmx.de">M. Dahm</A>
  * @see     Constant
  */
 public final class ConstantUtf8 extends Constant {
 
     private static final long serialVersionUID = 8119001312020421976L;
-    private String bytes;
+    private final String bytes;
 
+    private static final int MAX_CACHE_ENTRIES = 20000;
+    private static final int INITIAL_CACHE_CAPACITY = (int)(MAX_CACHE_ENTRIES/0.75);
+    private static HashMap<String, ConstantUtf8> cache;
+
+    private static synchronized ConstantUtf8 getCachedInstance(String s) {
+        if (s.length() > 200) {
+            return  new ConstantUtf8(s);
+        }
+        if (cache == null)  {
+            cache = new LinkedHashMap<String, ConstantUtf8>(INITIAL_CACHE_CAPACITY, 0.75f, true) {
+                private static final long serialVersionUID = 1L;
+
+                @Override
+                protected boolean removeEldestEntry(Map.Entry<String, ConstantUtf8> eldest) {
+                     return size() > MAX_CACHE_ENTRIES;
+                }
+            };
+        }
+        ConstantUtf8 result = cache.get(s);
+        if (result != null) {
+                return result;
+            }
+        result = new ConstantUtf8(s);
+        cache.put(s, result);
+        return result;
+    }
+
+    private static ConstantUtf8 getInstance(String s) {
+        return getCachedInstance(s);
+    }
+
+    static ConstantUtf8 getInstance(DataInputStream file) throws IOException {
+        return getInstance(file.readUTF());
+    }
 
     /**
      * Initialize instance from file data.
@@ -49,6 +87,18 @@ public final class ConstantUtf8 extends Constant {
 
 
     /**
+     * @param bytes Data
+     */
+    private ConstantUtf8(String bytes) {
+        super(Constants.CONSTANT_Utf8);
+        if (bytes == null) {
+            throw new IllegalArgumentException("bytes must not be null!");
+        }
+        this.bytes = bytes;
+    }
+
+
+    /**
      * @return Data converted to string.
      */
     public final String getBytes() {
diff --git a/java/org/apache/tomcat/util/bcel/package.html b/java/org/apache/tomcat/util/bcel/package.html
index 242ef14..7cbd65d 100644
--- a/java/org/apache/tomcat/util/bcel/package.html
+++ b/java/org/apache/tomcat/util/bcel/package.html
@@ -18,13 +18,13 @@
 <html>
 <head>
 <!--
-$Id: package.html 919341 2010-03-05 09:08:45Z markt $
+$Id: package.html 1507021 2013-07-25 15:44:24Z markt $
 -->
 </head>
 <body bgcolor="white">
 <p>
 This package contains basic classes for the
-<a href="http://jakarta.apache.org/bcel/">Byte Code Engineering Library</a>
+<a href="http://commons.apache.org/bcel">Byte Code Engineering Library</a>
  and constants defined by the
 <a href="http://java.sun.com/docs/books/vmspec/html/VMSpecTOC.doc.html">
  JVM specification</a>.
diff --git a/java/org/apache/tomcat/util/buf/Utf8Encoder.java b/java/org/apache/tomcat/util/buf/Utf8Encoder.java
new file mode 100644
index 0000000..9c0fa79
--- /dev/null
+++ b/java/org/apache/tomcat/util/buf/Utf8Encoder.java
@@ -0,0 +1,234 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.util.buf;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+
+/**
+ * Encodes characters as bytes using UTF-8. Extracted from Apache Harmony with
+ * some minor bug fixes applied.
+ */
+public class Utf8Encoder extends CharsetEncoder {
+
+    public Utf8Encoder() {
+        super(B2CConverter.UTF_8, 1.1f, 4.0f);
+    }
+
+    @Override
+    protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
+        if (in.hasArray() && out.hasArray()) {
+            return encodeHasArray(in, out);
+        }
+        return encodeNotHasArray(in, out);
+    }
+
+    private CoderResult encodeHasArray(CharBuffer in, ByteBuffer out) {
+        int outRemaining = out.remaining();
+        int pos = in.position();
+        int limit = in.limit();
+        byte[] bArr;
+        char[] cArr;
+        int x = pos;
+        bArr = out.array();
+        cArr = in.array();
+        int outPos = out.position();
+        int rem = in.remaining();
+        for (x = pos; x < pos + rem; x++) {
+            int jchar = (cArr[x] & 0xFFFF);
+
+            if (jchar <= 0x7F) {
+                if (outRemaining < 1) {
+                    in.position(x);
+                    out.position(outPos);
+                    return CoderResult.OVERFLOW;
+                }
+                bArr[outPos++] = (byte) (jchar & 0xFF);
+                outRemaining--;
+            } else if (jchar <= 0x7FF) {
+
+                if (outRemaining < 2) {
+                    in.position(x);
+                    out.position(outPos);
+                    return CoderResult.OVERFLOW;
+                }
+                bArr[outPos++] = (byte) (0xC0 + ((jchar >> 6) & 0x1F));
+                bArr[outPos++] = (byte) (0x80 + (jchar & 0x3F));
+                outRemaining -= 2;
+
+            } else if (jchar >= 0xD800 && jchar <= 0xDFFF) {
+
+                // in has to have one byte more.
+                if (limit <= x + 1) {
+                    in.position(x);
+                    out.position(outPos);
+                    return CoderResult.UNDERFLOW;
+                }
+
+                if (outRemaining < 4) {
+                    in.position(x);
+                    out.position(outPos);
+                    return CoderResult.OVERFLOW;
+                }
+
+                // The surrogate pair starts with a low-surrogate.
+                if (jchar >= 0xDC00) {
+                    in.position(x);
+                    out.position(outPos);
+                    return CoderResult.malformedForLength(1);
+                }
+
+                int jchar2 = cArr[x + 1] & 0xFFFF;
+
+                // The surrogate pair ends with a high-surrogate.
+                if (jchar2 < 0xDC00) {
+                    in.position(x);
+                    out.position(outPos);
+                    return CoderResult.malformedForLength(1);
+                }
+
+                // Note, the Unicode scalar value n is defined
+                // as follows:
+                // n = (jchar-0xD800)*0x400+(jchar2-0xDC00)+0x10000
+                // Where jchar is a high-surrogate,
+                // jchar2 is a low-surrogate.
+                int n = (jchar << 10) + jchar2 + 0xFCA02400;
+
+                bArr[outPos++] = (byte) (0xF0 + ((n >> 18) & 0x07));
+                bArr[outPos++] = (byte) (0x80 + ((n >> 12) & 0x3F));
+                bArr[outPos++] = (byte) (0x80 + ((n >> 6) & 0x3F));
+                bArr[outPos++] = (byte) (0x80 + (n & 0x3F));
+                outRemaining -= 4;
+                x++;
+
+            } else {
+
+                if (outRemaining < 3) {
+                    in.position(x);
+                    out.position(outPos);
+                    return CoderResult.OVERFLOW;
+                }
+                bArr[outPos++] = (byte) (0xE0 + ((jchar >> 12) & 0x0F));
+                bArr[outPos++] = (byte) (0x80 + ((jchar >> 6) & 0x3F));
+                bArr[outPos++] = (byte) (0x80 + (jchar & 0x3F));
+                outRemaining -= 3;
+            }
+            if (outRemaining == 0) {
+                in.position(x + 1);
+                out.position(outPos);
+                // If both input and output are exhausted, return UNDERFLOW
+                if (x + 1 == limit) {
+                    return CoderResult.UNDERFLOW;
+                } else {
+                    return CoderResult.OVERFLOW;
+                }
+            }
+
+        }
+        if (rem != 0) {
+            in.position(x);
+            out.position(outPos);
+        }
+        return CoderResult.UNDERFLOW;
+    }
+
+    private CoderResult encodeNotHasArray(CharBuffer in, ByteBuffer out) {
+        int outRemaining = out.remaining();
+        int pos = in.position();
+        int limit = in.limit();
+        try {
+            while (pos < limit) {
+                if (outRemaining == 0) {
+                    return CoderResult.OVERFLOW;
+                }
+
+                int jchar = (in.get() & 0xFFFF);
+
+                if (jchar <= 0x7F) {
+
+                    if (outRemaining < 1) {
+                        return CoderResult.OVERFLOW;
+                    }
+                    out.put((byte) jchar);
+                    outRemaining--;
+
+                } else if (jchar <= 0x7FF) {
+
+                    if (outRemaining < 2) {
+                        return CoderResult.OVERFLOW;
+                    }
+                    out.put((byte) (0xC0 + ((jchar >> 6) & 0x1F)));
+                    out.put((byte) (0x80 + (jchar & 0x3F)));
+                    outRemaining -= 2;
+
+                } else if (jchar >= 0xD800 && jchar <= 0xDFFF) {
+
+                    // in has to have one byte more.
+                    if (limit <= pos + 1) {
+                        return CoderResult.UNDERFLOW;
+                    }
+
+                    if (outRemaining < 4) {
+                        return CoderResult.OVERFLOW;
+                    }
+
+                    // The surrogate pair starts with a low-surrogate.
+                    if (jchar >= 0xDC00) {
+                        return CoderResult.malformedForLength(1);
+                    }
+
+                    int jchar2 = (in.get() & 0xFFFF);
+
+                    // The surrogate pair ends with a high-surrogate.
+                    if (jchar2 < 0xDC00) {
+                        return CoderResult.malformedForLength(1);
+                    }
+
+                    // Note, the Unicode scalar value n is defined
+                    // as follows:
+                    // n = (jchar-0xD800)*0x400+(jchar2-0xDC00)+0x10000
+                    // Where jchar is a high-surrogate,
+                    // jchar2 is a low-surrogate.
+                    int n = (jchar << 10) + jchar2 + 0xFCA02400;
+
+                    out.put((byte) (0xF0 + ((n >> 18) & 0x07)));
+                    out.put((byte) (0x80 + ((n >> 12) & 0x3F)));
+                    out.put((byte) (0x80 + ((n >> 6) & 0x3F)));
+                    out.put((byte) (0x80 + (n & 0x3F)));
+                    outRemaining -= 4;
+                    pos++;
+
+                } else {
+
+                    if (outRemaining < 3) {
+                        return CoderResult.OVERFLOW;
+                    }
+                    out.put((byte) (0xE0 + ((jchar >> 12) & 0x0F)));
+                    out.put((byte) (0x80 + ((jchar >> 6) & 0x3F)));
+                    out.put((byte) (0x80 + (jchar & 0x3F)));
+                    outRemaining -= 3;
+                }
+                pos++;
+            }
+        } finally {
+            in.position(pos);
+        }
+        return CoderResult.UNDERFLOW;
+    }
+}
\ No newline at end of file
diff --git a/java/org/apache/tomcat/util/http/HttpMessages.java b/java/org/apache/tomcat/util/http/HttpMessages.java
index 105af7d..b5a8f98 100644
--- a/java/org/apache/tomcat/util/http/HttpMessages.java
+++ b/java/org/apache/tomcat/util/http/HttpMessages.java
@@ -16,6 +16,10 @@
  */
 package org.apache.tomcat.util.http;
 
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
 import org.apache.tomcat.util.res.StringManager;
 
 /**
@@ -28,14 +32,27 @@ import org.apache.tomcat.util.res.StringManager;
  * @author costin at eng.sun.com
  */
 public class HttpMessages {
+
+    private static final Map<Locale,HttpMessages> instances =
+            new ConcurrentHashMap<Locale, HttpMessages>();
+
+    private static final HttpMessages DEFAULT = new HttpMessages(
+            StringManager.getManager("org.apache.tomcat.util.http.res",
+                    Locale.getDefault()));
+
+
     // XXX move message resources in this package
-    protected static final StringManager sm =
-        StringManager.getManager("org.apache.tomcat.util.http.res");
+    private final StringManager sm;
+
+    private String st_200 = null;
+    private String st_302 = null;
+    private String st_400 = null;
+    private String st_404 = null;
+
+    private HttpMessages(StringManager sm) {
+        this.sm = sm;
+    }
 
-    static String st_200=null;
-    static String st_302=null;
-    static String st_400=null;
-    static String st_404=null;
 
     /** Get the status string associated with a status code.
      *  No I18N - return the messages defined in the HTTP spec.
@@ -45,36 +62,53 @@ public class HttpMessages {
      *  Common messages are cached.
      *
      */
-    public static String getMessage( int status ) {
+    public String getMessage(int status) {
         // method from Response.
 
         // Does HTTP requires/allow international messages or
         // are pre-defined? The user doesn't see them most of the time
         switch( status ) {
         case 200:
-            if( st_200==null ) {
-                st_200=sm.getString( "sc.200");
+            if(st_200 == null ) {
+                st_200 = sm.getString("sc.200");
             }
             return st_200;
         case 302:
-            if( st_302==null ) {
-                st_302=sm.getString( "sc.302");
+            if(st_302 == null ) {
+                st_302 = sm.getString("sc.302");
             }
             return st_302;
         case 400:
-            if( st_400==null ) {
-                st_400=sm.getString( "sc.400");
+            if(st_400 == null ) {
+                st_400 = sm.getString("sc.400");
             }
             return st_400;
         case 404:
-            if( st_404==null ) {
-                st_404=sm.getString( "sc.404");
+            if(st_404 == null ) {
+                st_404 = sm.getString("sc.404");
             }
             return st_404;
         }
         return sm.getString("sc."+ status);
     }
 
+
+    public static HttpMessages getInstance(Locale locale) {
+        HttpMessages result = instances.get(locale);
+        if (result == null) {
+            StringManager sm = StringManager.getManager(
+                    "org.apache.tomcat.util.http.res", locale);
+            if (Locale.getDefault().equals(sm.getLocale())) {
+                result = DEFAULT;
+            } else {
+                result = new HttpMessages(sm);
+            }
+            instances.put(locale, result);
+        }
+        return result;
+    }
+
+
     /**
      * Filter the specified message string for characters that are sensitive
      * in HTML.  This avoids potential attacks caused by including JavaScript
diff --git a/java/org/apache/tomcat/util/http/Parameters.java b/java/org/apache/tomcat/util/http/Parameters.java
index 51c4ffb..66b2870 100644
--- a/java/org/apache/tomcat/util/http/Parameters.java
+++ b/java/org/apache/tomcat/util/http/Parameters.java
@@ -22,7 +22,7 @@ import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Enumeration;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 import org.apache.tomcat.util.buf.B2CConverter;
@@ -49,8 +49,8 @@ public final class Parameters {
     protected static final StringManager sm =
         StringManager.getManager("org.apache.tomcat.util.http");
 
-    private final HashMap<String,ArrayList<String>> paramHashValues =
-        new HashMap<String,ArrayList<String>>();
+    private final Map<String,ArrayList<String>> paramHashValues =
+            new LinkedHashMap<String,ArrayList<String>>();
 
     private boolean didQueryParameters=false;
 
diff --git a/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java b/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
index f4d8ead..94cd57f 100644
--- a/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
+++ b/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
@@ -80,7 +80,7 @@ import org.apache.tomcat.util.http.fileupload.util.Streams;
  *   }
  * </pre>
  *
- * @version $Id: MultipartStream.java 1456935 2013-03-15 12:47:29Z markt $
+ * @version $Id: MultipartStream.java 1521063 2013-09-09 12:03:51Z markt $
  */
 public class MultipartStream {
 
@@ -281,7 +281,7 @@ public class MultipartStream {
      * @see #MultipartStream(InputStream, byte[],
      *     MultipartStream.ProgressNotifier)
      */
-    MultipartStream(InputStream input,
+    public MultipartStream(InputStream input,
             byte[] boundary,
             int bufSize,
             ProgressNotifier pNotifier) {
diff --git a/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java b/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java
index d9f670a..1a01fb4 100644
--- a/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java
+++ b/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java
@@ -52,23 +52,11 @@ import org.apache.tomcat.util.http.fileupload.util.Streams;
  * it into memory, which may come handy with large files.
  *
  * <p>Temporary files, which are created for file items, should be
- * deleted later on. The best way to do this is using a
- * {@link org.apache.tomcat.util.http.fileupload.FileCleaningTracker
- * FileCleaningTracker}, which you can set on the
- * {@link DiskFileItemFactory}. However, if you do use such a tracker,
- * then you must consider the following: Temporary files are automatically
- * deleted as soon as they are no longer needed. (More precisely, when the
- * corresponding instance of {@link java.io.File} is garbage collected.)
- * This is done by the so-called reaper thread, which is started
- * automatically when the class {@link org.apache.commons.io.FileCleaner}
- * is loaded.
- * It might make sense to terminate that thread, for example, if
- * your web application ends. See the section on "Resource cleanup"
- * in the users guide of commons-fileupload.</p>
+ * deleted later on.</p>
  *
  * @since FileUpload 1.1
  *
- * @version $Id: DiskFileItem.java 1470437 2013-04-22 10:35:10Z markt $
+ * @version $Id: DiskFileItem.java 1521056 2013-09-09 11:37:47Z markt $
  */
 public class DiskFileItem
     implements FileItem {
diff --git a/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItemFactory.java b/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItemFactory.java
index 294d3e7..869aad0 100644
--- a/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItemFactory.java
+++ b/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItemFactory.java
@@ -53,22 +53,11 @@ import org.apache.tomcat.util.http.fileupload.FileItemFactory;
  * </p>
  *
  * <p>Temporary files, which are created for file items, should be
- * deleted later on. The best way to do this is using a
- * {@link FileCleaningTracker}, which you can set on the
- * {@link DiskFileItemFactory}. However, if you do use such a tracker,
- * then you must consider the following: Temporary files are automatically
- * deleted as soon as they are no longer needed. (More precisely, when the
- * corresponding instance of {@link java.io.File} is garbage collected.)
- * This is done by the so-called reaper thread, which is started
- * automatically when the class {@link org.apache.commons.io.FileCleaner}
- * is loaded.
- * It might make sense to terminate that thread, for example, if
- * your web application ends. See the section on "Resource cleanup"
- * in the users guide of commons-fileupload.</p>
+ * deleted later on.</p>
  *
  * @since FileUpload 1.1
  *
- * @version $Id: DiskFileItemFactory.java 1456935 2013-03-15 12:47:29Z markt $
+ * @version $Id: DiskFileItemFactory.java 1521056 2013-09-09 11:37:47Z markt $
  */
 public class DiskFileItemFactory implements FileItemFactory {
 
diff --git a/java/org/apache/tomcat/util/http/fileupload/util/Streams.java b/java/org/apache/tomcat/util/http/fileupload/util/Streams.java
index 04e1232..927515c 100644
--- a/java/org/apache/tomcat/util/http/fileupload/util/Streams.java
+++ b/java/org/apache/tomcat/util/http/fileupload/util/Streams.java
@@ -26,7 +26,7 @@ import org.apache.tomcat.util.http.fileupload.InvalidFileNameException;
 /**
  * Utility class for working with streams.
  *
- * @version $Id: Streams.java 1456935 2013-03-15 12:47:29Z markt $
+ * @version $Id: Streams.java 1507053 2013-07-25 16:20:41Z markt $
  */
 public final class Streams {
 
@@ -186,7 +186,7 @@ public final class Streams {
     public static String checkFileName(String pFileName) {
         if (pFileName != null  &&  pFileName.indexOf('\u0000') != -1) {
             // pFileName.replace("\u0000", "\\0")
-            final StringBuffer sb = new StringBuffer();
+            final StringBuilder sb = new StringBuilder();
             for (int i = 0;  i < pFileName.length();  i++) {
                 char c = pFileName.charAt(i);
                 switch (c) {
diff --git a/java/org/apache/tomcat/util/http/parser/HttpParser.java b/java/org/apache/tomcat/util/http/parser/HttpParser.java
index c243ee4..fe85299 100644
--- a/java/org/apache/tomcat/util/http/parser/HttpParser.java
+++ b/java/org/apache/tomcat/util/http/parser/HttpParser.java
@@ -210,11 +210,13 @@ public class HttpParser {
         while (lookForSemiColon == SkipConstantResult.FOUND) {
             String attribute = readToken(input);
 
+            String value = "";
             if (skipConstant(input, "=") == SkipConstantResult.FOUND) {
-                String value = readTokenOrQuotedString(input, true);
+                value = readTokenOrQuotedString(input, true);
+            }
+
+            if (attribute != null) {
                 parameters.put(attribute.toLowerCase(Locale.ENGLISH), value);
-            } else {
-                parameters.put(attribute.toLowerCase(Locale.ENGLISH), "");
             }
 
             lookForSemiColon = skipConstant(input, ";");
diff --git a/java/org/apache/tomcat/util/modeler/BaseModelMBean.java b/java/org/apache/tomcat/util/modeler/BaseModelMBean.java
index 7d5145b..ddbbd4e 100644
--- a/java/org/apache/tomcat/util/modeler/BaseModelMBean.java
+++ b/java/org/apache/tomcat/util/modeler/BaseModelMBean.java
@@ -270,7 +270,7 @@ public class BaseModelMBean implements DynamicMBean, MBeanRegistration, ModelMBe
      *
      * @exception MBeanException if the initializer of an object
      *  throws an exception
-     * @exception ReflectioNException if a Java reflection exception
+     * @exception ReflectionException if a Java reflection exception
      *  occurs when invoking a method
      */
     @Override
@@ -538,8 +538,6 @@ public class BaseModelMBean implements DynamicMBean, MBeanRegistration, ModelMBe
      *
      * @exception InstanceNotFoundException if the managed resource object
      *  cannot be found
-     * @exception InvalidTargetObjectTypeException if this ModelMBean is
-     *  asked to handle a reference type it cannot deal with
      * @exception MBeanException if the initializer of the object throws
      *  an exception
      * @exception RuntimeOperationsException if the managed resource or the
diff --git a/java/org/apache/tomcat/util/modeler/ManagedBean.java b/java/org/apache/tomcat/util/modeler/ManagedBean.java
index 823baf6..81315db 100644
--- a/java/org/apache/tomcat/util/modeler/ManagedBean.java
+++ b/java/org/apache/tomcat/util/modeler/ManagedBean.java
@@ -42,7 +42,7 @@ import javax.management.ServiceNotFoundException;
  * descriptor.</p>
  *
  * @author Craig R. McClanahan
- * @version $Id: ManagedBean.java 1457749 2013-03-18 13:10:33Z markt $
+ * @version $Id: ManagedBean.java 1515583 2013-08-19 20:11:47Z markt $
  */
 
 public class ManagedBean implements java.io.Serializable {
@@ -305,8 +305,6 @@ public class ManagedBean implements java.io.Serializable {
      *
      * @exception InstanceNotFoundException if the managed resource
      *  object cannot be found
-     * @exception javax.management.modelmbean.InvalidTargetObjectTypeException
-     *  if our MBean cannot handle object references (should never happen)
      * @exception MBeanException if a problem occurs instantiating the
      *  <code>ModelMBean</code> instance
      * @exception RuntimeOperationsException if a JMX runtime error occurs
@@ -333,8 +331,6 @@ public class ManagedBean implements java.io.Serializable {
      *
      * @exception InstanceNotFoundException if the managed resource
      *  object cannot be found
-     * @exception javax.management.modelmbean.InvalidTargetObjectTypeException
-     *  if our MBean cannot handle object references (should never happen)
      * @exception MBeanException if a problem occurs instantiating the
      *  <code>ModelMBean</code> instance
      * @exception RuntimeOperationsException if a JMX runtime error occurs
diff --git a/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsDOMSource.java b/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsDOMSource.java
index 3b6d0d5..cd724b7 100644
--- a/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsDOMSource.java
+++ b/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsDOMSource.java
@@ -36,7 +36,10 @@ import org.apache.tomcat.util.modeler.Registry;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 
-
+/**
+ * @deprecated Unused: Will be removed in Tomcat 8.0.x
+ */
+ at Deprecated
 public class MbeansDescriptorsDOMSource extends ModelerSource
 {
     private static final Log log = LogFactory.getLog(MbeansDescriptorsDOMSource.class);
diff --git a/java/org/apache/tomcat/util/net/AbstractEndpoint.java b/java/org/apache/tomcat/util/net/AbstractEndpoint.java
index df078a5..af56bf2 100644
--- a/java/org/apache/tomcat/util/net/AbstractEndpoint.java
+++ b/java/org/apache/tomcat/util/net/AbstractEndpoint.java
@@ -55,7 +55,8 @@ public abstract class AbstractEndpoint {
         public enum SocketState {
             // TODO Add a new state to the AsyncStateMachine and remove
             //      ASYNC_END (if possible)
-            OPEN, CLOSED, LONG, ASYNC_END, SENDFILE, UPGRADING, UPGRADED
+            OPEN, CLOSED, LONG, ASYNC_END, SENDFILE, UPGRADING_TOMCAT,
+            UPGRADING, UPGRADED
         }
 
 
@@ -503,6 +504,18 @@ public abstract class AbstractEndpoint {
                 //this is our internal one, so we need to shut it down
                 ThreadPoolExecutor tpe = (ThreadPoolExecutor) executor;
                 tpe.shutdownNow();
+                int count = 0;
+                while (count < 50 && tpe.isTerminating()) {
+                    try {
+                        Thread.sleep(100);
+                        count++;
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    }
+                }
+                if (tpe.isTerminating()) {
+                    getLog().warn(sm.getString("endpoint.warn.executorShutdown", getName()));
+                }
                 TaskQueue queue = (TaskQueue) tpe.getQueue();
                 queue.setParent(null);
             }
diff --git a/java/org/apache/tomcat/util/net/AprEndpoint.java b/java/org/apache/tomcat/util/net/AprEndpoint.java
index 7f4f2e6..b1f5892 100644
--- a/java/org/apache/tomcat/util/net/AprEndpoint.java
+++ b/java/org/apache/tomcat/util/net/AprEndpoint.java
@@ -22,6 +22,8 @@ import java.security.PrivilegedAction;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Executor;
 import java.util.concurrent.RejectedExecutionException;
@@ -97,6 +99,9 @@ public class AprEndpoint extends AbstractEndpoint {
     protected ConcurrentLinkedQueue<SocketWrapper<Long>> waitingRequests =
         new ConcurrentLinkedQueue<SocketWrapper<Long>>();
 
+    private final Map<Long,AprSocketWrapper> connections =
+            new ConcurrentHashMap<Long, AprSocketWrapper>();
+
     // ------------------------------------------------------------ Constructor
 
     public AprEndpoint() {
@@ -173,43 +178,29 @@ public class AprEndpoint extends AbstractEndpoint {
 
 
     /**
-     * Poller thread count.
-     */
-    protected int pollerThreadCount = 0;
-    public void setPollerThreadCount(int pollerThreadCount) { this.pollerThreadCount = pollerThreadCount; }
-    public int getPollerThreadCount() { return pollerThreadCount; }
-
-
-    /**
      * The socket poller.
      */
-    protected Poller[] pollers = null;
-    protected int pollerRoundRobin = 0;
+    protected Poller poller = null;
     public Poller getPoller() {
-        pollerRoundRobin = (pollerRoundRobin + 1) % pollers.length;
-        return pollers[pollerRoundRobin];
+        return poller;
     }
 
 
     /**
-     * The socket poller used for Comet support.
+     * The socket poller.
      */
-    protected Poller[] cometPollers = null;
-    protected int cometPollerRoundRobin = 0;
-    public Poller getCometPoller() {
-        cometPollerRoundRobin = (cometPollerRoundRobin + 1) % cometPollers.length;
-        return cometPollers[cometPollerRoundRobin];
+    protected AsyncTimeout asyncTimeout = null;
+    public AsyncTimeout getAsyncTimeout() {
+        return asyncTimeout;
     }
 
 
     /**
      * The static file sender.
      */
-    protected Sendfile[] sendfiles = null;
-    protected int sendfileRoundRobin = 0;
+    protected Sendfile sendfile = null;
     public Sendfile getSendfile() {
-        sendfileRoundRobin = (sendfileRoundRobin + 1) % sendfiles.length;
-        return sendfiles[sendfileRoundRobin];
+        return sendfile;
     }
 
 
@@ -368,15 +359,11 @@ public class AprEndpoint extends AbstractEndpoint {
      * Number of keepalive sockets.
      */
     public int getKeepAliveCount() {
-        if (pollers == null) {
+        if (poller == null) {
             return 0;
         }
 
-        int keepAliveCount = 0;
-        for (int i = 0; i < pollers.length; i++) {
-            keepAliveCount += pollers[i].getKeepAliveCount();
-        }
-        return keepAliveCount;
+        return poller.getConnectionCount();
     }
 
 
@@ -384,15 +371,11 @@ public class AprEndpoint extends AbstractEndpoint {
      * Number of sendfile sockets.
      */
     public int getSendfileCount() {
-        if (sendfiles == null) {
+        if (sendfile == null) {
             return 0;
         }
 
-        int sendfileCount = 0;
-        for (int i = 0; i < sendfiles.length; i++) {
-            sendfileCount += sendfiles[i].getSendfileCount();
-        }
-        return sendfileCount;
+        return sendfile.getSendfileCount();
     }
 
 
@@ -460,35 +443,11 @@ public class AprEndpoint extends AbstractEndpoint {
             useSendfile = false;
         }
 
-        // Initialize thread count defaults for acceptor, poller and sendfile
+        // Initialize thread count default for acceptor
         if (acceptorThreadCount == 0) {
             // FIXME: Doesn't seem to work that well with multiple accept threads
             acceptorThreadCount = 1;
         }
-        if (pollerThreadCount == 0) {
-            if ((OS.IS_WIN32 || OS.IS_WIN64) && (getMaxConnections() > 1024)) {
-                // The maximum per poller to get reasonable performance is 1024
-                pollerThreadCount = getMaxConnections() / 1024;
-                // Adjust poller size so that it won't reach the limit
-                setMaxConnections(
-                        getMaxConnections() - (getMaxConnections() % 1024));
-            } else {
-                // No explicit poller size limitation
-                pollerThreadCount = 1;
-            }
-        }
-        if (sendfileThreadCount == 0) {
-            if ((OS.IS_WIN32 || OS.IS_WIN64) && (sendfileSize > 1024)) {
-                // The maximum per poller to get reasonable performance is 1024
-                sendfileThreadCount = sendfileSize / 1024;
-                // Adjust poller size so that it won't reach the limit
-                sendfileSize = sendfileSize - (sendfileSize % 1024);
-            } else {
-                // No explicit poller size limitation
-                // FIXME: Default to one per CPU ?
-                sendfileThreadCount = 1;
-            }
-        }
 
         // Delay accepting of new connections until data is available
         // Only Linux kernels 2.4 + have that implemented
@@ -627,45 +586,30 @@ public class AprEndpoint extends AbstractEndpoint {
 
             initializeConnectionLatch();
 
-            // Start poller threads
-            pollers = new Poller[pollerThreadCount];
-            for (int i = 0; i < pollerThreadCount; i++) {
-                pollers[i] = new Poller(false);
-                pollers[i].init();
-                pollers[i].setName(getName() + "-Poller-" + i);
-                pollers[i].setPriority(threadPriority);
-                pollers[i].setDaemon(true);
-                pollers[i].start();
-            }
+            // Start poller thread
+            poller = new Poller();
+            poller.init();
+            Thread pollerThread = new Thread(poller, getName() + "-Poller");
+            pollerThread.setPriority(threadPriority);
+            pollerThread.setDaemon(true);
+            pollerThread.start();
 
-            // Start comet poller threads
-            cometPollers = new Poller[pollerThreadCount];
-            for (int i = 0; i < pollerThreadCount; i++) {
-                cometPollers[i] = new Poller(true);
-                cometPollers[i].init();
-                cometPollers[i].setName(getName() + "-CometPoller-" + i);
-                cometPollers[i].setPriority(threadPriority);
-                cometPollers[i].setDaemon(true);
-                cometPollers[i].start();
-            }
-
-            // Start sendfile threads
+            // Start sendfile thread
             if (useSendfile) {
-                sendfiles = new Sendfile[sendfileThreadCount];
-                for (int i = 0; i < sendfileThreadCount; i++) {
-                    sendfiles[i] = new Sendfile();
-                    sendfiles[i].init();
-                    sendfiles[i].setName(getName() + "-Sendfile-" + i);
-                    sendfiles[i].setPriority(threadPriority);
-                    sendfiles[i].setDaemon(true);
-                    sendfiles[i].start();
-                }
+                sendfile = new Sendfile();
+                sendfile.init();
+                Thread sendfileThread =
+                        new Thread(sendfile, getName() + "-Sendfile");
+                sendfileThread.setPriority(threadPriority);
+                sendfileThread.setDaemon(true);
+                sendfileThread.start();
             }
 
             startAcceptorThreads();
 
             // Start async timeout thread
-            Thread timeoutThread = new Thread(new AsyncTimeout(),
+            asyncTimeout = new AsyncTimeout();
+            Thread timeoutThread = new Thread(asyncTimeout,
                     getName() + "-AsyncTimeout");
             timeoutThread.setPriority(threadPriority);
             timeoutThread.setDaemon(true);
@@ -685,6 +629,8 @@ public class AprEndpoint extends AbstractEndpoint {
         }
         if (running) {
             running = false;
+            poller.stop();
+            asyncTimeout.stop();
             unlockAccept();
             for (AbstractEndpoint.Acceptor acceptor : acceptors) {
                 long waitLeft = 10000;
@@ -694,8 +640,7 @@ public class AprEndpoint extends AbstractEndpoint {
                     try {
                         Thread.sleep(50);
                     } catch (InterruptedException e) {
-                        // Ignore and clean the interrupt flag
-                        Thread.interrupted();
+                        // Ignore
                     }
                     waitLeft -= 50;
                 }
@@ -710,31 +655,20 @@ public class AprEndpoint extends AbstractEndpoint {
                    }
                 }
             }
-            for (int i = 0; i < pollers.length; i++) {
-                try {
-                    pollers[i].destroy();
-                } catch (Exception e) {
-                    // Ignore
-                }
+            try {
+                poller.destroy();
+            } catch (Exception e) {
+                // Ignore
             }
-            pollers = null;
-            for (int i = 0; i < cometPollers.length; i++) {
+            poller = null;
+            connections.clear();
+            if (useSendfile) {
                 try {
-                    cometPollers[i].destroy();
+                    sendfile.destroy();
                 } catch (Exception e) {
                     // Ignore
                 }
-            }
-            cometPollers = null;
-            if (useSendfile) {
-                for (int i = 0; i < sendfiles.length; i++) {
-                    try {
-                        sendfiles[i].destroy();
-                    } catch (Exception e) {
-                        // Ignore
-                    }
-                }
-                sendfiles = null;
+                sendfile = null;
             }
         }
         shutdownExecutor();
@@ -842,16 +776,22 @@ public class AprEndpoint extends AbstractEndpoint {
         }
     }
 
-
     /**
-     * Process given socket.
+     * Process given socket. This is called when the socket has been
+     * accepted.
      */
     protected boolean processSocketWithOptions(long socket) {
         try {
             // During shutdown, executor may be null - avoid NPE
             if (running) {
-                SocketWrapper<Long> wrapper =
-                    new SocketWrapper<Long>(Long.valueOf(socket));
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("endpoint.debug.socket",
+                            Long.valueOf(socket)));
+                }
+                AprSocketWrapper wrapper =
+                        new AprSocketWrapper(Long.valueOf(socket));
+                wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
+                connections.put(Long.valueOf(socket), wrapper);
                 getExecutor().execute(new SocketWithOptionsProcessor(wrapper));
             }
         } catch (RejectedExecutionException x) {
@@ -869,9 +809,10 @@ public class AprEndpoint extends AbstractEndpoint {
 
 
     /**
-     * Process given socket.
+     * Process given socket. Called in non-comet mode, typically keep alive
+     * or upgraded protocol.
      */
-    protected boolean processSocket(long socket) {
+    public boolean processSocket(long socket, SocketStatus status) {
         try {
             Executor executor = getExecutor();
             if (executor == null) {
@@ -879,8 +820,11 @@ public class AprEndpoint extends AbstractEndpoint {
                         Long.valueOf(socket), null));
             } else {
                 SocketWrapper<Long> wrapper =
-                    new SocketWrapper<Long>(Long.valueOf(socket));
-                executor.execute(new SocketProcessor(wrapper, null));
+                        connections.get(Long.valueOf(socket));
+                // Make sure connection hasn't been closed
+                if (wrapper != null) {
+                    executor.execute(new SocketProcessor(wrapper, status));
+                }
             }
         } catch (RejectedExecutionException x) {
             log.warn("Socket processing request was rejected for:"+socket,x);
@@ -896,33 +840,6 @@ public class AprEndpoint extends AbstractEndpoint {
     }
 
 
-    /**
-     * Process given socket for an event.
-     */
-    public boolean processSocket(long socket, SocketStatus status) {
-        try {
-            Executor executor = getExecutor();
-            if (executor == null) {
-                log.warn(sm.getString("endpoint.warn.noExector",
-                        Long.valueOf(socket), status));
-            } else {
-                SocketWrapper<Long> wrapper =
-                        new SocketWrapper<Long>(Long.valueOf(socket));
-                executor.execute(new SocketEventProcessor(wrapper, status));
-            }
-        } catch (RejectedExecutionException x) {
-            log.warn("Socket processing request was rejected for:"+socket,x);
-            return false;
-        } catch (Throwable t) {
-            ExceptionUtils.handleThrowable(t);
-            // This means we got an OOM or similar creating a thread, or that
-            // the pool and its queue are full
-            log.error(sm.getString("endpoint.process.fail"), t);
-            return false;
-        }
-        return true;
-    }
-
     public boolean processSocketAsync(SocketWrapper<Long> socket,
             SocketStatus status) {
         try {
@@ -980,10 +897,24 @@ public class AprEndpoint extends AbstractEndpoint {
         // countDownConnection(). Once the connector is stopped, the latch is
         // removed so it does not matter that destroySocket() does not call
         // countDownConnection() in that case
+        Poller poller = this.poller;
+        if (poller != null) {
+            poller.removeFromPoller(socket);
+        }
+        connections.remove(Long.valueOf(socket));
         destroySocket(socket, running);
     }
 
     private void destroySocket(long socket, boolean doIt) {
+        if (log.isDebugEnabled()) {
+            String msg = sm.getString("endpoint.debug.destroySocket",
+                    Long.valueOf(socket), Boolean.valueOf(doIt));
+            if (log.isTraceEnabled()) {
+                log.trace(msg, new Exception());
+            } else {
+                log.debug(msg);
+            }
+        }
         // Be VERY careful if you call this method directly. If it is called
         // twice for the same socket the JVM will core. Currently this is only
         // called from Poller.closePollset() to ensure kept alive connections
@@ -999,7 +930,6 @@ public class AprEndpoint extends AbstractEndpoint {
         return log;
     }
 
-
     // --------------------------------------------------- Acceptor Inner Class
     /**
      * The background thread that listens for incoming TCP/IP connections and
@@ -1092,6 +1022,9 @@ public class AprEndpoint extends AbstractEndpoint {
      * Async timeout thread
      */
     protected class AsyncTimeout implements Runnable {
+
+        private volatile boolean asyncTimeoutRunning = true;
+
         /**
          * The background thread that checks async requests and fires the
          * timeout if there has been no activity.
@@ -1100,7 +1033,7 @@ public class AprEndpoint extends AbstractEndpoint {
         public void run() {
 
             // Loop until we receive a shutdown command
-            while (running) {
+            while (asyncTimeoutRunning) {
                 try {
                     Thread.sleep(1000);
                 } catch (InterruptedException e) {
@@ -1121,7 +1054,7 @@ public class AprEndpoint extends AbstractEndpoint {
                 }
 
                 // Loop if endpoint is paused
-                while (paused && running) {
+                while (paused && asyncTimeoutRunning) {
                     try {
                         Thread.sleep(1000);
                     } catch (InterruptedException e) {
@@ -1131,226 +1064,719 @@ public class AprEndpoint extends AbstractEndpoint {
 
             }
         }
+
+
+        protected void stop() {
+            asyncTimeoutRunning = false;
+        }
     }
 
 
-    // ----------------------------------------------------- Poller Inner Class
-    /**
-     * Poller class.
-     */
-    public class Poller extends Thread {
+    // -------------------------------------------------- SocketInfo Inner Class
 
-        public static final int FLAGS_READ = Poll.APR_POLLIN;
-        public static final int FLAGS_WRITE  = Poll.APR_POLLOUT;
+    public static class SocketInfo {
+        public long socket;
+        public int timeout;
+        public int flags;
+        public boolean read() {
+            return (flags & Poll.APR_POLLIN) == Poll.APR_POLLIN;
+        }
+        public boolean write() {
+            return (flags & Poll.APR_POLLOUT) == Poll.APR_POLLOUT;
+        }
+        public static int merge(int flag1, int flag2) {
+            return ((flag1 & Poll.APR_POLLIN) | (flag2 & Poll.APR_POLLIN))
+                | ((flag1 & Poll.APR_POLLOUT) | (flag2 & Poll.APR_POLLOUT));
+        }
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("Socket: [");
+            sb.append(socket);
+            sb.append("], timeout: [");
+            sb.append(timeout);
+            sb.append("], flags: [");
+            sb.append(flags);
+            return sb.toString();
+        }
+    }
 
-        // Need two pollsets since the socketTimeout and the keep-alive timeout
-        // can have different values.
-        private long connectionPollset = 0;
-        private long pool = 0;
-        private long[] desc;
 
-        private long[] addSocket;
-        private int[] addSocketTimeout;
-        private int[] addSocketFlags;
+    // ---------------------------------------------- SocketTimeouts Inner Class
 
-        private volatile int addCount = 0;
+    public class SocketTimeouts {
+        protected int size;
 
-        private boolean comet = true;
+        protected long[] sockets;
+        protected long[] timeouts;
+        protected int pos = 0;
 
-        protected volatile int keepAliveCount = 0;
-        public int getKeepAliveCount() { return keepAliveCount; }
+        public SocketTimeouts(int size) {
+            this.size = 0;
+            sockets = new long[size];
+            timeouts = new long[size];
+        }
 
-        public Poller(boolean comet) {
-            this.comet = comet;
+        public void add(long socket, long timeout) {
+            sockets[size] = socket;
+            timeouts[size] = timeout;
+            size++;
         }
 
+        public boolean remove(long socket) {
+            for (int i = 0; i < size; i++) {
+                if (sockets[i] == socket) {
+                    sockets[i] = sockets[size - 1];
+                    timeouts[i] = timeouts[size - 1];
+                    size--;
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public long check(long date) {
+            while (pos < size) {
+                if (date >= timeouts[pos]) {
+                    long result = sockets[pos];
+                    sockets[pos] = sockets[size - 1];
+                    timeouts[pos] = timeouts[size - 1];
+                    size--;
+                    return result;
+                }
+                pos++;
+            }
+            pos = 0;
+            return 0;
+        }
+
+    }
+
+
+    // -------------------------------------------------- SocketList Inner Class
+
+    public class SocketList {
+        protected int size;
+        protected int pos;
+
+        protected long[] sockets;
+        protected int[] timeouts;
+        protected int[] flags;
+
+        protected SocketInfo info = new SocketInfo();
+
+        public SocketList(int size) {
+            this.size = 0;
+            pos = 0;
+            sockets = new long[size];
+            timeouts = new int[size];
+            flags = new int[size];
+        }
+
+        public int size() {
+            return this.size;
+        }
+
+        public SocketInfo get() {
+            if (pos == size) {
+                return null;
+            } else {
+                info.socket = sockets[pos];
+                info.timeout = timeouts[pos];
+                info.flags = flags[pos];
+                pos++;
+                return info;
+            }
+        }
+
+        public void clear() {
+            size = 0;
+            pos = 0;
+        }
+
+        public boolean add(long socket, int timeout, int flag) {
+            if (size == sockets.length) {
+                return false;
+            } else {
+                for (int i = 0; i < size; i++) {
+                    if (sockets[i] == socket) {
+                        flags[i] = SocketInfo.merge(flags[i], flag);
+                        return true;
+                    }
+                }
+                sockets[size] = socket;
+                timeouts[size] = timeout;
+                flags[size] = flag;
+                size++;
+                return true;
+            }
+        }
+
+        public void duplicate(SocketList copy) {
+            copy.size = size;
+            copy.pos = pos;
+            System.arraycopy(sockets, 0, copy.sockets, 0, size);
+            System.arraycopy(timeouts, 0, copy.timeouts, 0, size);
+            System.arraycopy(flags, 0, copy.flags, 0, size);
+        }
+
+    }
+
+    // ------------------------------------------------------ Poller Inner Class
+
+   public class Poller implements Runnable {
+
+        /**
+         * Pointers to the pollers.
+         */
+        protected long[] pollers = null;
+
+        /**
+         * Actual poller size.
+         */
+        protected int actualPollerSize = 0;
+
+        /**
+         * Amount of spots left in the poller.
+         */
+        protected int[] pollerSpace = null;
+
+        /**
+         * Amount of low level pollers in use by this poller.
+         */
+        protected int pollerCount;
+
+        /**
+         * Timeout value for the poll call.
+         */
+        protected int pollerTime;
+
+        /**
+         * Root pool.
+         */
+        protected long pool = 0;
+
+        /**
+         * Socket descriptors.
+         */
+        protected long[] desc;
+
+        /**
+         * List of sockets to be added to the poller.
+         */
+        protected SocketList addList = null;
+
+        /**
+         * List of sockets to be added to the poller.
+         */
+        protected SocketList localAddList = null;
+
+        /**
+         * Structure used for storing timeouts.
+         */
+        protected SocketTimeouts timeouts = null;
+
+
+        /**
+         * Last run of maintain. Maintain will run usually every 5s.
+         */
+        protected long lastMaintain = System.currentTimeMillis();
+
+
+        /**
+         * Amount of connections inside this poller.
+         */
+        protected int connectionCount = 0;
+        public int getConnectionCount() { return connectionCount; }
+
+
+        private volatile boolean pollerRunning = true;
+
         /**
          * Create the poller. With some versions of APR, the maximum poller size
          * will be 62 (recompiling APR is necessary to remove this limitation).
          */
         protected void init() {
+
             pool = Pool.create(serverSockPool);
-            int size = getMaxConnections() / pollerThreadCount;
-            int socketTimeout = socketProperties.getSoTimeout();
-            connectionPollset = allocatePoller(size, pool, socketTimeout);
-            if (connectionPollset == 0 && size > 1024) {
-                size = 1024;
-                connectionPollset = allocatePoller(size, pool, socketTimeout);
+
+            // Single poller by default
+            int defaultPollerSize = getMaxConnections();
+
+            if ((OS.IS_WIN32 || OS.IS_WIN64) && (defaultPollerSize > 1024)) {
+                // The maximum per poller to get reasonable performance is 1024
+                // Adjust poller size so that it won't reach the limit. This is
+                // a limitation of XP / Server 2003 that has been fixed in
+                // Vista / Server 2008 onwards.
+                actualPollerSize = 1024;
+            } else {
+                actualPollerSize = defaultPollerSize;
             }
-            if (connectionPollset == 0) {
-                size = 62;
-                connectionPollset = allocatePoller(size, pool, socketTimeout);
+
+            timeouts = new SocketTimeouts(defaultPollerSize);
+
+            // At the moment, setting the timeout is useless, but it could get
+            // used again as the normal poller could be faster using maintain.
+            // It might not be worth bothering though.
+            long pollset = allocatePoller(actualPollerSize, pool, -1);
+            if (pollset == 0 && actualPollerSize > 1024) {
+                actualPollerSize = 1024;
+                pollset = allocatePoller(actualPollerSize, pool, -1);
             }
-            desc = new long[size * 2];
-            keepAliveCount = 0;
-            addSocket = new long[size];
-            addSocketTimeout = new int[size];
-            addSocketFlags = new int[size];
-            addCount = 0;
+            if (pollset == 0) {
+                actualPollerSize = 62;
+                pollset = allocatePoller(actualPollerSize, pool, -1);
+            }
+
+            pollerCount = defaultPollerSize / actualPollerSize;
+            pollerTime = pollTime / pollerCount;
+
+            pollers = new long[pollerCount];
+            pollers[0] = pollset;
+            for (int i = 1; i < pollerCount; i++) {
+                pollers[i] = allocatePoller(actualPollerSize, pool, -1);
+            }
+
+            pollerSpace = new int[pollerCount];
+            for (int i = 0; i < pollerCount; i++) {
+                pollerSpace[i] = actualPollerSize;
+            }
+
+            desc = new long[actualPollerSize * 2];
+            connectionCount = 0;
+            addList = new SocketList(defaultPollerSize);
+            localAddList = new SocketList(defaultPollerSize);
+        }
+
+
+        /*
+         * This method is synchronized so that it is not possible for a socket
+         * to be added to the Poller's addList once this method has completed.
+         */
+        protected synchronized void stop() {
+            pollerRunning = false;
         }
 
+
         /**
          * Destroy the poller.
          */
-        @Override
-        public void destroy() {
-            // Close all sockets in the add queue
-            for (int i = 0; i < addCount; i++) {
-                if (comet) {
-                    processSocket(addSocket[i], SocketStatus.STOP);
-                } else {
-                    destroySocket(addSocket[i]);
-                }
-            }
-            // Close all sockets still in the poller
-            closePollset(connectionPollset);
-            Pool.destroy(pool);
-            keepAliveCount = 0;
-            addCount = 0;
+        protected void destroy() {
+            // Wait for pollerTime before doing anything, so that the poller
+            // threads exit, otherwise parallel destruction of sockets which are
+            // still in the poller can cause problems
             try {
-                while (this.isAlive()) {
-                    this.interrupt();
-                    this.join(1000);
+                synchronized (this) {
+                    this.notify();
+                    this.wait(pollTime / 1000);
                 }
             } catch (InterruptedException e) {
                 // Ignore
             }
-        }
-
-        private void closePollset(long pollset) {
-            int rv = Poll.pollset(pollset, desc);
-            if (rv > 0) {
-                for (int n = 0; n < rv; n++) {
-                    if (comet) {
-                        processSocket(desc[n*2+1], SocketStatus.STOP);
-                    } else {
-                        destroySocket(desc[n*2+1], true);
+            // Close all sockets in the add queue
+            SocketInfo info = addList.get();
+            while (info != null) {
+                boolean comet =
+                        connections.get(Long.valueOf(info.socket)).isComet();
+                if (!comet || (comet && !processSocket(
+                        info.socket, SocketStatus.STOP))) {
+                    destroySocket(info.socket);
+                }
+                info = addList.get();
+            }
+            addList.clear();
+            // Close all sockets still in the poller
+            for (int i = 0; i < pollerCount; i++) {
+                int rv = Poll.pollset(pollers[i], desc);
+                if (rv > 0) {
+                    for (int n = 0; n < rv; n++) {
+                        boolean comet = connections.get(
+                                Long.valueOf(desc[n*2+1])).isComet();
+                        if (!comet || (comet && !processSocket(
+                                desc[n*2+1], SocketStatus.STOP))) {
+                            destroySocket(desc[n*2+1], true);
+                        }
                     }
                 }
             }
+            Pool.destroy(pool);
+            connectionCount = 0;
         }
 
+
         /**
          * Add specified socket and associated pool to the poller. The socket
          * will be added to a temporary array, and polled first after a maximum
          * amount of time equal to pollTime (in most cases, latency will be much
-         * lower, however).
+         * lower, however). Note: If both read and write are false, the socket
+         * will only be checked for timeout; if the socket was already present
+         * in the poller, a callback event will be generated and the socket will
+         * be removed from the poller.
          *
-         * @param socket    to add to the poller
-         * @param timeout   read timeout (in milliseconds) to use with this
-         *                  socket. Use -1 for infinite timeout
-         * @param flags     flags that define the events that are to be polled
-         *                  for
+         * @param socket to add to the poller
+         * @param timeout to use for this connection
+         * @param read to do read polling
+         * @param write to do write polling
          */
-        public void add(long socket, int timeout, int flags) {
+        public void add(long socket, int timeout, boolean read, boolean write) {
+            add(socket, timeout,
+                    (read ? Poll.APR_POLLIN : 0) |
+                    (write ? Poll.APR_POLLOUT : 0));
+        }
+
+        private void add(long socket, int timeout, int flags) {
+            if (log.isDebugEnabled()) {
+                String msg = sm.getString("endpoint.debug.pollerAdd",
+                        Long.valueOf(socket), Integer.valueOf(timeout),
+                        Integer.valueOf(flags));
+                if (log.isTraceEnabled()) {
+                    log.trace(msg, new Exception());
+                } else {
+                    log.debug(msg);
+                }
+            }
+            if (timeout <= 0) {
+                // Always put a timeout in
+                timeout = Integer.MAX_VALUE;
+            }
+            boolean ok = false;
             synchronized (this) {
                 // Add socket to the list. Newly added sockets will wait
-                // at most for pollTime before being polled
-                if (addCount >= addSocket.length) {
-                    // Can't do anything: close the socket right away
-                    if (comet) {
-                        processSocket(socket, SocketStatus.ERROR);
-                    } else {
-                        destroySocket(socket);
+                // at most for pollTime before being polled. Don't add the
+                // socket once the poller has stopped but destroy it straight
+                // away
+                if (pollerRunning && addList.add(socket, timeout, flags)) {
+                    ok = true;
+                    this.notify();
+                }
+            }
+            if (!ok) {
+                // Can't do anything: close the socket right away
+                boolean comet = connections.get(
+                        Long.valueOf(socket)).isComet();
+                if (!comet || (comet && !processSocket(
+                        socket, SocketStatus.ERROR))) {
+                    destroySocket(socket);
+                }
+            }
+        }
+
+        /**
+         * Add specified socket to one of the pollers.
+         */
+        protected boolean addToPoller(long socket, int events) {
+            int rv = -1;
+            for (int i = 0; i < pollers.length; i++) {
+                if (pollerSpace[i] > 0) {
+                    rv = Poll.add(pollers[i], socket, events);
+                    if (rv == Status.APR_SUCCESS) {
+                        pollerSpace[i]--;
+                        connectionCount++;
+                        return true;
                     }
-                    return;
                 }
-                addSocket[addCount] = socket;
-                addSocketTimeout[addCount] = timeout;
-                addSocketFlags[addCount] = flags;
-                addCount++;
-                this.notify();
             }
+            return false;
+        }
+
+        /**
+         * Remove specified socket from the pollers.
+         */
+        protected boolean removeFromPoller(long socket) {
+            int rv = -1;
+            for (int i = 0; i < pollers.length; i++) {
+                if (pollerSpace[i] < actualPollerSize) {
+                    rv = Poll.remove(pollers[i], socket);
+                    if (rv != Status.APR_NOTFOUND) {
+                        pollerSpace[i]++;
+                        connectionCount--;
+                        break;
+                    }
+                }
+            }
+            return (rv == Status.APR_SUCCESS);
+        }
+
+        /**
+         * Timeout checks.
+         */
+        protected void maintain() {
+
+            long date = System.currentTimeMillis();
+            // Maintain runs at most once every 5s, although it will likely get
+            // called more
+            if ((date - lastMaintain) < 5000L) {
+                return;
+            } else {
+                lastMaintain = date;
+            }
+            long socket = timeouts.check(date);
+            while (socket != 0) {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("endpoint.debug.socketTimeout",
+                            Long.valueOf(socket)));
+                }
+                removeFromPoller(socket);
+                boolean comet = connections.get(
+                        Long.valueOf(socket)).isComet();
+                if (!comet || (comet && !processSocket(
+                        socket, SocketStatus.TIMEOUT))) {
+                    destroySocket(socket);
+                }
+                socket = timeouts.check(date);
+            }
+
         }
 
         /**
-         * The background thread that listens for incoming TCP/IP connections and
-         * hands them off to an appropriate processor.
+         * Displays the list of sockets in the pollers.
+         */
+        @Override
+        public String toString() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("Poller");
+            long[] res = new long[actualPollerSize * 2];
+            for (int i = 0; i < pollers.length; i++) {
+                int count = Poll.pollset(pollers[i], res);
+                buf.append(" [ ");
+                for (int j = 0; j < count; j++) {
+                    buf.append(desc[2*j+1]).append(" ");
+                }
+                buf.append("]");
+            }
+            return buf.toString();
+        }
+
+        /**
+         * The background thread that listens for incoming TCP/IP connections
+         * and hands them off to an appropriate processor.
          */
         @Override
         public void run() {
 
-            long maintainTime = 0;
+            int maintain = 0;
             // Loop until we receive a shutdown command
-            while (running) {
+            while (pollerRunning) {
+
                 // Loop if endpoint is paused
-                while (paused && running) {
+                while (pollerRunning && paused) {
                     try {
                         Thread.sleep(1000);
                     } catch (InterruptedException e) {
                         // Ignore
                     }
                 }
-
-                if (!running) {
-                    break;
-                }
-                if (keepAliveCount < 1 && addCount < 1) {
-                    synchronized (this) {
-                        while (keepAliveCount < 1 && addCount < 1 && running) {
-                            // Reset maintain time.
-                            maintainTime = 0;
-                            try {
-                                this.wait();
-                            } catch (InterruptedException e) {
-                                // Ignore
-                            }
+                // Check timeouts if the poller is empty
+                while (pollerRunning && connectionCount < 1 &&
+                        addList.size() < 1) {
+                    // Reset maintain time.
+                    try {
+                        if (getSoTimeout() > 0 && pollerRunning) {
+                            maintain();
                         }
+                        synchronized (this) {
+                            this.wait(10000);
+                        }
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    } catch (Throwable t) {
+                        ExceptionUtils.handleThrowable(t);
+                        getLog().warn(sm.getString("endpoint.timeout.err"));
                     }
                 }
 
-                if (!running) {
+                // Don't add or poll if the poller has been stopped
+                if (!pollerRunning) {
                     break;
                 }
+
                 try {
                     // Add sockets which are waiting to the poller
-                    if (addCount > 0) {
+                    if (addList.size() > 0) {
                         synchronized (this) {
-                            int successCount = 0;
-                            try {
-                                for (int i = (addCount - 1); i >= 0; i--) {
-                                    int timeout = addSocketTimeout[i];
-                                    if (timeout > 0) {
-                                        // Convert milliseconds to microseconds
-                                        timeout = timeout * 1000;
+                            // Duplicate to another list, so that the syncing is
+                            // minimal
+                            addList.duplicate(localAddList);
+                            addList.clear();
+                        }
+                        SocketInfo info = localAddList.get();
+                        while (info != null) {
+                            if (log.isDebugEnabled()) {
+                                log.debug(sm.getString(
+                                        "endpoint.debug.pollerAddDo",
+                                        Long.valueOf(info.socket)));
+                            }
+                            timeouts.remove(info.socket);
+                            AprSocketWrapper wrapper = connections.get(
+                                    Long.valueOf(info.socket));
+                            if (wrapper == null) {
+                                continue;
+                            }
+                            if (info.read() || info.write()) {
+                                boolean comet = wrapper.isComet();
+                                if (comet || wrapper.pollerFlags != 0) {
+                                    removeFromPoller(info.socket);
+                                }
+                                wrapper.pollerFlags = wrapper.pollerFlags |
+                                        (info.read() ? Poll.APR_POLLIN : 0) |
+                                        (info.write() ? Poll.APR_POLLOUT : 0);
+                                if (!addToPoller(info.socket, wrapper.pollerFlags)) {
+                                    // Can't do anything: close the socket right
+                                    // away
+                                    if (!comet || (comet && !processSocket(
+                                            info.socket, SocketStatus.ERROR))) {
+                                        destroySocket(info.socket);
                                     }
-                                    int rv = Poll.addWithTimeout(
-                                            connectionPollset, addSocket[i],
-                                            addSocketFlags[i], timeout);
-                                    if (rv == Status.APR_SUCCESS) {
-                                        successCount++;
+                                } else {
+                                    timeouts.add(info.socket,
+                                            System.currentTimeMillis() +
+                                                    info.timeout);
+                                }
+                            } else {
+                                // Should never happen.
+                                destroySocket(info.socket);
+                                getLog().warn(sm.getString(
+                                        "endpoint.apr.pollAddInvalid", info));
+                            }
+                            info = localAddList.get();
+                        }
+                    }
+
+                    // Poll for the specified interval
+                    for (int i = 0; i < pollers.length; i++) {
+
+                        // Flags to ask to reallocate the pool
+                        boolean reset = false;
+                        //ArrayList<Long> skip = null;
+
+                        int rv = 0;
+                        // Iterate on each pollers, but no need to poll empty pollers
+                        if (pollerSpace[i] < actualPollerSize) {
+                            rv = Poll.poll(pollers[i], pollerTime, desc, true);
+                        }
+                        if (rv > 0) {
+                            pollerSpace[i] += rv;
+                            connectionCount -= rv;
+                            for (int n = 0; n < rv; n++) {
+                                timeouts.remove(desc[n*2+1]);
+                                AprSocketWrapper wrapper = connections.get(
+                                        Long.valueOf(desc[n*2+1]));
+                                if (getLog().isDebugEnabled()) {
+                                    log.debug(sm.getString(
+                                            "endpoint.debug.pollerProcess",
+                                            Long.valueOf(desc[n*2+1]),
+                                            Long.valueOf(desc[n*2])));
+                                }
+                                wrapper.pollerFlags = wrapper.pollerFlags & ~((int) desc[n*2]);
+                                // Check for failed sockets and hand this socket off to a worker
+                                if (wrapper.isComet()) {
+                                    // Event processes either a read or a write depending on what the poller returns
+                                    if (((desc[n*2] & Poll.APR_POLLHUP) == Poll.APR_POLLHUP)
+                                            || ((desc[n*2] & Poll.APR_POLLERR) == Poll.APR_POLLERR)
+                                            || ((desc[n*2] & Poll.APR_POLLNVAL) == Poll.APR_POLLNVAL)) {
+                                        if (!processSocket(desc[n*2+1], SocketStatus.ERROR)) {
+                                            // Close socket and clear pool
+                                            destroySocket(desc[n*2+1]);
+                                        }
+                                    } else if ((desc[n*2] & Poll.APR_POLLIN) == Poll.APR_POLLIN) {
+                                        if (wrapper.pollerFlags != 0) {
+                                            add(desc[n*2+1], 1, wrapper.pollerFlags);
+                                        }
+                                        if (!processSocket(desc[n*2+1], SocketStatus.OPEN_READ)) {
+                                            // Close socket and clear pool
+                                            destroySocket(desc[n*2+1]);
+                                        }
+                                    } else if ((desc[n*2] & Poll.APR_POLLOUT) == Poll.APR_POLLOUT) {
+                                        if (wrapper.pollerFlags != 0) {
+                                            add(desc[n*2+1], 1, wrapper.pollerFlags);
+                                        }
+                                        if (!processSocket(desc[n*2+1], SocketStatus.OPEN_WRITE)) {
+                                            // Close socket and clear pool
+                                            destroySocket(desc[n*2+1]);
+                                        }
                                     } else {
-                                        // Can't do anything: close the socket right away
-                                        if (comet) {
-                                            processSocket(addSocket[i], SocketStatus.ERROR);
-                                        } else {
-                                            destroySocket(addSocket[i]);
+                                        // Unknown event
+                                        getLog().warn(sm.getString(
+                                                "endpoint.apr.pollUnknownEvent",
+                                                Long.valueOf(desc[n*2])));
+                                        if (!processSocket(desc[n*2+1], SocketStatus.ERROR)) {
+                                            // Close socket and clear pool
+                                            destroySocket(desc[n*2+1]);
                                         }
                                     }
+                                } else if (((desc[n*2] & Poll.APR_POLLHUP) == Poll.APR_POLLHUP)
+                                        || ((desc[n*2] & Poll.APR_POLLERR) == Poll.APR_POLLERR)
+                                        || ((desc[n*2] & Poll.APR_POLLNVAL) == Poll.APR_POLLNVAL)) {
+                                    // Close socket and clear pool
+                                    destroySocket(desc[n*2+1]);
+                                } else if (((desc[n*2] & Poll.APR_POLLIN) == Poll.APR_POLLIN)
+                                        || ((desc[n*2] & Poll.APR_POLLOUT) == Poll.APR_POLLOUT)) {
+                                    boolean error = false;
+                                    if (((desc[n*2] & Poll.APR_POLLIN) == Poll.APR_POLLIN) &&
+                                            !processSocket(desc[n*2+1], SocketStatus.OPEN_READ)) {
+                                        error = true;
+                                        // Close socket and clear pool
+                                        destroySocket(desc[n*2+1]);
+                                    }
+                                    if (!error &&
+                                            ((desc[n*2] & Poll.APR_POLLOUT) == Poll.APR_POLLOUT) &&
+                                            !processSocket(desc[n*2+1], SocketStatus.OPEN_WRITE)) {
+                                        // Close socket and clear pool
+                                        destroySocket(desc[n*2+1]);
+                                    }
+                                } else {
+                                    // Unknown event
+                                    getLog().warn(sm.getString(
+                                            "endpoint.apr.pollUnknownEvent",
+                                            Long.valueOf(desc[n*2])));
+                                    // Close socket and clear pool
+                                    destroySocket(desc[n*2+1]);
+                                }
+                            }
+                        } else if (rv < 0) {
+                            int errn = -rv;
+                            // Any non timeup or interrupted error is critical
+                            if ((errn != Status.TIMEUP) && (errn != Status.EINTR)) {
+                                if (errn >  Status.APR_OS_START_USERERR) {
+                                    errn -=  Status.APR_OS_START_USERERR;
                                 }
-                            } finally {
-                                keepAliveCount += successCount;
-                                addCount = 0;
+                                getLog().error(sm.getString(
+                                        "endpoint.apr.pollError",
+                                        Integer.valueOf(errn),
+                                        Error.strerror(errn)));
+                                // Destroy and reallocate the poller
+                                reset = true;
                             }
                         }
-                    }
 
-                    maintainTime += pollTime;
-                    // Poll for the specified interval
-                    if (doPoll(connectionPollset)) {
-                        continue;
+                        if (reset) {
+                            // Reallocate the current poller
+                            int count = Poll.pollset(pollers[i], desc);
+                            long newPoller = allocatePoller(actualPollerSize, pool, -1);
+                            // Don't restore connections for now, since I have not tested it
+                            pollerSpace[i] = actualPollerSize;
+                            connectionCount -= count;
+                            Poll.destroy(pollers[i]);
+                            pollers[i] = newPoller;
+                        }
+
                     }
 
-                    // Check timeouts (much less frequently that polling)
-                    if (maintainTime > 1000000L && running) {
-                        maintainTime = 0;
-                        if (socketProperties.getSoTimeout() > 0) {
-                            doTimeout(connectionPollset);
-                        }
+                    // Process socket timeouts
+                    if (getSoTimeout() > 0 && maintain++ > 1000 && pollerRunning) {
+                        // This works and uses only one timeout mechanism for everything, but the
+                        // non event poller might be a bit faster by using the old maintain.
+                        maintain = 0;
+                        maintain();
                     }
+
                 } catch (Throwable t) {
                     ExceptionUtils.handleThrowable(t);
-                    log.error(sm.getString("endpoint.poll.error"), t);
+                    if (maintain == 0) {
+                        getLog().warn(sm.getString("endpoint.timeout.error"), t);
+                    } else {
+                        getLog().warn(sm.getString("endpoint.poll.error"), t);
+                    }
                 }
 
             }
@@ -1358,59 +1784,6 @@ public class AprEndpoint extends AbstractEndpoint {
             synchronized (this) {
                 this.notifyAll();
             }
-
-        }
-
-        private boolean doPoll(long pollset) {
-            int rv = Poll.poll(pollset, pollTime, desc, true);
-            if (rv > 0) {
-                keepAliveCount -= rv;
-                for (int n = 0; n < rv; n++) {
-                    // Check for failed sockets and hand this socket off to a worker
-                    if (((desc[n*2] & Poll.APR_POLLHUP) == Poll.APR_POLLHUP)
-                            || ((desc[n*2] & Poll.APR_POLLERR) == Poll.APR_POLLERR)
-                            || (comet && (!processSocket(desc[n*2+1], SocketStatus.OPEN)))
-                            || (!comet && (!processSocket(desc[n*2+1])))) {
-                        // Close socket and clear pool
-                        if (comet) {
-                            processSocket(desc[n*2+1], SocketStatus.DISCONNECT);
-                        } else {
-                            destroySocket(desc[n*2+1]);
-                        }
-                    }
-                }
-            } else if (rv < 0) {
-                int errn = -rv;
-                /* Any non timeup or interrupted error is critical */
-                if ((errn != Status.TIMEUP) && (errn != Status.EINTR)) {
-                    if (errn >  Status.APR_OS_START_USERERR) {
-                        errn -=  Status.APR_OS_START_USERERR;
-                    }
-                    log.error(sm.getString("endpoint.poll.fail", "" + errn, Error.strerror(errn)));
-                    // Handle poll critical failure
-                    synchronized (this) {
-                        destroy();
-                        init();
-                    }
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        private void doTimeout(long pollset) {
-            int rv = Poll.maintain(pollset, desc, true);
-            if (rv > 0) {
-                keepAliveCount -= rv;
-                for (int n = 0; n < rv; n++) {
-                    // Close socket and clear pool
-                    if (comet) {
-                        processSocket(desc[n], SocketStatus.TIMEOUT);
-                    } else {
-                        destroySocket(desc[n]);
-                    }
-                }
-            }
         }
     }
 
@@ -1441,56 +1814,66 @@ public class AprEndpoint extends AbstractEndpoint {
     // --------------------------------------------------- Sendfile Inner Class
 
 
-    /**
-     * Sendfile class.
-     */
-    public class Sendfile extends Thread {
+    public class Sendfile implements Runnable {
 
         protected long sendfilePollset = 0;
         protected long pool = 0;
         protected long[] desc;
         protected HashMap<Long, SendfileData> sendfileData;
 
-        protected volatile int sendfileCount;
+        protected int sendfileCount;
         public int getSendfileCount() { return sendfileCount; }
 
         protected ArrayList<SendfileData> addS;
-        protected volatile int addCount;
+
+        private volatile boolean sendfileRunning = true;
 
         /**
-         * Create the sendfile poller. With some versions of APR, the maximum poller size will
-         * be 62 (recompiling APR is necessary to remove this limitation).
+         * Create the sendfile poller. With some versions of APR, the maximum
+         * poller size will be 62 (recompiling APR is necessary to remove this
+         * limitation).
          */
         protected void init() {
             pool = Pool.create(serverSockPool);
-            int size = sendfileSize / sendfileThreadCount;
-            sendfilePollset = allocatePoller(size, pool, socketProperties.getSoTimeout());
+            int size = sendfileSize;
+            if (size <= 0) {
+                size = (OS.IS_WIN32 || OS.IS_WIN64) ? (1 * 1024) : (16 * 1024);
+            }
+            sendfilePollset = allocatePoller(size, pool, getSoTimeout());
             if (sendfilePollset == 0 && size > 1024) {
                 size = 1024;
-                sendfilePollset = allocatePoller(size, pool, socketProperties.getSoTimeout());
+                sendfilePollset = allocatePoller(size, pool, getSoTimeout());
             }
             if (sendfilePollset == 0) {
                 size = 62;
-                sendfilePollset = allocatePoller(size, pool, socketProperties.getSoTimeout());
+                sendfilePollset = allocatePoller(size, pool, getSoTimeout());
             }
             desc = new long[size * 2];
             sendfileData = new HashMap<Long, SendfileData>(size);
             addS = new ArrayList<SendfileData>();
-            addCount = 0;
         }
 
         /**
          * Destroy the poller.
          */
-        @Override
-        public void destroy() {
+        protected void destroy() {
+            sendfileRunning = false;
+            // Wait for polltime before doing anything, so that the poller threads
+            // exit, otherwise parallel destruction of sockets which are still
+            // in the poller can cause problems
+            try {
+                synchronized (this) {
+                    this.notify();
+                    this.wait(pollTime / 1000);
+                }
+            } catch (InterruptedException e) {
+                // Ignore
+            }
             // Close any socket remaining in the add queue
-            addCount = 0;
             for (int i = (addS.size() - 1); i >= 0; i--) {
                 SendfileData data = addS.get(i);
                 destroySocket(data.socket);
             }
-            addS.clear();
             // Close all sockets still in the poller
             int rv = Poll.pollset(sendfilePollset, desc);
             if (rv > 0) {
@@ -1500,14 +1883,6 @@ public class AprEndpoint extends AbstractEndpoint {
             }
             Pool.destroy(pool);
             sendfileData.clear();
-            try {
-                while (this.isAlive()) {
-                    this.interrupt();
-                    this.join(1000);
-                }
-            } catch (InterruptedException e) {
-                // Ignore
-            }
         }
 
         /**
@@ -1516,7 +1891,7 @@ public class AprEndpoint extends AbstractEndpoint {
          * will be handled asynchronously inside the kernel. As a result,
          * the poller will never be used.
          *
-         * @param data containing the reference to the data which should be sent
+         * @param data containing the reference to the data which should be snet
          * @return true if all the data has been sent right away, and false
          *              otherwise
          */
@@ -1524,13 +1899,6 @@ public class AprEndpoint extends AbstractEndpoint {
             // Initialize fd from data given
             try {
                 data.fdpool = Socket.pool(data.socket);
-            } catch (Exception e) {
-                // Pool not created so no need to destroy it.
-                log.error(sm.getString("endpoint.sendfile.error"), e);
-                data.socket = 0;
-                return false;
-            }
-            try {
                 data.fd = File.open
                     (data.fileName, File.APR_FOPEN_READ
                      | File.APR_FOPEN_SENDFILE_ENABLED | File.APR_FOPEN_BINARY,
@@ -1544,36 +1912,32 @@ public class AprEndpoint extends AbstractEndpoint {
                     if (nw < 0) {
                         if (!(-nw == Status.EAGAIN)) {
                             Pool.destroy(data.fdpool);
-                            // No need to close socket, this will be done by
-                            // calling code since data.socket == 0
                             data.socket = 0;
                             return false;
                         } else {
                             // Break the loop and add the socket to poller.
                             break;
                         }
-                    }
-
-                    data.pos = data.pos + nw;
-                    if (data.pos >= data.end) {
-                        // Entire file has been sent
-                        Pool.destroy(data.fdpool);
-                        // Set back socket to blocking mode
-                        Socket.timeoutSet(data.socket, socketProperties.getSoTimeout() * 1000);
-                        return true;
+                    } else {
+                        data.pos = data.pos + nw;
+                        if (data.pos >= data.end) {
+                            // Entire file has been sent
+                            Pool.destroy(data.fdpool);
+                            // Set back socket to blocking mode
+                            Socket.timeoutSet(
+                                    data.socket, getSoTimeout() * 1000);
+                            return true;
+                        }
                     }
                 }
             } catch (Exception e) {
-                log.error(sm.getString("endpoint.sendfile.error"), e);
-                Pool.destroy(data.fdpool);
-                data.socket = 0;
+                log.warn(sm.getString("endpoint.sendfile.error"), e);
                 return false;
             }
             // Add socket to the list. Newly added sockets will wait
             // at most for pollTime before being polled
             synchronized (this) {
                 addS.add(data);
-                addCount++;
                 this.notify();
             }
             return false;
@@ -1589,72 +1953,66 @@ public class AprEndpoint extends AbstractEndpoint {
             if (rv == Status.APR_SUCCESS) {
                 sendfileCount--;
             }
-            sendfileData.remove(Long.valueOf(data.socket));
+            sendfileData.remove(new Long(data.socket));
         }
 
         /**
-         * The background thread that listens for incoming TCP/IP connections and
-         * hands them off to an appropriate processor.
+         * The background thread that listens for incoming TCP/IP connections
+         * and hands them off to an appropriate processor.
          */
         @Override
         public void run() {
 
             long maintainTime = 0;
             // Loop until we receive a shutdown command
-            while (running) {
+            while (sendfileRunning) {
 
                 // Loop if endpoint is paused
-                while (paused && running) {
+                while (sendfileRunning && paused) {
                     try {
                         Thread.sleep(1000);
                     } catch (InterruptedException e) {
                         // Ignore
                     }
                 }
-
-                if (!running) {
-                    break;
-                }
-                if (sendfileCount < 1 && addCount < 1) {
-                    synchronized (this) {
-                        while (sendfileCount < 1 && addS.size() < 1 && running) {
-                            // Reset maintain time.
-                            maintainTime = 0;
-                            try {
-                                this.wait();
-                            } catch (InterruptedException e) {
-                                // Ignore
-                            }
+                // Loop if poller is empty
+                while (sendfileRunning && sendfileCount < 1 && addS.size() < 1) {
+                    // Reset maintain time.
+                    maintainTime = 0;
+                    try {
+                        synchronized (this) {
+                            this.wait();
                         }
+                    } catch (InterruptedException e) {
+                        // Ignore
                     }
                 }
 
-                if (!running) {
+                // Don't add or poll if the poller has been stopped
+                if (!sendfileRunning) {
                     break;
                 }
+                
                 try {
                     // Add socket to the poller
-                    if (addCount > 0) {
+                    if (addS.size() > 0) {
                         synchronized (this) {
-                            int successCount = 0;
-                            try {
-                                for (int i = (addS.size() - 1); i >= 0; i--) {
-                                    SendfileData data = addS.get(i);
-                                    int rv = Poll.add(sendfilePollset, data.socket, Poll.APR_POLLOUT);
-                                    if (rv == Status.APR_SUCCESS) {
-                                        sendfileData.put(Long.valueOf(data.socket), data);
-                                        successCount++;
-                                    } else {
-                                        log.warn(sm.getString("endpoint.sendfile.addfail", "" + rv, Error.strerror(rv)));
-                                        // Can't do anything: close the socket right away
-                                        destroySocket(data.socket);
-                                    }
+                            for (int i = (addS.size() - 1); i >= 0; i--) {
+                                SendfileData data = addS.get(i);
+                                int rv = Poll.add(sendfilePollset, data.socket, Poll.APR_POLLOUT);
+                                if (rv == Status.APR_SUCCESS) {
+                                    sendfileData.put(new Long(data.socket), data);
+                                    sendfileCount++;
+                                } else {
+                                    getLog().warn(sm.getString(
+                                            "endpoint.sendfile.addfail",
+                                            Integer.valueOf(rv),
+                                            Error.strerror(rv)));
+                                    // Can't do anything: close the socket right away
+                                    destroySocket(data.socket);
                                 }
-                            } finally {
-                                sendfileCount += successCount;
-                                addS.clear();
-                                addCount = 0;
                             }
+                            addS.clear();
                         }
                     }
 
@@ -1665,7 +2023,7 @@ public class AprEndpoint extends AbstractEndpoint {
                         for (int n = 0; n < rv; n++) {
                             // Get the sendfile state
                             SendfileData state =
-                                sendfileData.get(Long.valueOf(desc[n*2+1]));
+                                sendfileData.get(new Long(desc[n*2+1]));
                             // Problem events
                             if (((desc[n*2] & Poll.APR_POLLHUP) == Poll.APR_POLLHUP)
                                     || ((desc[n*2] & Poll.APR_POLLERR) == Poll.APR_POLLERR)) {
@@ -1695,12 +2053,13 @@ public class AprEndpoint extends AbstractEndpoint {
                                 if (state.keepAlive) {
                                     // Destroy file descriptor pool, which should close the file
                                     Pool.destroy(state.fdpool);
-                                    Socket.timeoutSet(state.socket, socketProperties.getSoTimeout() * 1000);
-                                    // If all done put the socket back in the poller for
-                                    // processing of further requests
-                                    getPoller().add(state.socket,
-                                            getKeepAliveTimeout(),
-                                            Poller.FLAGS_READ);
+                                    Socket.timeoutSet(state.socket,
+                                            getSoTimeout() * 1000);
+                                    // If all done put the socket back in the
+                                    // poller for processing of further requests
+                                    getPoller().add(
+                                            state.socket, getKeepAliveTimeout(),
+                                            true, false);
                                 } else {
                                     // Close the socket since this is
                                     // the end of not keep-alive request.
@@ -1715,7 +2074,10 @@ public class AprEndpoint extends AbstractEndpoint {
                             if (errn >  Status.APR_OS_START_USERERR) {
                                 errn -=  Status.APR_OS_START_USERERR;
                             }
-                            log.error(sm.getString("endpoint.poll.fail", "" + errn, Error.strerror(errn)));
+                            getLog().error(sm.getString(
+                                    "Unexpected poller error",
+                                    Integer.valueOf(errn),
+                                    Error.strerror(errn)));
                             // Handle poll critical failure
                             synchronized (this) {
                                 destroy();
@@ -1725,13 +2087,14 @@ public class AprEndpoint extends AbstractEndpoint {
                         }
                     }
                     // Call maintain for the sendfile poller
-                    if (socketProperties.getSoTimeout() > 0 && maintainTime > 1000000L && running) {
-                        rv = Poll.maintain(sendfilePollset, desc, true);
+                    if (getSoTimeout() > 0 &&
+                            maintainTime > 1000000L && sendfileRunning) {
+                        rv = Poll.maintain(sendfilePollset, desc, false);
                         maintainTime = 0;
                         if (rv > 0) {
                             for (int n = 0; n < rv; n++) {
                                 // Get the sendfile state
-                                SendfileData state = sendfileData.get(Long.valueOf(desc[n]));
+                                SendfileData state = sendfileData.get(new Long(desc[n]));
                                 // Close socket and clear pool
                                 remove(state);
                                 // Destroy file descriptor pool, which should close the file
@@ -1742,7 +2105,7 @@ public class AprEndpoint extends AbstractEndpoint {
                     }
                 } catch (Throwable t) {
                     ExceptionUtils.handleThrowable(t);
-                    log.error(sm.getString("endpoint.poll.error"), t);
+                    getLog().error(sm.getString("endpoint.poll.error"), t);
                 }
             }
 
@@ -1754,7 +2117,6 @@ public class AprEndpoint extends AbstractEndpoint {
 
     }
 
-
     // ------------------------------------------------ Handler Inner Interface
 
 
@@ -1775,6 +2137,8 @@ public class AprEndpoint extends AbstractEndpoint {
      * This class is the equivalent of the Worker, but will simply use in an
      * external Executor thread pool. This will also set the socket options
      * and do the handshake.
+     *
+     * This is called after an accept().
      */
     protected class SocketWithOptionsProcessor implements Runnable {
 
@@ -1792,7 +2156,7 @@ public class AprEndpoint extends AbstractEndpoint {
                 if (!deferAccept) {
                     if (setSocketOptions(socket.getSocket().longValue())) {
                         getPoller().add(socket.getSocket().longValue(),
-                                getSoTimeout(), Poller.FLAGS_READ);
+                                getSoTimeout(), true, false);
                     } else {
                         // Close socket and pool
                         destroySocket(socket.getSocket().longValue());
@@ -1808,7 +2172,7 @@ public class AprEndpoint extends AbstractEndpoint {
                     }
                     // Process the request from this socket
                     Handler.SocketState state = handler.process(socket,
-                            SocketStatus.OPEN);
+                            SocketStatus.OPEN_READ);
                     if (state == Handler.SocketState.CLOSED) {
                         // Close socket and pool
                         destroySocket(socket.getSocket().longValue());
@@ -1834,76 +2198,73 @@ public class AprEndpoint extends AbstractEndpoint {
      */
     protected class SocketProcessor implements Runnable {
 
-        protected SocketWrapper<Long> socket = null;
-        protected SocketStatus status = null;
+        private final SocketWrapper<Long> socket;
+        private final SocketStatus status;
 
         public SocketProcessor(SocketWrapper<Long> socket,
                 SocketStatus status) {
             this.socket = socket;
+            if (status == null) {
+                // Should never happen
+                throw new NullPointerException();
+            }
             this.status = status;
         }
 
         @Override
         public void run() {
-            synchronized (socket) {
-                // Process the request from this socket
-                SocketState state = SocketState.OPEN;
-                if (status == null) {
-                    state = handler.process(socket,SocketStatus.OPEN);
-                } else {
-                    state = handler.process(socket, status);
+
+            // Upgraded connections need to allow multiple threads to access the
+            // connection at the same time to enable blocking IO to be used when
+            // Servlet 3.1 NIO has been configured
+            if (socket.isUpgraded() && SocketStatus.OPEN_WRITE == status) {
+                synchronized (socket.getWriteThreadLock()) {
+                    doRun();
                 }
-                if (state == Handler.SocketState.CLOSED) {
-                    // Close socket and pool
-                    destroySocket(socket.getSocket().longValue());
-                    socket = null;
-                } else if (state == Handler.SocketState.LONG) {
-                    socket.access();
-                    if (socket.async) {
-                        waitingRequests.add(socket);
-                    }
-                } else if (state == Handler.SocketState.ASYNC_END) {
-                    socket.access();
-                    SocketProcessor proc = new SocketProcessor(socket, SocketStatus.OPEN);
-                    getExecutor().execute(proc);
+            } else {
+                synchronized (socket) {
+                    doRun();
                 }
             }
         }
-    }
 
+        private void doRun() {
+            // Process the request from this socket
+            if (socket.getSocket() == null) {
+                // Closed in another thread
+                return;
+            }
+            SocketState state = handler.process(socket, status);
+            if (state == Handler.SocketState.CLOSED) {
+                // Close socket and pool
+                destroySocket(socket.getSocket().longValue());
+                socket.socket = null;
+            } else if (state == Handler.SocketState.LONG) {
+                socket.access();
+                if (socket.async) {
+                    waitingRequests.add(socket);
+                }
+            } else if (state == Handler.SocketState.ASYNC_END) {
+                socket.access();
+                SocketProcessor proc = new SocketProcessor(socket,
+                        SocketStatus.OPEN_READ);
+                getExecutor().execute(proc);
+            }
+        }
+    }
 
-    // --------------------------------------- SocketEventProcessor Inner Class
 
+    private static class AprSocketWrapper extends SocketWrapper<Long> {
 
-    /**
-     * This class is the equivalent of the Worker, but will simply use in an
-     * external Executor thread pool.
-     */
-    protected class SocketEventProcessor implements Runnable {
+        // This field should only be used by Poller#run()
+        private int pollerFlags = 0;
 
-        protected SocketWrapper<Long> socket = null;
-        protected SocketStatus status = null;
-
-        public SocketEventProcessor(SocketWrapper<Long> socket,
-                SocketStatus status) {
-            this.socket = socket;
-            this.status = status;
-        }
-
-        @Override
-        public void run() {
-            synchronized (socket) {
-                // Process the request from this socket
-                Handler.SocketState state = handler.process(socket, status);
-                if (state == Handler.SocketState.CLOSED) {
-                    // Close socket and pool
-                    destroySocket(socket.getSocket().longValue());
-                    socket = null;
-                }
-            }
+        public AprSocketWrapper(Long socket) {
+            super(socket);
         }
     }
 
+
     private static class PrivilegedSetTccl implements PrivilegedAction<Void> {
 
         private ClassLoader cl;
diff --git a/java/org/apache/tomcat/util/net/JIoEndpoint.java b/java/org/apache/tomcat/util/net/JIoEndpoint.java
index 20670d5..058f6de 100644
--- a/java/org/apache/tomcat/util/net/JIoEndpoint.java
+++ b/java/org/apache/tomcat/util/net/JIoEndpoint.java
@@ -307,7 +307,7 @@ public class JIoEndpoint extends AbstractEndpoint {
 
                     if ((state != SocketState.CLOSED)) {
                         if (status == null) {
-                            state = handler.process(socket, SocketStatus.OPEN);
+                            state = handler.process(socket, SocketStatus.OPEN_READ);
                         } else {
                             state = handler.process(socket,status);
                         }
@@ -324,7 +324,8 @@ public class JIoEndpoint extends AbstractEndpoint {
                             // Ignore
                         }
                     } else if (state == SocketState.OPEN ||
-                            state == SocketState.UPGRADING  ||
+                            state == SocketState.UPGRADING ||
+                            state == SocketState.UPGRADING_TOMCAT  ||
                             state == SocketState.UPGRADED){
                         socket.setKeptAlive(true);
                         socket.access();
@@ -336,7 +337,7 @@ public class JIoEndpoint extends AbstractEndpoint {
                 } finally {
                     if (launch) {
                         try {
-                            getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN));
+                            getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN_READ));
                         } catch (RejectedExecutionException x) {
                             log.warn("Socket reprocessing request was rejected for:"+socket,x);
                             try {
diff --git a/java/org/apache/tomcat/util/net/NioEndpoint.java b/java/org/apache/tomcat/util/net/NioEndpoint.java
index e350109..f4ee3b9 100644
--- a/java/org/apache/tomcat/util/net/NioEndpoint.java
+++ b/java/org/apache/tomcat/util/net/NioEndpoint.java
@@ -905,7 +905,7 @@ public class NioEndpoint extends AbstractEndpoint {
                         final KeyAttachment att = (KeyAttachment) key.attachment();
                         if ( att!=null ) {
                             //handle callback flag
-                            if (att.getComet() && (interestOps & OP_CALLBACK) == OP_CALLBACK ) {
+                            if (att.isComet() && (interestOps & OP_CALLBACK) == OP_CALLBACK ) {
                                 att.setCometNotify(true);
                             } else {
                                 att.setCometNotify(false);
@@ -1065,7 +1065,7 @@ public class NioEndpoint extends AbstractEndpoint {
             try {
                 if ( key == null ) return;//nothing to do
                 KeyAttachment ka = (KeyAttachment) key.attachment();
-                if (ka != null && ka.getComet() && status != null) {
+                if (ka != null && ka.isComet() && status != null) {
                     //the comet event takes care of clean up
                     //processSocket(ka.getChannel(), status, dispatch);
                     ka.setComet(false);//to avoid a loop
@@ -1247,7 +1247,7 @@ public class NioEndpoint extends AbstractEndpoint {
                     if (sk.isReadable() || sk.isWritable() ) {
                         if ( attachment.getSendfileData() != null ) {
                             processSendfile(sk,attachment, false);
-                        } else if ( attachment.getComet() ) {
+                        } else if ( attachment.isComet() ) {
                             //check if thread is available
                             if ( isWorkerAvailable() ) {
                                 //set interest ops to 0 so we don't get multiple
@@ -1256,11 +1256,11 @@ public class NioEndpoint extends AbstractEndpoint {
                                 //read goes before write
                                 if (sk.isReadable()) {
                                     //read notification
-                                    if (!processSocket(channel, SocketStatus.OPEN, true))
+                                    if (!processSocket(channel, SocketStatus.OPEN_READ, true))
                                         processSocket(channel, SocketStatus.DISCONNECT, true);
                                 } else {
                                     //future placement of a WRITE notif
-                                    if (!processSocket(channel, SocketStatus.OPEN, true))
+                                    if (!processSocket(channel, SocketStatus.OPEN_WRITE, true))
                                         processSocket(channel, SocketStatus.DISCONNECT, true);
                                 }
                             } else {
@@ -1269,8 +1269,23 @@ public class NioEndpoint extends AbstractEndpoint {
                         } else {
                             //later on, improve latch behavior
                             if ( isWorkerAvailable() ) {
-                                unreg(sk, attachment,sk.readyOps());
-                                boolean close = (!processSocket(channel, null, true));
+
+                                boolean readAndWrite = sk.isReadable() && sk.isWritable();
+                                reg(sk, attachment, 0);
+                                if (attachment.isAsync() && readAndWrite) {
+                                    //remember the that we want to know about write too
+                                    attachment.interestOps(SelectionKey.OP_WRITE);
+                                }
+                                //read goes before write
+                                if (sk.isReadable()) {
+                                    //read notification
+                                    if (!processSocket(channel, SocketStatus.OPEN_READ, true))
+                                        close = true;
+                                } else {
+                                    //future placement of a WRITE notif
+                                    if (!processSocket(channel, SocketStatus.OPEN_WRITE, true))
+                                        close = true;
+                                }
                                 if (close) {
                                     cancelledKey(sk,SocketStatus.DISCONNECT,false);
                                 }
@@ -1319,7 +1334,9 @@ public class NioEndpoint extends AbstractEndpoint {
                         cancelledKey(sk,SocketStatus.ERROR,false);
                         return false;
                     }
-                    sd.fchannel = new FileInputStream(f).getChannel();
+                    @SuppressWarnings("resource") // Closed when channel is closed
+                    FileInputStream fis = new FileInputStream(f);
+                    sd.fchannel = fis.getChannel();
                 }
 
                 //configure output channel
@@ -1432,11 +1449,11 @@ public class NioEndpoint extends AbstractEndpoint {
                         cancelledKey(key, SocketStatus.ERROR,false); //we don't support any keys without attachments
                     } else if ( ka.getError() ) {
                         cancelledKey(key, SocketStatus.ERROR,true);//TODO this is not yet being used
-                    } else if (ka.getComet() && ka.getCometNotify() ) {
+                    } else if (ka.isComet() && ka.getCometNotify() ) {
                         ka.setCometNotify(false);
                         reg(key,ka,0);//avoid multiple calls, this gets reregistered after invocation
                         //if (!processSocket(ka.getChannel(), SocketStatus.OPEN_CALLBACK)) processSocket(ka.getChannel(), SocketStatus.DISCONNECT);
-                        if (!processSocket(ka.getChannel(), SocketStatus.OPEN, true)) processSocket(ka.getChannel(), SocketStatus.DISCONNECT, true);
+                        if (!processSocket(ka.getChannel(), SocketStatus.OPEN_READ, true)) processSocket(ka.getChannel(), SocketStatus.DISCONNECT, true);
                     } else if ((ka.interestOps()&SelectionKey.OP_READ) == SelectionKey.OP_READ ||
                               (ka.interestOps()&SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {
                         //only timeout sockets that we are waiting for a read from
@@ -1452,7 +1469,7 @@ public class NioEndpoint extends AbstractEndpoint {
                             ka.interestOps(0); //avoid duplicate timeout calls
                             cancelledKey(key, SocketStatus.TIMEOUT,true);
                         }
-                    } else if (ka.isAsync() || ka.getComet()) {
+                    } else if (ka.isAsync() || ka.isComet()) {
                         if (close) {
                             key.interestOps(0);
                             ka.interestOps(0); //avoid duplicate stop calls
@@ -1494,12 +1511,12 @@ public class NioEndpoint extends AbstractEndpoint {
         }
 
         public void reset(Poller poller, NioChannel channel, long soTimeout) {
-            this.socket = channel;
+            super.reset(channel, soTimeout);
+
+            cometNotify = false;
+            cometOps = SelectionKey.OP_READ;
+            interestOps = 0;
             this.poller = poller;
-            lastAccess = System.currentTimeMillis();
-            comet = false;
-            timeout = soTimeout;
-            error = false;
             lastRegistered = 0;
             sendfileData = null;
             if (readLatch != null) {
@@ -1511,6 +1528,7 @@ public class NioEndpoint extends AbstractEndpoint {
                 }
             }
             readLatch = null;
+            sendfileData = null;
             if (writeLatch != null) {
                 try {
                     for (int i = 0; i < (int) writeLatch.getCount(); i++) {
@@ -1520,11 +1538,7 @@ public class NioEndpoint extends AbstractEndpoint {
                 }
             }
             writeLatch = null;
-            cometNotify = false;
-            cometOps = SelectionKey.OP_READ;
-            sendfileData = null;
-            keepAliveLeft = 100;
-            async = false;
+            setWriteTimeout(soTimeout);
         }
 
         public void reset() {
@@ -1533,8 +1547,6 @@ public class NioEndpoint extends AbstractEndpoint {
 
         public Poller getPoller() { return poller;}
         public void setPoller(Poller poller){this.poller = poller;}
-        public void setComet(boolean comet) { this.comet = comet; }
-        public boolean getComet() { return comet; }
         public void setCometNotify(boolean notify) { this.cometNotify = notify; }
         public boolean getCometNotify() { return cometNotify; }
         /**
@@ -1593,13 +1605,18 @@ public class NioEndpoint extends AbstractEndpoint {
         public void setSendfileData(SendfileData sf) { this.sendfileData = sf;}
         public SendfileData getSendfileData() { return this.sendfileData;}
 
+        public void setWriteTimeout(long writeTimeout) {
+            this.writeTimeout = writeTimeout;
+        }
+        public long getWriteTimeout() {return this.writeTimeout;}
+
         protected boolean comet = false;
         protected int cometOps = SelectionKey.OP_READ;
         protected boolean cometNotify = false;
         protected CountDownLatch readLatch = null;
         protected CountDownLatch writeLatch = null;
         protected SendfileData sendfileData = null;
-
+        private long writeTimeout = -1;
     }
 
     // ------------------------------------------------ Application Buffer Handler
@@ -1664,102 +1681,136 @@ public class NioEndpoint extends AbstractEndpoint {
 
         @Override
         public void run() {
+            SelectionKey key = socket.getIOChannel().keyFor(
+                    socket.getPoller().getSelector());
+            KeyAttachment ka = null;
+
+            if (key != null) {
+                ka = (KeyAttachment)key.attachment();
+            }
+
+            // Upgraded connections need to allow multiple threads to access the
+            // connection at the same time to enable blocking IO to be used when
+            // NIO has been configured
+            if (ka != null && ka.isUpgraded() &&
+                    SocketStatus.OPEN_WRITE == status) {
+                synchronized (ka.getWriteThreadLock()) {
+                    doRun(key, ka);
+                }
+            } else {
+                synchronized (socket) {
+                    doRun(key, ka);
+                }
+            }
+        }
+
+        private void doRun(SelectionKey key, KeyAttachment ka) {
             boolean launch = false;
-            synchronized (socket) {
-                SelectionKey key = null;
-                try {
-                    key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
-                    int handshake = -1;
+            try {
+                int handshake = -1;
 
-                    try {
-                        if (key!=null) handshake = socket.handshake(key.isReadable(), key.isWritable());
-                    }catch ( IOException x ) {
-                        handshake = -1;
-                        if ( log.isDebugEnabled() ) log.debug("Error during SSL handshake",x);
-                    }catch ( CancelledKeyException ckx ) {
-                        handshake = -1;
-                    }
-                    if ( handshake == 0 ) {
-                        SocketState state = SocketState.OPEN;
-                        // Process the request from this socket
-                        if (status == null) {
-                            state = handler.process(
-                                    (KeyAttachment) key.attachment(),
-                                    SocketStatus.OPEN);
+                try {
+                    if (key != null) {
+                        // For STOP there is no point trying to handshake as the
+                        // Poller has been stopped.
+                        if (socket.isHandshakeComplete() ||
+                                status == SocketStatus.STOP) {
+                            handshake = 0;
                         } else {
-                            state = handler.process(
-                                    (KeyAttachment) key.attachment(),
-                                    status);
+                            handshake = socket.handshake(
+                                    key.isReadable(), key.isWritable());
+                            // The handshake process reads/writes from/to the
+                            // socket. status may therefore be OPEN_WRITE once
+                            // the handshake completes. However, the handshake
+                            // happens when the socket is opened so the status
+                            // must always be OPEN_READ after it completes. It
+                            // is OK to always set this as it is only used if
+                            // the handshake completes.
+                            status = SocketStatus.OPEN_READ;
                         }
-
-                        if (state == SocketState.CLOSED) {
-                            // Close socket and pool
-                            try {
-                                KeyAttachment ka = null;
-                                if (key!=null) {
-                                    ka = (KeyAttachment) key.attachment();
-                                    if (ka!=null) ka.setComet(false);
-                                    socket.getPoller().cancelledKey(key, SocketStatus.ERROR, false);
-                                }
+                    }
+                }catch ( IOException x ) {
+                    handshake = -1;
+                    if ( log.isDebugEnabled() ) log.debug("Error during SSL handshake",x);
+                }catch ( CancelledKeyException ckx ) {
+                    handshake = -1;
+                }
+                if ( handshake == 0 ) {
+                    SocketState state = SocketState.OPEN;
+                    // Process the request from this socket
+                    if (status == null) {
+                        state = handler.process(ka, SocketStatus.OPEN_READ);
+                    } else {
+                        state = handler.process(ka, status);
+                    }
+                    if (state == SocketState.CLOSED) {
+                        // Close socket and pool
+                        try {
+                            if (ka!=null) ka.setComet(false);
+                            socket.getPoller().cancelledKey(key, SocketStatus.ERROR, false);
+                            if (running && !paused) {
                                 nioChannels.offer(socket);
-                                socket = null;
-                                if ( ka!=null ) keyCache.offer(ka);
-                                ka = null;
-                            }catch ( Exception x ) {
-                                log.error("",x);
                             }
+                            socket = null;
+                            if (running && !paused && ka!=null) {
+                                keyCache.offer(ka);
+                            }
+                            ka = null;
+                        } catch ( Exception x ) {
+                            log.error("",x);
                         }
-                    } else if (handshake == -1 ) {
-                        KeyAttachment ka = null;
-                        if (key!=null) {
-                            ka = (KeyAttachment) key.attachment();
-                            socket.getPoller().cancelledKey(key, SocketStatus.DISCONNECT, false);
-                        }
-                        nioChannels.offer(socket);
-                        socket = null;
-                        if ( ka!=null ) keyCache.offer(ka);
-                        ka = null;
-                    } else {
-                        final SelectionKey fk = key;
-                        final int intops = handshake;
-                        final KeyAttachment ka = (KeyAttachment)fk.attachment();
-                        ka.getPoller().add(socket,intops);
                     }
-                }catch(CancelledKeyException cx) {
-                    socket.getPoller().cancelledKey(key,null,false);
-                } catch (OutOfMemoryError oom) {
+                } else if (handshake == -1 ) {
+                    if (key != null) {
+                        socket.getPoller().cancelledKey(key, SocketStatus.DISCONNECT, false);
+                    }
+                    nioChannels.offer(socket);
+                    socket = null;
+                    if ( ka!=null ) keyCache.offer(ka);
+                    ka = null;
+                } else {
+                    ka.getPoller().add(socket, handshake);
+                }
+            }catch(CancelledKeyException cx) {
+                socket.getPoller().cancelledKey(key,null,false);
+            } catch (OutOfMemoryError oom) {
+                try {
+                    oomParachuteData = null;
+                    log.error("", oom);
+                    if (socket != null) {
+                        socket.getPoller().cancelledKey(key,SocketStatus.ERROR, false);
+                    }
+                    releaseCaches();
+                }catch ( Throwable oomt ) {
                     try {
-                        oomParachuteData = null;
-                        log.error("", oom);
-                        if (socket != null) {
-                            socket.getPoller().cancelledKey(key,SocketStatus.ERROR, false);
-                        }
-                        releaseCaches();
-                    }catch ( Throwable oomt ) {
-                        try {
-                            System.err.println(oomParachuteMsg);
-                            oomt.printStackTrace();
-                        }catch (Throwable letsHopeWeDontGetHere){
-                            ExceptionUtils.handleThrowable(letsHopeWeDontGetHere);
-                        }
+                        System.err.println(oomParachuteMsg);
+                        oomt.printStackTrace();
+                    }catch (Throwable letsHopeWeDontGetHere){
+                        ExceptionUtils.handleThrowable(letsHopeWeDontGetHere);
                     }
-                }catch ( Throwable t ) {
-                    log.error("",t);
+                }
+            } catch (VirtualMachineError vme) {
+                ExceptionUtils.handleThrowable(vme);
+            }catch ( Throwable t ) {
+                log.error("",t);
+                if (socket != null) {
                     socket.getPoller().cancelledKey(key,SocketStatus.ERROR,false);
-                } finally {
-                    if (launch) {
-                        try {
-                            getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN));
-                        } catch (NullPointerException npe) {
-                            if (running) {
-                                log.error(sm.getString("endpoint.launch.fail"),
-                                        npe);
-                            }
+                }
+            } finally {
+                if (launch) {
+                    try {
+                        getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN_READ));
+                    } catch (NullPointerException npe) {
+                        if (running) {
+                            log.error(sm.getString("endpoint.launch.fail"),
+                                    npe);
                         }
                     }
-                    socket = null;
-                    status = null;
-                    //return to cache
+                }
+                socket = null;
+                status = null;
+                //return to cache
+                if (running && !paused) {
                     processorCache.offer(this);
                 }
             }
diff --git a/java/org/apache/tomcat/util/net/SocketStatus.java b/java/org/apache/tomcat/util/net/SocketStatus.java
index bf53c2b..38d18f7 100644
--- a/java/org/apache/tomcat/util/net/SocketStatus.java
+++ b/java/org/apache/tomcat/util/net/SocketStatus.java
@@ -23,5 +23,5 @@ package org.apache.tomcat.util.net;
  * @author remm
  */
 public enum SocketStatus {
-    OPEN, STOP, TIMEOUT, DISCONNECT, ERROR
+    OPEN_READ, OPEN_WRITE, STOP, TIMEOUT, DISCONNECT, ERROR
 }
diff --git a/java/org/apache/tomcat/util/net/SocketWrapper.java b/java/org/apache/tomcat/util/net/SocketWrapper.java
index b194670..964e7f3 100644
--- a/java/org/apache/tomcat/util/net/SocketWrapper.java
+++ b/java/org/apache/tomcat/util/net/SocketWrapper.java
@@ -16,6 +16,10 @@
  */
 package org.apache.tomcat.util.net;
 
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
+
 public class SocketWrapper<E> {
 
     protected volatile E socket;
@@ -25,19 +29,46 @@ public class SocketWrapper<E> {
     protected boolean error = false;
     protected long lastRegistered = 0;
     protected volatile int keepAliveLeft = 100;
+    private boolean comet = false;
     protected boolean async = false;
     protected boolean keptAlive = false;
+    private boolean upgraded = false;
+
+    /*
+     * Used if block/non-blocking is set at the socket level. The client is
+     * responsible for the thread-safe use of this field via the locks provided.
+     */
+    private volatile boolean blockingStatus = true;
+    private final Lock blockingStatusReadLock;
+    private final WriteLock blockingStatusWriteLock;
+
+    /*
+     * In normal servlet processing only one thread is allowed to access the
+     * socket at a time. That is controlled by a lock on the socket for both
+     * read and writes). When HTTP upgrade is used, one read thread and one
+     * write thread are allowed to access the socket concurrently. In this case
+     * the lock on the socket is used for reads and the lock below is used for
+     * writes.
+     */
+    private final Object writeThreadLock = new Object();
 
     public SocketWrapper(E socket) {
         this.socket = socket;
+        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+        this.blockingStatusReadLock = lock.readLock();
+        this.blockingStatusWriteLock =lock.writeLock();
     }
 
     public E getSocket() {
         return socket;
     }
 
+    public boolean isComet() { return comet; }
+    public void setComet(boolean comet) { this.comet = comet; }
     public boolean isAsync() { return async; }
     public void setAsync(boolean async) { this.async = async; }
+    public boolean isUpgraded() { return upgraded; }
+    public void setUpgraded(boolean upgraded) { this.upgraded = upgraded; }
     public long getLastAccess() { return lastAccess; }
     public void access() { access(System.currentTimeMillis()); }
     public void access(long access) { lastAccess = access; }
@@ -49,4 +80,25 @@ public class SocketWrapper<E> {
     public int decrementKeepAlive() { return (--keepAliveLeft);}
     public boolean isKeptAlive() {return keptAlive;}
     public void setKeptAlive(boolean keptAlive) {this.keptAlive = keptAlive;}
+    public boolean getBlockingStatus() { return blockingStatus; }
+    public void setBlockingStatus(boolean blockingStatus) {
+        this.blockingStatus = blockingStatus;
+    }
+    public Lock getBlockingStatusReadLock() { return blockingStatusReadLock; }
+    public WriteLock getBlockingStatusWriteLock() {
+        return blockingStatusWriteLock;
+    }
+    public Object getWriteThreadLock() { return writeThreadLock; }
+
+    public void reset(E socket, long timeout) {
+        async = false;
+        blockingStatus = true;
+        comet = false;
+        error = false;
+        keepAliveLeft = 100;
+        lastAccess = System.currentTimeMillis();
+        this.socket = socket;
+        this.timeout = timeout;
+        upgraded = false;
+    }
 }
diff --git a/java/org/apache/tomcat/util/net/res/LocalStrings.properties b/java/org/apache/tomcat/util/net/res/LocalStrings.properties
index 869f56b..85eec46 100644
--- a/java/org/apache/tomcat/util/net/res/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/net/res/LocalStrings.properties
@@ -14,19 +14,25 @@
 # limitations under the License.
 
 # net resources
-endpoint.err.fatal=Endpoint {0} shutdown due to exception: {1}
-endpoint.err.nonfatal=Endpoint {0} ignored exception: {1}
-endpoint.warn.reinit=Reinitializing ServerSocket
-endpoint.warn.restart=Restarting endpoint
-endpoint.warn.security=Endpoint {0} security exception: {1}
-endpoint.err.socket=Socket error caused by remote host {0}
+endpoint.err.close=Caught exception trying to close socket
 endpoint.err.handshake=Handshake failed
 endpoint.err.unexpected=Unexpected error processing socket
-endpoint.warn.nullSocket=Null socket returned by accept
+endpoint.warn.noExector=Failed to process socket [{0}] in state [{1}] because the executor had already been shutdown
+endpoint.warn.noDisableCompression='Disable compression' option is not supported by the SSL library {0}
+endpoint.warn.noHonorCipherOrder='Honor cipher order' option is not supported by the SSL library {0}
+endpoint.warn.noInsecureReneg=Secure re-negotiation is not supported by the SSL library {0}
+endpoint.warn.unlockAcceptorFailed=Acceptor thread [{0}] failed to unlock. Forcing hard socket shutdown.
+endpoint.warn.executorShutdown=The executor associated with thread pool [{0}] has not fully shutdown. Some application threads may still be running.
+endpoint.debug.channelCloseFail=Failed to close channel
+endpoint.debug.destroySocket=socket [{0}], doIt [{1}]
+endpoint.debug.pollerAdd=socket [{0}], timeout [{1}], flags [{2}]
+endpoint.debug.pollerAddDo=socket [{0}]
+endpoint.debug.pollerProcess=Processing socket [{0}] for event(s) [{1}]
+endpoint.debug.socket=socket [{0}]
+endpoint.debug.socketCloseFail=Failed to close socket
+endpoint.debug.socketTimeout=Timing out [{0}
 endpoint.debug.unlock=Caught exception trying to unlock accept on port {0}
 endpoint.err.close=Caught exception trying to close socket
-endpoint.noProcessor=No Processors - worker thread dead!
-endpoint.info.maxThreads=Maximum number of threads ({0}) created for connector with address {1} and port {2}
 
 endpoint.init.bind=Socket bind failed: [{0}] {1}
 endpoint.init.listen=Socket listen failed: [{0}] {1}
@@ -40,15 +46,11 @@ endpoint.poll.error=Unexpected poller error
 endpoint.process.fail=Error allocating socket processor
 endpoint.sendfile.error=Unexpected sendfile error
 endpoint.sendfile.addfail=Sendfile failure: [{0}] {1}
-endpoint.sendfile.nosupport=Disabling sendfile, since either the APR version or the system doesn't support it
-endpoint.warn.noDisableCompression='Disable compression' option is not supported by the SSL library {0}
-endpoint.warn.noInsecureReneg=Secure re-negotiation is not supported by the SSL library {0}
-endpoint.warn.noHonorCipherOrder='Honor cipher order' option is not supported by the SSL library {0}
-endpoint.warn.unlockAcceptorFailed=Acceptor thread [{0}] failed to unlock. Forcing hard socket shutdown.
-endpoint.warn.noHonorCipherOrder='Honor cipher order' option is not supported by the SSL library {0}
-endpoint.debug.channelCloseFail=Failed to close channel
-endpoint.debug.socketCloseFail=Failed to close socket
+endpoint.timeout.err=Error processing socket timeout
 endpoint.apr.noSslCertFile=Connector attribute SSLCertificateFile must be defined when using SSL with APR
+endpoint.apr.pollAddInvalid=Invalid attempted to add a socket [{0}] to the poller
+endpoint.apr.pollError=Poller failed with error [{0}] : [{1}]
+endpoint.apr.pollUnknownEvent=A socket was returned from the poller with an unrecognized event [{0}]
 endpoint.apr.invalidSslProtocol=An invalid value [{0}] was provided for the SSLProtocol attribute
 endpoint.nio.selectorCloseFail=Failed to close selector when closing the poller
 endpoint.warn.noExector=Failed to process socket [{0}] in state [{1}] because the executor had already been shutdown
diff --git a/java/org/apache/tomcat/util/res/StringManager.java b/java/org/apache/tomcat/util/res/StringManager.java
index d1b0448..86fea77 100644
--- a/java/org/apache/tomcat/util/res/StringManager.java
+++ b/java/org/apache/tomcat/util/res/StringManager.java
@@ -18,7 +18,9 @@
 package org.apache.tomcat.util.res;
 
 import java.text.MessageFormat;
+import java.util.Enumeration;
 import java.util.Hashtable;
+import java.util.LinkedHashMap;
 import java.util.Locale;
 import java.util.Map;
 import java.util.MissingResourceException;
@@ -43,7 +45,7 @@ import java.util.ResourceBundle;
  * <p>Please see the documentation for java.util.ResourceBundle for
  * more information.
  *
- * @version $Id: StringManager.java 1200166 2011-11-10 05:50:36Z kkolinko $
+ * @version $Id: StringManager.java 1521871 2013-09-11 14:27:27Z markt $
  *
  * @author James Duncan Davidson [duncan at eng.sun.com]
  * @author James Todd [gonzo at eng.sun.com]
@@ -53,6 +55,8 @@ import java.util.ResourceBundle;
 
 public class StringManager {
 
+    private static int LOCALE_CACHE_SIZE = 10;
+
     /**
      * The ResourceBundle for this StringManager.
      */
@@ -88,7 +92,12 @@ public class StringManager {
         bundle = bnd;
         // Get the actual locale, which may be different from the requested one
         if (bundle != null) {
-            this.locale = bundle.getLocale();
+            Locale bundleLocale = bundle.getLocale();
+            if (bundleLocale.equals(Locale.ROOT)) {
+                this.locale = Locale.ENGLISH;
+            } else {
+                this.locale = bundleLocale;
+            }
         } else {
             this.locale = null;
         }
@@ -192,7 +201,25 @@ public class StringManager {
 
         Map<Locale,StringManager> map = managers.get(packageName);
         if (map == null) {
-            map = new Hashtable<Locale, StringManager>();
+            /*
+             * Don't want the HashMap to be expanded beyond LOCALE_CACHE_SIZE.
+             * Expansion occurs when size() exceeds capacity. Therefore keep
+             * size at or below capacity.
+             * removeEldestEntry() executes after insertion therefore the test
+             * for removal needs to use one less than the maximum desired size
+             *
+             */
+            map = new LinkedHashMap<Locale,StringManager>(LOCALE_CACHE_SIZE, 1, true) {
+                private static final long serialVersionUID = 1L;
+                @Override
+                protected boolean removeEldestEntry(
+                        Map.Entry<Locale,StringManager> eldest) {
+                    if (size() > (LOCALE_CACHE_SIZE - 1)) {
+                        return true;
+                    }
+                    return false;
+                }
+            };
             managers.put(packageName, map);
         }
 
@@ -203,4 +230,25 @@ public class StringManager {
         }
         return mgr;
     }
+
+    /**
+     * Retrieve the StringManager for a list of Locales. The first StringManager
+     * found will be returned.
+     *
+     * @param requestedLocales the list of Locales
+     *
+     * @return the found StringManager or the default StringManager
+     */
+    public static StringManager getManager(String packageName,
+            Enumeration<Locale> requestedLocales) {
+        while (requestedLocales.hasMoreElements()) {
+            Locale locale = requestedLocales.nextElement();
+            StringManager result = getManager(packageName, locale);
+            if (result.getLocale().equals(locale)) {
+                return result;
+            }
+        }
+        // Return the default
+        return getManager(packageName);
+    }
 }
diff --git a/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java b/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java
new file mode 100644
index 0000000..d970d75
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java
@@ -0,0 +1,47 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.nio.ByteBuffer;
+import java.nio.channels.CompletionHandler;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.SSLException;
+
+/**
+ * This is a wrapper for a {@link java.nio.channels.AsynchronousSocketChannel}
+ * that limits the methods available thereby simplifying the process of
+ * implementing SSL/TLS support since there are fewer methods to intercept.
+ */
+public interface AsyncChannelWrapper {
+
+    Future<Integer> read(ByteBuffer dst);
+
+    <B,A extends B> void read(ByteBuffer dst, A attachment,
+            CompletionHandler<Integer,B> handler);
+
+    Future<Integer> write(ByteBuffer src);
+
+    <B,A extends B> void write(ByteBuffer[] srcs, int offset, int length,
+            long timeout, TimeUnit unit, A attachment,
+            CompletionHandler<Long,B> handler);
+
+    void close();
+
+    Future<Void> handshake() throws SSLException;
+}
diff --git a/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java b/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java
new file mode 100644
index 0000000..d86c22c
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java
@@ -0,0 +1,112 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.channels.CompletionHandler;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Generally, just passes calls straight to the wrapped
+ * {@link AsynchronousSocketChannel}. In some cases exceptions may be swallowed
+ * to save them being swallowed by the calling code.
+ */
+public class AsyncChannelWrapperNonSecure implements AsyncChannelWrapper {
+
+    private static final Future<Void> NOOP_FUTURE = new NoOpFuture();
+
+    private final AsynchronousSocketChannel socketChannel;
+
+    public AsyncChannelWrapperNonSecure(
+            AsynchronousSocketChannel socketChannel) {
+        this.socketChannel = socketChannel;
+    }
+
+    @Override
+    public Future<Integer> read(ByteBuffer dst) {
+        return socketChannel.read(dst);
+    }
+
+    @Override
+    public <B,A extends B> void read(ByteBuffer dst, A attachment,
+            CompletionHandler<Integer,B> handler) {
+        socketChannel.read(dst, attachment, handler);
+    }
+
+    @Override
+    public Future<Integer> write(ByteBuffer src) {
+        return socketChannel.write(src);
+    }
+
+    @Override
+    public <B,A extends B> void write(ByteBuffer[] srcs, int offset, int length,
+            long timeout, TimeUnit unit, A attachment,
+            CompletionHandler<Long,B> handler) {
+        socketChannel.write(
+                srcs, offset, length, timeout, unit, attachment, handler);
+    }
+
+    @Override
+    public void close() {
+        try {
+            socketChannel.close();
+        } catch (IOException e) {
+            // Ignore
+        }
+    }
+
+    @Override
+    public Future<Void> handshake() {
+        return NOOP_FUTURE;
+    }
+
+
+    private static final class NoOpFuture implements Future<Void> {
+
+        @Override
+        public boolean cancel(boolean mayInterruptIfRunning) {
+            return false;
+        }
+
+        @Override
+        public boolean isCancelled() {
+            return false;
+        }
+
+        @Override
+        public boolean isDone() {
+            return true;
+        }
+
+        @Override
+        public Void get() throws InterruptedException, ExecutionException {
+            return null;
+        }
+
+        @Override
+        public Void get(long timeout, TimeUnit unit)
+                throws InterruptedException, ExecutionException,
+                TimeoutException {
+            return null;
+        }
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java b/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java
new file mode 100644
index 0000000..e35d60d
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java
@@ -0,0 +1,558 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.channels.CompletionHandler;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+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.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Wraps the {@link AsynchronousSocketChannel} with SSL/TLS. This needs a lot
+ * more testing before it can be considered robust.
+ */
+public class AsyncChannelWrapperSecure implements AsyncChannelWrapper {
+
+    private static final Log log =
+            LogFactory.getLog(AsyncChannelWrapperSecure.class);
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+
+    private static final ByteBuffer DUMMY = ByteBuffer.allocate(8192);
+    private final AsynchronousSocketChannel socketChannel;
+    private final SSLEngine sslEngine;
+    private final ByteBuffer socketReadBuffer;
+    private final ByteBuffer socketWriteBuffer;
+    // One thread for read, one for write
+    private final ExecutorService executor = Executors.newFixedThreadPool(2);
+    private AtomicBoolean writing = new AtomicBoolean(false);
+    private AtomicBoolean reading = new AtomicBoolean(false);
+
+    public AsyncChannelWrapperSecure(AsynchronousSocketChannel socketChannel,
+            SSLEngine sslEngine) {
+        this.socketChannel = socketChannel;
+        this.sslEngine = sslEngine;
+
+        int socketBufferSize = sslEngine.getSession().getPacketBufferSize();
+        socketReadBuffer = ByteBuffer.allocateDirect(socketBufferSize);
+        socketWriteBuffer = ByteBuffer.allocateDirect(socketBufferSize);
+    }
+
+    @Override
+    public Future<Integer> read(ByteBuffer dst) {
+        WrapperFuture<Integer,Void> future = new WrapperFuture<Integer, Void>();
+
+        if (!reading.compareAndSet(false, true)) {
+            throw new IllegalStateException(sm.getString(
+                    "asyncChannelWrapperSecure.concurrentRead"));
+        }
+
+        ReadTask readTask = new ReadTask(dst, future);
+
+        executor.execute(readTask);
+
+        return future;
+    }
+
+    @Override
+    public <B,A extends B> void read(ByteBuffer dst, A attachment,
+            CompletionHandler<Integer,B> handler) {
+
+        WrapperFuture<Integer,B> future =
+                new WrapperFuture<Integer, B>(handler, attachment);
+
+        if (!reading.compareAndSet(false, true)) {
+            throw new IllegalStateException(sm.getString(
+                    "asyncChannelWrapperSecure.concurrentRead"));
+        }
+
+        ReadTask readTask = new ReadTask(dst, future);
+
+        executor.execute(readTask);
+    }
+
+    @Override
+    public Future<Integer> write(ByteBuffer src) {
+
+        WrapperFuture<Long,Void> inner = new WrapperFuture<Long, Void>();
+
+        if (!writing.compareAndSet(false, true)) {
+            throw new IllegalStateException(sm.getString(
+                    "asyncChannelWrapperSecure.concurrentWrite"));
+        }
+
+        WriteTask writeTask =
+                new WriteTask(new ByteBuffer[] {src}, 0, 1, inner);
+
+        executor.execute(writeTask);
+
+        Future<Integer> future = new LongToIntegerFuture(inner);
+        return future;
+    }
+
+    @Override
+    public <B,A extends B> void write(ByteBuffer[] srcs, int offset, int length,
+            long timeout, TimeUnit unit, A attachment,
+            CompletionHandler<Long,B> handler) {
+
+        WrapperFuture<Long,B> future =
+                new WrapperFuture<Long, B>(handler, attachment);
+
+        if (!writing.compareAndSet(false, true)) {
+            throw new IllegalStateException(sm.getString(
+                    "asyncChannelWrapperSecure.concurrentWrite"));
+        }
+
+        WriteTask writeTask = new WriteTask(srcs, offset, length, future);
+
+        executor.execute(writeTask);
+    }
+
+    @Override
+    public void close() {
+        try {
+            socketChannel.close();
+        } catch (IOException e) {
+            log.info(sm.getString("asyncChannelWrapperSecure.closeFail"));
+        }
+    }
+
+    @Override
+    public Future<Void> handshake() throws SSLException {
+
+        WrapperFuture<Void,Void> wFuture = new WrapperFuture<Void, Void>();
+
+        Thread t = new WebSocketSslHandshakeThread(wFuture);
+        t.start();
+
+        return wFuture;
+    }
+
+
+    private class WriteTask implements Runnable {
+
+        private final ByteBuffer[] srcs;
+        private final int offset;
+        private final int length;
+        private final WrapperFuture<Long,?> future;
+
+        public WriteTask(ByteBuffer[] srcs, int offset, int length,
+                WrapperFuture<Long,?> future) {
+            this.srcs = srcs;
+            this.future = future;
+            this.offset = offset;
+            this.length = length;
+        }
+
+        @Override
+        public void run() {
+            long written = 0;
+
+            try {
+                for (int i = offset; i < offset + length; i++) {
+                    ByteBuffer src = srcs[i];
+                    while (src.hasRemaining()) {
+                        socketWriteBuffer.clear();
+
+                        // Encrypt the data
+                        SSLEngineResult r = sslEngine.wrap(src, socketWriteBuffer);
+                        written += r.bytesConsumed();
+                        Status s = r.getStatus();
+
+                        if (s == Status.OK || s == Status.BUFFER_OVERFLOW) {
+                            // Need to write out the bytes and may need to read from
+                            // the source again to empty it
+                        } else {
+                            // Status.BUFFER_UNDERFLOW - only happens on unwrap
+                            // Status.CLOSED - unexpected
+                            throw new IllegalStateException(sm.getString(
+                                    "asyncChannelWrapperSecure.statusWrap"));
+                        }
+
+                        // Check for tasks
+                        if (r.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
+                            Runnable runnable = sslEngine.getDelegatedTask();
+                            while (runnable != null) {
+                                runnable.run();
+                                runnable = sslEngine.getDelegatedTask();
+                            }
+                        }
+
+                        socketWriteBuffer.flip();
+
+                        // Do the write
+                        int toWrite = r.bytesProduced();
+                        while (toWrite > 0) {
+                            Future<Integer> f =
+                                    socketChannel.write(socketWriteBuffer);
+                            Integer socketWrite = f.get();
+                            toWrite -= socketWrite.intValue();
+                        }
+                    }
+                }
+
+
+                if (writing.compareAndSet(true, false)) {
+                    future.complete(Long.valueOf(written));
+                } else {
+                    future.fail(new IllegalStateException(sm.getString(
+                            "asyncChannelWrapperSecure.wrongStateWrite")));
+                }
+            } catch (Exception e) {
+                future.fail(e);
+            }
+        }
+    }
+
+
+    private class ReadTask implements Runnable {
+
+        private final ByteBuffer dest;
+        private final WrapperFuture<Integer,?> future;
+
+        public ReadTask(ByteBuffer dest, WrapperFuture<Integer,?> future) {
+            this.dest = dest;
+            this.future = future;
+        }
+
+        @Override
+        public void run() {
+            int read = 0;
+
+            boolean forceRead = false;
+
+            try {
+                while (read == 0) {
+                    socketReadBuffer.compact();
+
+                    if (forceRead) {
+                        Future<Integer> f =
+                                socketChannel.read(socketReadBuffer);
+                        Integer socketRead = f.get();
+                        if (socketRead.intValue() == -1) {
+                            throw new EOFException(sm.getString(
+                                    "asyncChannelWrapperSecure.eof"));
+                        }
+                    }
+
+                    socketReadBuffer.flip();
+
+                    if (socketReadBuffer.hasRemaining()) {
+                        // Decrypt the data in the buffer
+                        SSLEngineResult r =
+                                sslEngine.unwrap(socketReadBuffer, dest);
+                        read += r.bytesProduced();
+                        Status s = r.getStatus();
+
+                        if (s == Status.OK) {
+                            // Bytes available for reading and there may be
+                            // sufficient data in the socketReadBuffer to
+                            // support further reads without reading from the
+                            // socket
+                        } else if (s == Status.BUFFER_UNDERFLOW) {
+                            // There is partial data in the socketReadBuffer
+                            if (read == 0) {
+                                // Need more data before the partial data can be
+                                // processed and some output generated
+                                forceRead = true;
+                            }
+                            // else return the data we have and deal with the
+                            // partial data on the next read
+                        } else if (s == Status.BUFFER_OVERFLOW) {
+                            // Not enough space in the destination buffer to
+                            // store all of the data. We could use a bytes read
+                            // value of -bufferSizeRequired to signal the new
+                            // buffer size required but an explicit exception is
+                            // clearer.
+                            if (reading.compareAndSet(true, false)) {
+                                throw new ReadBufferOverflowException(sslEngine.
+                                        getSession().getApplicationBufferSize());
+                            } else {
+                                future.fail(new IllegalStateException(sm.getString(
+                                        "asyncChannelWrapperSecure.wrongStateRead")));
+                            }
+                        } else {
+                            // Status.CLOSED - unexpected
+                            throw new IllegalStateException(sm.getString(
+                                    "asyncChannelWrapperSecure.statusUnwrap"));
+                        }
+
+                        // Check for tasks
+                        if (r.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
+                            Runnable runnable = sslEngine.getDelegatedTask();
+                            while (runnable != null) {
+                                runnable.run();
+                                runnable = sslEngine.getDelegatedTask();
+                            }
+                        }
+                    } else {
+                        forceRead = true;
+                    }
+                }
+
+
+                if (reading.compareAndSet(true, false)) {
+                    future.complete(Integer.valueOf(read));
+                } else {
+                    future.fail(new IllegalStateException(sm.getString(
+                            "asyncChannelWrapperSecure.wrongStateRead")));
+                }
+            } catch (Exception e) {
+                future.fail(e);
+            }
+        }
+    }
+
+
+    private class WebSocketSslHandshakeThread extends Thread {
+
+        private final WrapperFuture<Void,Void> hFuture;
+
+        private HandshakeStatus handshakeStatus;
+        private Status resultStatus;
+
+        public WebSocketSslHandshakeThread(WrapperFuture<Void,Void> hFuture) {
+            this.hFuture = hFuture;
+        }
+
+        @Override
+        public void run() {
+            try {
+                sslEngine.beginHandshake();
+                // So the first compact does the right thing
+                socketReadBuffer.position(socketReadBuffer.limit());
+
+                handshakeStatus = sslEngine.getHandshakeStatus();
+                resultStatus = Status.OK;
+
+                boolean handshaking = true;
+
+                while(handshaking) {
+                    switch (handshakeStatus) {
+                        case NEED_WRAP: {
+                            socketWriteBuffer.clear();
+                            SSLEngineResult r =
+                                    sslEngine.wrap(DUMMY, socketWriteBuffer);
+                            checkResult(r, true);
+                            socketWriteBuffer.flip();
+                            Future<Integer> fWrite =
+                                    socketChannel.write(socketWriteBuffer);
+                            fWrite.get();
+                            break;
+                        }
+                        case NEED_UNWRAP: {
+                            socketReadBuffer.compact();
+                            if (socketReadBuffer.position() == 0 ||
+                                    resultStatus == Status.BUFFER_UNDERFLOW) {
+                                Future<Integer> fRead =
+                                        socketChannel.read(socketReadBuffer);
+                                fRead.get();
+                            }
+                            socketReadBuffer.flip();
+                            SSLEngineResult r =
+                                    sslEngine.unwrap(socketReadBuffer, DUMMY);
+                            checkResult(r, false);
+                            break;
+                        }
+                        case NEED_TASK: {
+                            Runnable r = null;
+                            while ((r = sslEngine.getDelegatedTask()) != null) {
+                                r.run();
+                            }
+                            handshakeStatus = sslEngine.getHandshakeStatus();
+                            break;
+                        }
+                        case FINISHED: {
+                            handshaking = false;
+                            break;
+                        }
+                        default: {
+                            throw new SSLException("TODO");
+                        }
+                    }
+                }
+            } catch (SSLException e) {
+                hFuture.fail(e);
+            } catch (InterruptedException e) {
+                hFuture.fail(e);
+            } catch (ExecutionException e) {
+                hFuture.fail(e);
+            }
+
+            hFuture.complete(null);
+        }
+
+        private void checkResult(SSLEngineResult result, boolean wrap)
+                throws SSLException {
+
+            handshakeStatus = result.getHandshakeStatus();
+            resultStatus = result.getStatus();
+
+            if (resultStatus != Status.OK &&
+                    (wrap || resultStatus != Status.BUFFER_UNDERFLOW)) {
+                throw new SSLException("TODO");
+            }
+            if (wrap && result.bytesConsumed() != 0) {
+                throw new SSLException("TODO");
+            }
+            if (!wrap && result.bytesProduced() != 0) {
+                throw new SSLException("TODO");
+            }
+        }
+    }
+
+
+    private static class WrapperFuture<T,A> implements Future<T> {
+
+        private final CompletionHandler<T,A> handler;
+        private final A attachment;
+
+        private volatile T result = null;
+        private volatile Throwable throwable = null;
+        private CountDownLatch completionLatch = new CountDownLatch(1);
+
+        public WrapperFuture() {
+            this(null, null);
+        }
+
+        public WrapperFuture(CompletionHandler<T,A> handler, A attachment) {
+            this.handler = handler;
+            this.attachment = attachment;
+        }
+
+        public void complete(T result) {
+            this.result = result;
+            completionLatch.countDown();
+            if (handler != null) {
+                handler.completed(result, attachment);
+            }
+        }
+
+        public void fail(Throwable t) {
+            throwable = t;
+            completionLatch.countDown();
+            if (handler != null) {
+                handler.failed(throwable, attachment);
+            }
+        }
+
+        @Override
+        public final boolean cancel(boolean mayInterruptIfRunning) {
+            // Could support cancellation by closing the connection
+            return false;
+        }
+
+        @Override
+        public final boolean isCancelled() {
+            // Could support cancellation by closing the connection
+            return false;
+        }
+
+        @Override
+        public final boolean isDone() {
+            return completionLatch.getCount() > 0;
+        }
+
+        @Override
+        public T get() throws InterruptedException, ExecutionException {
+            completionLatch.await();
+            if (throwable != null) {
+                throw new ExecutionException(throwable);
+            }
+            return result;
+        }
+
+        @Override
+        public T get(long timeout, TimeUnit unit)
+                throws InterruptedException, ExecutionException,
+                TimeoutException {
+            boolean latchResult = completionLatch.await(timeout, unit);
+            if (latchResult == false) {
+                throw new TimeoutException();
+            }
+            if (throwable != null) {
+                throw new ExecutionException(throwable);
+            }
+            return result;
+        }
+    }
+
+    private static final class LongToIntegerFuture implements Future<Integer> {
+
+        private final Future<Long> wrapped;
+
+        public LongToIntegerFuture(Future<Long> wrapped) {
+            this.wrapped = wrapped;
+        }
+
+        @Override
+        public boolean cancel(boolean mayInterruptIfRunning) {
+            return wrapped.cancel(mayInterruptIfRunning);
+        }
+
+        @Override
+        public boolean isCancelled() {
+            return wrapped.isCancelled();
+        }
+
+        @Override
+        public boolean isDone() {
+            return wrapped.isDone();
+        }
+
+        @Override
+        public Integer get() throws InterruptedException, ExecutionException {
+            Long result = wrapped.get();
+            if (result.longValue() > Integer.MAX_VALUE) {
+                throw new ExecutionException(sm.getString(
+                        "asyncChannelWrapperSecure.tooBig", result), null);
+            }
+            return new Integer(result.intValue());
+        }
+
+        @Override
+        public Integer get(long timeout, TimeUnit unit)
+                throws InterruptedException, ExecutionException,
+                TimeoutException {
+            Long result = wrapped.get(timeout, unit);
+            if (result.longValue() > Integer.MAX_VALUE) {
+                throw new ExecutionException(sm.getString(
+                        "asyncChannelWrapperSecure.tooBig", result), null);
+            }
+            return new Integer(result.intValue());
+        }
+    }
+}
diff --git a/java/org/apache/tomcat/util/net/SocketStatus.java b/java/org/apache/tomcat/websocket/BackgroundProcess.java
similarity index 82%
copy from java/org/apache/tomcat/util/net/SocketStatus.java
copy to java/org/apache/tomcat/websocket/BackgroundProcess.java
index bf53c2b..510df57 100644
--- a/java/org/apache/tomcat/util/net/SocketStatus.java
+++ b/java/org/apache/tomcat/websocket/BackgroundProcess.java
@@ -14,14 +14,13 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
+package org.apache.tomcat.websocket;
 
-package org.apache.tomcat.util.net;
+public interface BackgroundProcess {
 
-/**
- * Someone, please change the enum name.
- *
- * @author remm
- */
-public enum SocketStatus {
-    OPEN, STOP, TIMEOUT, DISCONNECT, ERROR
+    void backgroundProcess();
+
+    void setProcessPeriod(int period);
+
+    int getProcessPeriod();
 }
diff --git a/java/org/apache/tomcat/websocket/BackgroundProcessManager.java b/java/org/apache/tomcat/websocket/BackgroundProcessManager.java
new file mode 100644
index 0000000..6733b14
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/BackgroundProcessManager.java
@@ -0,0 +1,128 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Provides a background processing mechanism that triggers roughly once a
+ * second. The class maintains a thread that only runs when there is at least
+ * one instance of {@link BackgroundProcess} registered.
+ */
+public class BackgroundProcessManager {
+
+    private static final Log log =
+            LogFactory.getLog(BackgroundProcessManager.class);
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+    private static final BackgroundProcessManager instance;
+
+
+    static {
+        instance = new BackgroundProcessManager();
+    }
+
+
+    public static BackgroundProcessManager getInstance() {
+        return instance;
+    }
+
+    private final Set<BackgroundProcess> processes = new HashSet<BackgroundProcess>();
+    private final Object processesLock = new Object();
+    private WsBackgroundThread wsBackgroundThread = null;
+
+    private BackgroundProcessManager() {
+        // Hide default constructor
+    }
+
+
+    public void register(BackgroundProcess process) {
+        synchronized (processesLock) {
+            if (processes.size() == 0) {
+                wsBackgroundThread = new WsBackgroundThread(this);
+                wsBackgroundThread.setContextClassLoader(
+                        this.getClass().getClassLoader());
+                wsBackgroundThread.setDaemon(true);
+                wsBackgroundThread.start();
+            }
+            processes.add(process);
+        }
+    }
+
+
+    public void unregister(BackgroundProcess process) {
+        synchronized (processesLock) {
+            processes.remove(process);
+            if (wsBackgroundThread != null && processes.size() == 0) {
+                wsBackgroundThread.halt();
+                wsBackgroundThread = null;
+            }
+        }
+    }
+
+
+    private void process() {
+        Set<BackgroundProcess> currentProcesses = new HashSet<BackgroundProcess>();
+        synchronized (processesLock) {
+            currentProcesses.addAll(processes);
+        }
+        for (BackgroundProcess process : currentProcesses) {
+            try {
+                process.backgroundProcess();
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.error(sm.getString(
+                        "backgroundProcessManager.processFailed"), t);
+            }
+        }
+    }
+
+
+    private static class WsBackgroundThread extends Thread {
+
+        private final BackgroundProcessManager manager;
+        private volatile boolean running = true;
+
+        public WsBackgroundThread(BackgroundProcessManager manager) {
+            setName("WebSocket background processing");
+            this.manager = manager;
+        }
+
+        @Override
+        public void run() {
+            while (running) {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    // Ignore
+                }
+                manager.process();
+            }
+        }
+
+        public void halt() {
+            setName("WebSocket background processing - stopping");
+            running = false;
+        }
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/Constants.java b/java/org/apache/tomcat/websocket/Constants.java
new file mode 100644
index 0000000..0259174
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/Constants.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.util.Locale;
+
+/**
+ * Internal implementation constants.
+ */
+public class Constants {
+
+    protected static final String PACKAGE_NAME =
+            Constants.class.getPackage().getName();
+    // OP Codes
+    public static final byte OPCODE_CONTINUATION = 0x00;
+    public static final byte OPCODE_TEXT = 0x01;
+    public static final byte OPCODE_BINARY = 0x02;
+    public static final byte OPCODE_CLOSE = 0x08;
+    public static final byte OPCODE_PING = 0x09;
+    public static final byte OPCODE_PONG = 0x0A;
+
+    // Internal OP Codes
+    // RFC 6455 limits OP Codes to 4 bits so these should never clash
+    // Always set bit 4 so these will be treated as control codes
+    static final byte INTERNAL_OPCODE_FLUSH = 0x18;
+
+    // Buffers
+    static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
+
+    // Client connection
+    public static final String HOST_HEADER_NAME = "Host";
+    public static final String UPGRADE_HEADER_NAME = "Upgrade";
+    public static final String UPGRADE_HEADER_VALUE = "websocket";
+    public static final String CONNECTION_HEADER_NAME = "Connection";
+    public static final String CONNECTION_HEADER_VALUE = "upgrade";
+    public static final String WS_VERSION_HEADER_NAME = "Sec-WebSocket-Version";
+    public static final String WS_VERSION_HEADER_VALUE = "13";
+    public static final String WS_KEY_HEADER_NAME = "Sec-WebSocket-Key";
+    public static final String WS_PROTOCOL_HEADER_NAME =
+            "Sec-WebSocket-Protocol";
+    public static final String WS_PROTOCOL_HEADER_NAME_LOWER =
+            WS_PROTOCOL_HEADER_NAME.toLowerCase(Locale.ENGLISH);
+    public static final String WS_EXTENSIONS_HEADER_NAME =
+            "Sec-WebSocket-Extensions";
+
+    public static final boolean STRICT_SPEC_COMPLIANCE =
+            Boolean.getBoolean(
+                    "org.apache.tomcat.websocket.STRICT_SPEC_COMPLIANCE");
+
+    private Constants() {
+        // Hide default constructor
+    }
+}
diff --git a/java/javax/annotation/security/DeclareRoles.java b/java/org/apache/tomcat/websocket/DecoderEntry.java
similarity index 56%
copy from java/javax/annotation/security/DeclareRoles.java
copy to java/org/apache/tomcat/websocket/DecoderEntry.java
index d5d214a..299e38e 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/java/org/apache/tomcat/websocket/DecoderEntry.java
@@ -5,27 +5,35 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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.apache.tomcat.websocket;
 
+import javax.websocket.Decoder;
 
-package javax.annotation.security;
+public class DecoderEntry {
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+    private final Class<?> clazz;
+    private final Class<? extends Decoder> decoderClazz;
 
- at Target({ElementType.TYPE})
- at Retention(RetentionPolicy.RUNTIME)
+    public DecoderEntry(Class<?> clazz,
+            Class<? extends Decoder> decoderClazz) {
+        this.clazz = clazz;
+        this.decoderClazz = decoderClazz;
+    }
 
-public @interface DeclareRoles {
-    public String[] value();
-}
+    public Class<?> getClazz() {
+        return clazz;
+    }
+
+    public Class<? extends Decoder> getDecoderClazz() {
+        return decoderClazz;
+    }
+}
\ No newline at end of file
diff --git a/java/org/apache/tomcat/websocket/FutureToSendHandler.java b/java/org/apache/tomcat/websocket/FutureToSendHandler.java
new file mode 100644
index 0000000..7871400
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/FutureToSendHandler.java
@@ -0,0 +1,109 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+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 javax.websocket.SendHandler;
+import javax.websocket.SendResult;
+
+/**
+ * Converts a Future to a SendHandler.
+ */
+class FutureToSendHandler implements Future<Void>, SendHandler {
+
+    private final CountDownLatch latch = new CountDownLatch(1);
+    private final WsSession wsSession;
+    private volatile SendResult result = null;
+
+    public FutureToSendHandler(WsSession wsSession) {
+        this.wsSession = wsSession;
+    }
+
+
+    // --------------------------------------------------------- SendHandler
+
+    @Override
+    public void onResult(SendResult result) {
+
+        this.result = result;
+        latch.countDown();
+    }
+
+
+    // -------------------------------------------------------------- Future
+
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning) {
+        // Cancelling the task is not supported
+        return false;
+    }
+
+    @Override
+    public boolean isCancelled() {
+        // Cancelling the task is not supported
+        return false;
+    }
+
+    @Override
+    public boolean isDone() {
+        return latch.getCount() == 0;
+    }
+
+    @Override
+    public Void get() throws InterruptedException,
+            ExecutionException {
+        try {
+            wsSession.registerFuture(this);
+            latch.await();
+        } finally {
+            wsSession.unregisterFuture(this);
+        }
+        if (result.getException() != null) {
+            throw new ExecutionException(result.getException());
+        }
+        return null;
+    }
+
+    @Override
+    public Void get(long timeout, TimeUnit unit)
+            throws InterruptedException, ExecutionException,
+            TimeoutException {
+        boolean retval = false;
+        try {
+            wsSession.registerFuture(this);
+            retval = latch.await(timeout, unit);
+        } finally {
+            wsSession.unregisterFuture(this);
+
+        }
+        if (retval == false) {
+            throw new TimeoutException();
+        }
+        if (result.getException() != null) {
+            throw new ExecutionException(result.getException());
+        }
+        return null;
+    }
+}
+
+
+
diff --git a/java/org/apache/tomcat/websocket/LocalStrings.properties b/java/org/apache/tomcat/websocket/LocalStrings.properties
new file mode 100644
index 0000000..300a767
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/LocalStrings.properties
@@ -0,0 +1,101 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+asyncChannelWrapperSecure.closeFail=Failed to close channel cleanly
+asyncChannelWrapperSecure.concurrentRead=Concurrent read operations are not permitted
+asyncChannelWrapperSecure.concurrentWrite=Concurrent write operations are not permitted
+asyncChannelWrapperSecure.eof=Unexpected end of stream
+asyncChannelWrapperSecure.readOverflow=Buffer overflow. [{0}] bytes to write into a [{1}] byte buffer that already contained [{2}] bytes.
+asyncChannelWrapperSecure.statusUnwrap=Unexpected Status of SSLEngineResult after an unwrap() operation
+asyncChannelWrapperSecure.statusWrap=Unexpected Status of SSLEngineResult after a wrap() operation
+asyncChannelWrapperSecure.tooBig=The result [{0}] is too big to be expressed as an Integer
+asyncChannelWrapperSecure.wrongStateRead=Flag that indicates a read is in progress was found to be false (it should have been true) when trying to complete a read operation
+asyncChannelWrapperSecure.wrongStateWrite=Flag that indicates a write is in progress was found to be false (it should have been true) when trying to complete a write operation
+
+backgroundProcessManager.processFailed=A background process failed
+
+util.invalidMessageHandler=The message handler provided does not have an onMessage(Object) method
+util.invalidType=Unable to coerce value [{0}] to type [{1}]. That type is not supported.
+util.unknownDecoderType=The Decoder type [{0}] is not recognized
+
+# Note the wsFrame.* messages are used as close reasons in WebSocket control
+# frames and therefore must be 123 bytes (not characters) or less in length.
+# Messages are encoded using UTF-8 where a single character may be encoded in
+# as many as 4 bytes.
+wsFrame.bufferTooSmall=No async message support and buffer too small. Buffer size: [{0}], Message size: [{1}]
+wsFrame.byteToLongFail=Too many bytes ([{0}]) were provided to be converted into a long
+wsFrame.closed=New frame received after a close control frame
+wsFrame.controlFragmented=A fragmented control frame was received but control frames may not be fragmented
+wsFrame.controlPayloadTooBig=A control frame was sent with a payload of size [{0}] which is larger than the maximum permitted of 125 bytes
+wsFrame.controlNoFin=A control frame was sent that did not have the fin bit set. Control frames are not permitted to use continuation frames.
+wsFrame.invalidOpCode= A WebSocket frame was sent with an unrecognised opCode of [{0}]
+wsFrame.invalidUtf8=A WebSocket text frame was received that could not be decoded to UTF-8 because it contained invalid byte sequences
+wsFrame.invalidUtf8Close=A WebSocket close frame was received with a close reason that contained invalid UTF-8 byte sequences
+wsFrame.messageTooBig=The message was [{0}] bytes long but the MessageHandler has a limit of [{1}] bytes
+wsFrame.noContinuation=A new message was started when a continuation frame was expected
+wsFrame.notMasked=The client frame was not masked but all client frames must be masked
+wsFrame.oneByteCloseCode=The client sent a close frame with a single byte payload which is not valid
+wsFrame.sessionClosed=The client data can not be processed because the session has already been closed
+wsFrame.textMessageTooBig=The decoded text message was too big for the output buffer and the endpoint does not support partial messages
+wsFrame.wrongRsv=The client frame set the reserved bits to [{0}] which was not supported by this endpoint
+
+wsRemoteEndpoint.closed=Message will not be sent because the WebSocket session has been closed
+wsRemoteEndpoint.closedOutputStream=This method may not be called as the OutputStream has been closed
+wsRemoteEndpoint.closedWriter=This method may not be called as the Writer has been closed
+wsRemoteEndpoint.changeType=When sending a fragmented message, all fragments bust be of the same type
+wsRemoteEndpoint.concurrentMessageSend=Messages may not be sent concurrently even when using the asynchronous send messages. The client must wait for the previous message to complete before sending the next.
+wsRemoteEndpoint.flushOnCloseFailed=Flushing batched messages before closing the session failed
+wsRemoteEndpoint.inProgress=Message will not be sent because the WebSocket session is currently sending another message
+wsRemoteEndpoint.invalidEncoder=The specified encoder of type [{0}] could not be instantiated
+wsRemoteEndpoint.noEncoder=No encoder specified for object of class [{0}]
+
+# Note the following message is used as a close reason in a WebSocket control
+# frame and therefore must be 123 bytes (not characters) or less in length.
+# Messages are encoded using UTF-8 where a single character may be encoded in
+# as many as 4 bytes.
+wsSession.timeout=The WebSocket session timeout expired
+
+wsSession.closed=The WebSocket session has been closed and no method (apart from close()) may be called on a closed session
+wsSession.duplicateHandlerBinary=A binary message handler has already been configured
+wsSession.duplicateHandlerPong=A pong message handler has already been configured
+wsSession.duplicateHandlerText=A text message handler has already been configured
+wsSession.invalidHandlerTypePong=A pong message handler must implement MessageHandler.Basic
+wsSession.messageFailed=Unable to write the complete message as the WebSocket connection has been closed
+wsSession.sendCloseFail=Failed to send close message to remote endpoint
+wsSession.removeHandlerFailed=Unable to remove the handler [{0}] as it was not registered with this session
+wsSession.unknownHandler=Unable to add the message handler [{0}] as it was for the unrecognised type [{1}]
+wsSession.unknownHandlerType=Unable to add the message handler [{0}] as it was wrapped as the unrecognised type [{1}]
+
+# Note the following message is used as a close reason in a WebSocket control
+# frame and therefore must be 123 bytes (not characters) or less in length.
+# Messages are encoded using UTF-8 where a single character may be encoded in
+# as many as 4 bytes.
+wsWebSocketContainer.shutdown=The web application is stopping
+
+wsWebSocketContainer.asynchronousChannelGroupFail=Unable to create dedicated AsynchronousChannelGroup for WebSocket clients which is required to prevent memory leaks in complex class loader environments like J2EE containers
+wsWebSocketContainer.asynchronousSocketChannelFail=Unable to open a connection to the server
+wsWebSocketContainer.defaultConfiguratorFaill=Failed to create the default configurator
+wsWebSocketContainer.endpointCreateFail=Failed to create a local endpoint of type [{0}]
+wsWebSocketContainer.httpRequestFailed=The HTTP request to initiate the WebSocket connection failed
+wsWebSocketContainer.invalidHeader=Unable to parse HTTP header as no colon is present to delimit header name and header value in [{0}]. The header has been skipped.
+wsWebSocketContainer.invalidScheme=The requested scheme, [{0}], is not supported. The supported schemes are ws and wss
+wsWebSocketContainer.invalidStatus=The HTTP response from the server [{0}] did not permit the HTTP upgrade to WebSocket
+wsWebSocketContainer.invalidSubProtocol=The WebSocket server returned multiple values for the Sec-WebSocket-Protocol header
+wsWebSocketContainer.maxBuffer=This implementation limits the maximum size of a buffer to Integer.MAX_VALUE
+wsWebSocketContainer.missingAnnotation=Cannot use POJO class [{0}] as it is not annotated with @ClientEndpoint
+wsWebSocketContainer.pathNoHost=No host was specified in URI
+wsWebSocketContainer.pathWrongScheme=The scheme [{0}] is not supported
+wsWebSocketContainer.sessionCloseFail=Session with ID [{0}] did not close cleanly
+wsWebSocketContainer.sslEngineFail=Unable to create SSLEngine to support SSL/TLS connections
diff --git a/java/javax/annotation/security/DeclareRoles.java b/java/org/apache/tomcat/websocket/MessageHandlerResult.java
similarity index 60%
copy from java/javax/annotation/security/DeclareRoles.java
copy to java/org/apache/tomcat/websocket/MessageHandlerResult.java
index d5d214a..a55e8dd 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/java/org/apache/tomcat/websocket/MessageHandlerResult.java
@@ -5,27 +5,38 @@
  * The ASF licenses this file to You 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.apache.tomcat.websocket;
 
+import javax.websocket.MessageHandler;
 
-package javax.annotation.security;
+public class MessageHandlerResult {
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+    private final MessageHandler handler;
+    private final MessageHandlerResultType type;
 
- at Target({ElementType.TYPE})
- at Retention(RetentionPolicy.RUNTIME)
 
-public @interface DeclareRoles {
-    public String[] value();
+    public MessageHandlerResult(MessageHandler handler,
+            MessageHandlerResultType type) {
+        this.handler = handler;
+        this.type = type;
+    }
+
+
+    public MessageHandler getHandler() {
+        return handler;
+    }
+
+
+    public MessageHandlerResultType getType() {
+        return type;
+    }
 }
diff --git a/java/javax/annotation/PreDestroy.java b/java/org/apache/tomcat/websocket/MessageHandlerResultType.java
similarity index 71%
copy from java/javax/annotation/PreDestroy.java
copy to java/org/apache/tomcat/websocket/MessageHandlerResultType.java
index d5be75a..a4d857c 100644
--- a/java/javax/annotation/PreDestroy.java
+++ b/java/org/apache/tomcat/websocket/MessageHandlerResultType.java
@@ -5,27 +5,19 @@
  * The ASF licenses this file to You 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.apache.tomcat.websocket;
 
-
-package javax.annotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
- at Target({ElementType.METHOD})
- at Retention(RetentionPolicy.RUNTIME)
-
-public @interface PreDestroy {
-    // No attributes
+public enum MessageHandlerResultType {
+    BINARY,
+    TEXT,
+    PONG
 }
diff --git a/java/javax/annotation/security/DeclareRoles.java b/java/org/apache/tomcat/websocket/ReadBufferOverflowException.java
similarity index 62%
copy from java/javax/annotation/security/DeclareRoles.java
copy to java/org/apache/tomcat/websocket/ReadBufferOverflowException.java
index d5d214a..5ad6f4f 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/java/org/apache/tomcat/websocket/ReadBufferOverflowException.java
@@ -5,27 +5,30 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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.apache.tomcat.websocket;
 
+import java.io.IOException;
 
-package javax.annotation.security;
+public class ReadBufferOverflowException extends IOException {
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+    private static final long serialVersionUID = 1L;
 
- at Target({ElementType.TYPE})
- at Retention(RetentionPolicy.RUNTIME)
+    private final int minBufferSize;
 
-public @interface DeclareRoles {
-    public String[] value();
+    public ReadBufferOverflowException(int minBufferSize) {
+        this.minBufferSize = minBufferSize;
+    }
+
+    public int getMinBufferSize() {
+        return minBufferSize;
+    }
 }
diff --git a/java/org/apache/tomcat/util/net/SocketStatus.java b/java/org/apache/tomcat/websocket/SendHandlerToCompletionHandler.java
similarity index 56%
copy from java/org/apache/tomcat/util/net/SocketStatus.java
copy to java/org/apache/tomcat/websocket/SendHandlerToCompletionHandler.java
index bf53c2b..d910a93 100644
--- a/java/org/apache/tomcat/util/net/SocketStatus.java
+++ b/java/org/apache/tomcat/websocket/SendHandlerToCompletionHandler.java
@@ -14,14 +14,29 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
+package org.apache.tomcat.websocket;
 
-package org.apache.tomcat.util.net;
+import java.nio.channels.CompletionHandler;
 
-/**
- * Someone, please change the enum name.
- *
- * @author remm
- */
-public enum SocketStatus {
-    OPEN, STOP, TIMEOUT, DISCONNECT, ERROR
+import javax.websocket.SendHandler;
+import javax.websocket.SendResult;
+
+public class SendHandlerToCompletionHandler
+        implements CompletionHandler<Long,Void> {
+
+    private SendHandler handler;
+
+    public SendHandlerToCompletionHandler(SendHandler handler) {
+        this.handler = handler;
+    }
+
+    @Override
+    public void completed(Long result, Void attachment) {
+        handler.onResult(new SendResult());
+    }
+
+    @Override
+    public void failed(Throwable exc, Void attachment) {
+        handler.onResult(new SendResult(exc));
+    }
 }
diff --git a/java/org/apache/tomcat/websocket/Util.java b/java/org/apache/tomcat/websocket/Util.java
new file mode 100644
index 0000000..9c2e2e7
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/Util.java
@@ -0,0 +1,483 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.nio.ByteBuffer;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.websocket.CloseReason.CloseCode;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.Decoder;
+import javax.websocket.Decoder.Binary;
+import javax.websocket.Decoder.BinaryStream;
+import javax.websocket.Decoder.Text;
+import javax.websocket.Decoder.TextStream;
+import javax.websocket.DeploymentException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.PongMessage;
+
+import org.apache.tomcat.util.res.StringManager;
+import org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBinary;
+import org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeText;
+
+/**
+ * Utility class for internal use only within the
+ * {@link org.apache.tomcat.websocket} package.
+ */
+public class Util {
+
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+    private static final Queue<SecureRandom> randoms =
+            new ConcurrentLinkedQueue<SecureRandom>();
+
+    private Util() {
+        // Hide default constructor
+    }
+
+
+    static boolean isControl(byte opCode) {
+        return (opCode & 0x08) > 0;
+    }
+
+
+    static boolean isText(byte opCode) {
+        return opCode == Constants.OPCODE_TEXT;
+    }
+
+
+    static CloseCode getCloseCode(int code) {
+        if (code > 2999 && code < 5000) {
+            return CloseCodes.NORMAL_CLOSURE;
+        }
+        switch (code) {
+            case 1000:
+                return CloseCodes.NORMAL_CLOSURE;
+            case 1001:
+                return CloseCodes.GOING_AWAY;
+            case 1002:
+                return CloseCodes.PROTOCOL_ERROR;
+            case 1003:
+                return CloseCodes.CANNOT_ACCEPT;
+            case 1004:
+                // Should not be used in a close frame
+                // return CloseCodes.RESERVED;
+                return CloseCodes.PROTOCOL_ERROR;
+            case 1005:
+                // Should not be used in a close frame
+                // return CloseCodes.NO_STATUS_CODE;
+                return CloseCodes.PROTOCOL_ERROR;
+            case 1006:
+                // Should not be used in a close frame
+                // return CloseCodes.CLOSED_ABNORMALLY;
+                return CloseCodes.PROTOCOL_ERROR;
+            case 1007:
+                return CloseCodes.NOT_CONSISTENT;
+            case 1008:
+                return CloseCodes.VIOLATED_POLICY;
+            case 1009:
+                return CloseCodes.TOO_BIG;
+            case 1010:
+                return CloseCodes.NO_EXTENSION;
+            case 1011:
+                return CloseCodes.UNEXPECTED_CONDITION;
+            case 1012:
+                // Not in RFC6455
+                // return CloseCodes.SERVICE_RESTART;
+                return CloseCodes.PROTOCOL_ERROR;
+            case 1013:
+                // Not in RFC6455
+                // return CloseCodes.TRY_AGAIN_LATER;
+                return CloseCodes.PROTOCOL_ERROR;
+            case 1015:
+                // Should not be used in a close frame
+                // return CloseCodes.TLS_HANDSHAKE_FAILURE;
+                return CloseCodes.PROTOCOL_ERROR;
+            default:
+                return CloseCodes.PROTOCOL_ERROR;
+        }
+    }
+
+
+    static byte[] generateMask() {
+        // SecureRandom is not thread-safe so need to make sure only one thread
+        // uses it at a time. In theory, the pool could grow to the same size
+        // as the number of request processing threads. In reality it will be
+        // a lot smaller.
+
+        // Get a SecureRandom from the pool
+        SecureRandom sr = randoms.poll();
+
+        // If one isn't available, generate a new one
+        if (sr == null) {
+            try {
+                sr = SecureRandom.getInstance("SHA1PRNG");
+            } catch (NoSuchAlgorithmException e) {
+                // Fall back to platform default
+                sr = new SecureRandom();
+            }
+        }
+
+        // Generate the mask
+        byte[] result = new byte[4];
+        sr.nextBytes(result);
+
+        // Put the SecureRandom back in the poll
+        randoms.add(sr);
+
+        return result;
+    }
+
+
+    static Class<?> getMessageType(MessageHandler listener) {
+        return (Class<?>) Util.getGenericType(MessageHandler.class,
+                listener.getClass());
+    }
+
+
+    public static Class<?> getDecoderType(Class<? extends Decoder> Decoder) {
+        return (Class<?>) Util.getGenericType(Decoder.class, Decoder);
+    }
+
+
+    static Class<?> getEncoderType(Class<? extends Encoder> encoder) {
+        return (Class<?>) Util.getGenericType(Encoder.class, encoder);
+    }
+
+
+    private static <T> Object getGenericType(Class<T> type,
+            Class<? extends T> clazz) {
+
+        // Look to see if this class implements the generic MessageHandler<>
+        // interface
+
+        // Get all the interfaces
+        Type[] interfaces = clazz.getGenericInterfaces();
+        for (Type iface : interfaces) {
+            // Only need to check interfaces that use generics
+            if (iface instanceof ParameterizedType) {
+                ParameterizedType pi = (ParameterizedType) iface;
+                // Look for the MessageHandler<> interface
+                if (pi.getRawType() instanceof Class) {
+                    if (type.isAssignableFrom((Class<?>) pi.getRawType())) {
+                        return getTypeParameter(
+                                clazz, pi.getActualTypeArguments()[0]);
+                    }
+                }
+            }
+        }
+
+        // Interface not found on this class. Look at the superclass.
+        @SuppressWarnings("unchecked")
+        Class<? extends T> superClazz =
+                (Class<? extends T>) clazz.getSuperclass();
+
+        Object result = getGenericType(type, superClazz);
+        if (result instanceof Class<?>) {
+            // Superclass implements interface and defines explicit type for
+            // MessageHandler<>
+            return result;
+        } else if (result instanceof Integer) {
+            // Superclass implements interface and defines unknown type for
+            // MessageHandler<>
+            // Map that unknown type to the generic types defined in this class
+            ParameterizedType superClassType =
+                    (ParameterizedType) clazz.getGenericSuperclass();
+            return getTypeParameter(clazz,
+                    superClassType.getActualTypeArguments()[
+                            ((Integer) result).intValue()]);
+        } else {
+            // Error will be logged further up the call stack
+            return null;
+        }
+    }
+
+
+    /*
+     * For a generic parameter, return either the Class used or if the type
+     * is unknown, the index for the type in definition of the class
+     */
+    private static Object getTypeParameter(Class<?> clazz, Type argType) {
+        if (argType instanceof Class<?>) {
+            return argType;
+        } else {
+            TypeVariable<?>[] tvs = clazz.getTypeParameters();
+            for (int i = 0; i < tvs.length; i++) {
+                if (tvs[i].equals(argType)) {
+                    return Integer.valueOf(i);
+                }
+            }
+            return null;
+        }
+    }
+
+
+    public static boolean isPrimitive(Class<?> clazz) {
+        if (clazz.isPrimitive()) {
+            return true;
+        } else if(clazz.equals(Boolean.class) ||
+                clazz.equals(Byte.class) ||
+                clazz.equals(Character.class) ||
+                clazz.equals(Double.class) ||
+                clazz.equals(Float.class) ||
+                clazz.equals(Integer.class) ||
+                clazz.equals(Long.class) ||
+                clazz.equals(Short.class)) {
+            return true;
+        }
+        return false;
+    }
+
+
+    public static Object coerceToType(Class<?> type, String value) {
+        if (type.equals(String.class)) {
+            return value;
+        } else if (type.equals(boolean.class) || type.equals(Boolean.class)) {
+            return Boolean.valueOf(value);
+        } else if (type.equals(byte.class) || type.equals(Byte.class)) {
+            return Byte.valueOf(value);
+        } else if (value.length() == 1 &&
+                (type.equals(char.class) || type.equals(Character.class))) {
+            return Character.valueOf(value.charAt(0));
+        } else if (type.equals(double.class) || type.equals(Double.class)) {
+            return Double.valueOf(value);
+        } else if (type.equals(float.class) || type.equals(Float.class)) {
+            return Float.valueOf(value);
+        } else if (type.equals(int.class) || type.equals(Integer.class)) {
+            return Integer.valueOf(value);
+        } else if (type.equals(long.class) || type.equals(Long.class)) {
+            return Long.valueOf(value);
+        } else if (type.equals(short.class) || type.equals(Short.class)) {
+            return Short.valueOf(value);
+        } else {
+            throw new IllegalArgumentException(sm.getString(
+                    "util.invalidType", value, type.getName()));
+        }
+    }
+
+
+    public static List<DecoderEntry> getDecoders(
+            Class<? extends Decoder>[] decoderClazzes)
+                    throws DeploymentException{
+
+        List<DecoderEntry> result = new ArrayList<DecoderEntry>();
+        for (Class<? extends Decoder> decoderClazz : decoderClazzes) {
+            // Need to instantiate decoder to ensure it is valid and that
+            // deployment can be failed if it is not
+            @SuppressWarnings("unused")
+            Decoder instance;
+            try {
+                instance = decoderClazz.newInstance();
+            } catch (InstantiationException e) {
+                throw new DeploymentException(
+                        sm.getString("pojoMethodMapping.invalidDecoder",
+                                decoderClazz.getName()), e);
+            } catch (IllegalAccessException e) {
+                throw new DeploymentException(
+                        sm.getString("pojoMethodMapping.invalidDecoder",
+                                decoderClazz.getName()), e);
+            }
+            DecoderEntry entry = new DecoderEntry(
+                    Util.getDecoderType(decoderClazz), decoderClazz);
+            result.add(entry);
+        }
+
+        return result;
+    }
+
+
+
+    public static Set<MessageHandlerResult> getMessageHandlers(
+            MessageHandler listener, EndpointConfig endpointConfig) {
+
+        Class<?> target = Util.getMessageType(listener);
+
+        // Will never be more than 2 types
+        Set<MessageHandlerResult> results = new HashSet<MessageHandlerResult>(2);
+
+        // Simple cases - handlers already accepts one of the types expected by
+        // the frame handling code
+        if (String.class.isAssignableFrom(target)) {
+            MessageHandlerResult result =
+                    new MessageHandlerResult(listener,
+                            MessageHandlerResultType.TEXT);
+            results.add(result);
+        } else if (ByteBuffer.class.isAssignableFrom(target)) {
+            MessageHandlerResult result =
+                    new MessageHandlerResult(listener,
+                            MessageHandlerResultType.BINARY);
+            results.add(result);
+        } else if (PongMessage.class.isAssignableFrom(target)) {
+            MessageHandlerResult result =
+                    new MessageHandlerResult(listener,
+                            MessageHandlerResultType.PONG);
+            results.add(result);
+        // Relatively simple cases - handler needs wrapping but no decoder to
+        // convert it to one of the types expected by the frame handling code
+        } else if (byte[].class.isAssignableFrom(target)) {
+            MessageHandlerResult result = new MessageHandlerResult(
+                    new PojoMessageHandlerWholeBinary(listener,
+                            getOnMessageMethod(listener), null,
+                            endpointConfig, null, new Object[1], 0, true, -1,
+                            false, -1),
+                    MessageHandlerResultType.BINARY);
+            results.add(result);
+        } else if (InputStream.class.isAssignableFrom(target)) {
+            MessageHandlerResult result = new MessageHandlerResult(
+                    new PojoMessageHandlerWholeBinary(listener,
+                            getOnMessageMethod(listener), null,
+                            endpointConfig, null, new Object[1], 0, true, -1,
+                            true, -1),
+                    MessageHandlerResultType.BINARY);
+            results.add(result);
+        } else if (Reader.class.isAssignableFrom(target)) {
+            MessageHandlerResult result = new MessageHandlerResult(
+                    new PojoMessageHandlerWholeText(listener,
+                            getOnMessageMethod(listener), null,
+                            endpointConfig, null, new Object[1], 0, true, -1,
+                            -1),
+                    MessageHandlerResultType.TEXT);
+            results.add(result);
+        } else {
+        // More complex case - listener that requires a decoder
+            DecoderMatch decoderMatch;
+            try {
+                List<Class<? extends Decoder>> decoders =
+                        endpointConfig.getDecoders();
+                @SuppressWarnings("unchecked")
+                List<DecoderEntry> decoderEntries = getDecoders(
+                        decoders.toArray(new Class[decoders.size()]));
+                decoderMatch = new DecoderMatch(target, decoderEntries);
+            } catch (DeploymentException e) {
+                throw new IllegalArgumentException(e);
+            }
+            Method m = getOnMessageMethod(listener);
+            if (decoderMatch.getBinaryDecoders().size() > 0) {
+                MessageHandlerResult result = new MessageHandlerResult(
+                        new PojoMessageHandlerWholeBinary(listener, m, null,
+                                endpointConfig,
+                                decoderMatch.getBinaryDecoders(), new Object[1],
+                                0, false, -1, false, -1),
+                        MessageHandlerResultType.BINARY);
+                results.add(result);
+            }
+            if (decoderMatch.getTextDecoders().size() > 0) {
+                MessageHandlerResult result = new MessageHandlerResult(
+                        new PojoMessageHandlerWholeText(listener, m, null,
+                                endpointConfig,
+                                decoderMatch.getTextDecoders(), new Object[1],
+                                0, false, -1, -1),
+                        MessageHandlerResultType.TEXT);
+                results.add(result);
+            }
+        }
+
+        if (results.size() == 0) {
+            throw new IllegalArgumentException(
+                    sm.getString("wsSession.unknownHandler", listener, target));
+        }
+
+        return results;
+    }
+
+
+    private static Method getOnMessageMethod(MessageHandler listener) {
+        try {
+            return listener.getClass().getMethod("onMessage", Object.class);
+        } catch (NoSuchMethodException e) {
+            throw new IllegalArgumentException(
+                    sm.getString("util.invalidMessageHandler"), e);
+        } catch ( SecurityException e) {
+            throw new IllegalArgumentException(
+                    sm.getString("util.invalidMessageHandler"), e);
+        }
+    }
+
+    public static class DecoderMatch {
+
+        private final List<Class<? extends Decoder>> textDecoders =
+                new ArrayList<Class<? extends Decoder>>();
+        private final List<Class<? extends Decoder>> binaryDecoders =
+                new ArrayList<Class<? extends Decoder>>();
+
+
+        public DecoderMatch(Class<?> target, List<DecoderEntry> decoderEntries) {
+            for (DecoderEntry decoderEntry : decoderEntries) {
+                if (decoderEntry.getClazz().isAssignableFrom(target)) {
+                    if (Binary.class.isAssignableFrom(
+                            decoderEntry.getDecoderClazz())) {
+                        binaryDecoders.add(decoderEntry.getDecoderClazz());
+                        // willDecode() method means this decoder may or may not
+                        // decode a message so need to carry on checking for
+                        // other matches
+                    } else if (BinaryStream.class.isAssignableFrom(
+                            decoderEntry.getDecoderClazz())) {
+                        binaryDecoders.add(decoderEntry.getDecoderClazz());
+                        // Stream decoders have to process the message so no
+                        // more decoders can be matched
+                        break;
+                    } else if (Text.class.isAssignableFrom(
+                            decoderEntry.getDecoderClazz())) {
+                        textDecoders.add(decoderEntry.getDecoderClazz());
+                        // willDecode() method means this decoder may or may not
+                        // decode a message so need to carry on checking for
+                        // other matches
+                    } else if (TextStream.class.isAssignableFrom(
+                            decoderEntry.getDecoderClazz())) {
+                        textDecoders.add(decoderEntry.getDecoderClazz());
+                        // Stream decoders have to process the message so no
+                        // more decoders can be matched
+                        break;
+                    } else {
+                        throw new IllegalArgumentException(
+                                sm.getString("util.unknownDecoderType"));
+                    }
+                }
+            }
+        }
+
+
+        public List<Class<? extends Decoder>> getTextDecoders() {
+            return textDecoders;
+        }
+
+
+        public List<Class<? extends Decoder>> getBinaryDecoders() {
+            return binaryDecoders;
+        }
+
+
+        public boolean hasMatches() {
+            return (textDecoders.size() > 0) || (binaryDecoders.size() > 0);
+        }
+    }
+}
diff --git a/java/javax/annotation/PreDestroy.java b/java/org/apache/tomcat/websocket/WrappedMessageHandler.java
similarity index 67%
copy from java/javax/annotation/PreDestroy.java
copy to java/org/apache/tomcat/websocket/WrappedMessageHandler.java
index d5be75a..c4004b3 100644
--- a/java/javax/annotation/PreDestroy.java
+++ b/java/org/apache/tomcat/websocket/WrappedMessageHandler.java
@@ -5,27 +5,21 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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.apache.tomcat.websocket;
 
+import javax.websocket.MessageHandler;
 
-package javax.annotation;
+public interface WrappedMessageHandler {
+    long getMaxMessageSize();
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
- at Target({ElementType.METHOD})
- at Retention(RetentionPolicy.RUNTIME)
-
-public @interface PreDestroy {
-    // No attributes
+    MessageHandler getWrappedHandler();
 }
diff --git a/java/javax/annotation/PreDestroy.java b/java/org/apache/tomcat/websocket/WsContainerProvider.java
similarity index 67%
copy from java/javax/annotation/PreDestroy.java
copy to java/org/apache/tomcat/websocket/WsContainerProvider.java
index d5be75a..654ad79 100644
--- a/java/javax/annotation/PreDestroy.java
+++ b/java/org/apache/tomcat/websocket/WsContainerProvider.java
@@ -5,27 +5,24 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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.apache.tomcat.websocket;
 
+import javax.websocket.ContainerProvider;
+import javax.websocket.WebSocketContainer;
 
-package javax.annotation;
+public class WsContainerProvider extends ContainerProvider {
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
- at Target({ElementType.METHOD})
- at Retention(RetentionPolicy.RUNTIME)
-
-public @interface PreDestroy {
-    // No attributes
+    @Override
+    protected WebSocketContainer getContainer() {
+        return new WsWebSocketContainer();
+    }
 }
diff --git a/java/org/apache/tomcat/websocket/WsFrameBase.java b/java/org/apache/tomcat/websocket/WsFrameBase.java
new file mode 100644
index 0000000..a56e9d4
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/WsFrameBase.java
@@ -0,0 +1,679 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.MessageHandler;
+import javax.websocket.PongMessage;
+
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.Utf8Decoder;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Takes the ServletInputStream, processes the WebSocket frames it contains and
+ * extracts the messages. WebSocket Pings received will be responded to
+ * automatically without any action required by the application.
+ */
+public abstract class WsFrameBase {
+
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+
+    // Connection level attributes
+    protected final WsSession wsSession;
+    protected final byte[] inputBuffer;
+
+    // Attributes for control messages
+    // Control messages can appear in the middle of other messages so need
+    // separate attributes
+    private final ByteBuffer controlBufferBinary = ByteBuffer.allocate(125);
+    private final CharBuffer controlBufferText = CharBuffer.allocate(125);
+
+    // Attributes of the current message
+    private final CharsetDecoder utf8DecoderControl = new Utf8Decoder().
+            onMalformedInput(CodingErrorAction.REPORT).
+            onUnmappableCharacter(CodingErrorAction.REPORT);
+    private final CharsetDecoder utf8DecoderMessage = new Utf8Decoder().
+            onMalformedInput(CodingErrorAction.REPORT).
+            onUnmappableCharacter(CodingErrorAction.REPORT);
+    private boolean continuationExpected = false;
+    private boolean textMessage = false;
+    private ByteBuffer messageBufferBinary;
+    private CharBuffer messageBufferText;
+    // Cache the message handler in force when the message starts so it is used
+    // consistently for the entire message
+    private MessageHandler binaryMsgHandler = null;
+    private MessageHandler textMsgHandler = null;
+
+    // Attributes of the current frame
+    private boolean fin = false;
+    private int rsv = 0;
+    private byte opCode = 0;
+    private final byte[] mask = new byte[4];
+    private int maskIndex = 0;
+    private long payloadLength = 0;
+    private long payloadWritten = 0;
+
+    // Attributes tracking state
+    private State state = State.NEW_FRAME;
+    private volatile boolean open = true;
+    private int readPos = 0;
+    protected int writePos = 0;
+
+    public WsFrameBase(WsSession wsSession) {
+
+        inputBuffer = new byte[Constants.DEFAULT_BUFFER_SIZE];
+        messageBufferBinary =
+                ByteBuffer.allocate(wsSession.getMaxBinaryMessageBufferSize());
+        messageBufferText =
+                CharBuffer.allocate(wsSession.getMaxTextMessageBufferSize());
+        this.wsSession = wsSession;
+    }
+
+
+    protected void processInputBuffer() throws IOException {
+        while (true) {
+            wsSession.updateLastActive();
+
+            if (state == State.NEW_FRAME) {
+                if (!processInitialHeader()) {
+                    break;
+                }
+                // If a close frame has been received, no further data should
+                // have seen
+                if (!open) {
+                    throw new IOException(sm.getString("wsFrame.closed"));
+                }
+            }
+            if (state == State.PARTIAL_HEADER) {
+                if (!processRemainingHeader()) {
+                    break;
+                }
+            }
+            if (state == State.DATA) {
+                if (!processData()) {
+                    break;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * @return <code>true</code> if sufficient data was present to process all
+     *         of the initial header
+     */
+    private boolean processInitialHeader() throws IOException {
+        // Need at least two bytes of data to do this
+        if (writePos - readPos < 2) {
+            return false;
+        }
+        int b = inputBuffer[readPos++];
+        fin = (b & 0x80) > 0;
+        rsv = (b & 0x70) >>> 4;
+        if (rsv != 0) {
+            // Note extensions may use rsv bits but currently no extensions are
+            // supported
+            throw new WsIOException(new CloseReason(
+                    CloseCodes.PROTOCOL_ERROR,
+                    sm.getString("wsFrame.wrongRsv", Integer.valueOf(rsv))));
+        }
+        opCode = (byte) (b & 0x0F);
+        if (Util.isControl(opCode)) {
+            if (!fin) {
+                throw new WsIOException(new CloseReason(
+                        CloseCodes.PROTOCOL_ERROR,
+                        sm.getString("wsFrame.controlFragmented")));
+            }
+            if (opCode != Constants.OPCODE_PING &&
+                    opCode != Constants.OPCODE_PONG &&
+                    opCode != Constants.OPCODE_CLOSE) {
+                throw new WsIOException(new CloseReason(
+                        CloseCodes.PROTOCOL_ERROR,
+                        sm.getString("wsFrame.invalidOpCode",
+                                Integer.valueOf(opCode))));
+            }
+        } else {
+            if (continuationExpected) {
+                if (opCode != Constants.OPCODE_CONTINUATION) {
+                    throw new WsIOException(new CloseReason(
+                            CloseCodes.PROTOCOL_ERROR,
+                            sm.getString("wsFrame.noContinuation")));
+                }
+            } else {
+                try {
+                    if (opCode == Constants.OPCODE_BINARY) {
+                        // New binary message
+                        textMessage = false;
+                        int size = wsSession.getMaxBinaryMessageBufferSize();
+                        if (size != messageBufferBinary.capacity()) {
+                            messageBufferBinary = ByteBuffer.allocate(size);
+                        }
+                        binaryMsgHandler = wsSession.getBinaryMessageHandler();
+                        textMsgHandler = null;
+                    } else if (opCode == Constants.OPCODE_TEXT) {
+                        // New text message
+                        textMessage = true;
+                        int size = wsSession.getMaxTextMessageBufferSize();
+                        if (size != messageBufferText.capacity()) {
+                            messageBufferText = CharBuffer.allocate(size);
+                        }
+                        binaryMsgHandler = null;
+                        textMsgHandler = wsSession.getTextMessageHandler();
+                    } else {
+                        throw new WsIOException(new CloseReason(
+                                CloseCodes.PROTOCOL_ERROR,
+                                sm.getString("wsFrame.invalidOpCode",
+                                        Integer.valueOf(opCode))));
+                    }
+                } catch (IllegalStateException ise) {
+                    // Thrown if the session is already closed
+                    throw new WsIOException(new CloseReason(
+                            CloseCodes.PROTOCOL_ERROR,
+                            sm.getString("wsFrame.sessionClosed")));
+                }
+            }
+            continuationExpected = !fin;
+        }
+        b = inputBuffer[readPos++];
+        // Client data must be masked
+        if ((b & 0x80) == 0 && isMasked()) {
+            throw new WsIOException(new CloseReason(
+                    CloseCodes.PROTOCOL_ERROR,
+                    sm.getString("wsFrame.notMasked")));
+        }
+        payloadLength = b & 0x7F;
+        state = State.PARTIAL_HEADER;
+        return true;
+    }
+
+
+    protected abstract boolean isMasked();
+
+
+    /**
+     * @return <code>true</code> if sufficient data was present to complete the
+     *         processing of the header
+     */
+    private boolean processRemainingHeader() throws IOException {
+        // Ignore the 2 bytes already read. 4 for the mask
+        int headerLength;
+        if (isMasked()) {
+            headerLength = 4;
+        } else {
+            headerLength = 0;
+        }
+        // Add additional bytes depending on length
+        if (payloadLength == 126) {
+            headerLength += 2;
+        } else if (payloadLength == 127) {
+            headerLength += 8;
+        }
+        if (writePos - readPos < headerLength) {
+            return false;
+        }
+        // Calculate new payload length if necessary
+        if (payloadLength == 126) {
+            payloadLength = byteArrayToLong(inputBuffer, readPos, 2);
+            readPos += 2;
+        } else if (payloadLength == 127) {
+            payloadLength = byteArrayToLong(inputBuffer, readPos, 8);
+            readPos += 8;
+        }
+        if (Util.isControl(opCode)) {
+            if (payloadLength > 125) {
+                throw new WsIOException(new CloseReason(
+                        CloseCodes.PROTOCOL_ERROR,
+                        sm.getString("wsFrame.controlPayloadTooBig",
+                                Long.valueOf(payloadLength))));
+            }
+            if (!fin) {
+                throw new WsIOException(new CloseReason(
+                        CloseCodes.PROTOCOL_ERROR,
+                        sm.getString("wsFrame.controlNoFin")));
+            }
+        }
+        if (isMasked()) {
+            System.arraycopy(inputBuffer, readPos, mask, 0, 4);
+            readPos += 4;
+        }
+        state = State.DATA;
+        return true;
+    }
+
+
+    private boolean processData() throws IOException {
+        checkRoomPayload();
+        if (Util.isControl(opCode)) {
+            return processDataControl();
+        } else if (textMessage) {
+            return processDataText();
+        } else {
+            return processDataBinary();
+        }
+    }
+
+
+    private boolean processDataControl() throws IOException {
+        if (!appendPayloadToMessage(controlBufferBinary)) {
+            return false;
+        }
+        controlBufferBinary.flip();
+        if (opCode == Constants.OPCODE_CLOSE) {
+            open = false;
+            String reason = null;
+            int code = CloseCodes.NORMAL_CLOSURE.getCode();
+            if (controlBufferBinary.remaining() == 1) {
+                controlBufferBinary.clear();
+                // Payload must be zero or greater than 2
+                throw new WsIOException(new CloseReason(
+                        CloseCodes.PROTOCOL_ERROR,
+                        sm.getString("wsFrame.oneByteCloseCode")));
+            }
+            if (controlBufferBinary.remaining() > 1) {
+                code = controlBufferBinary.getShort();
+                if (controlBufferBinary.remaining() > 0) {
+                    CoderResult cr = utf8DecoderControl.decode(
+                            controlBufferBinary, controlBufferText, true);
+                    if (cr.isError()) {
+                        controlBufferBinary.clear();
+                        controlBufferText.clear();
+                        throw new WsIOException(new CloseReason(
+                                CloseCodes.PROTOCOL_ERROR,
+                                sm.getString("wsFrame.invalidUtf8Close")));
+                    }
+                    // There will be no overflow as the output buffer is big
+                    // enough. There will be no underflow as all the data is
+                    // passed to the decoder in a single call.
+                    controlBufferText.flip();
+                    reason = controlBufferText.toString();
+                }
+            }
+            wsSession.onClose(new CloseReason(Util.getCloseCode(code), reason));
+        } else if (opCode == Constants.OPCODE_PING) {
+            if (wsSession.isOpen()) {
+                wsSession.getBasicRemote().sendPong(controlBufferBinary);
+            }
+        } else if (opCode == Constants.OPCODE_PONG) {
+            MessageHandler.Whole<PongMessage> mhPong =
+                    wsSession.getPongMessageHandler();
+            if (mhPong != null) {
+                try {
+                    mhPong.onMessage(new WsPongMessage(controlBufferBinary));
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    wsSession.getLocal().onError(wsSession, t);
+                } finally {
+                    controlBufferBinary.clear();
+                }
+            }
+        } else {
+            // Should have caught this earlier but just in case...
+            controlBufferBinary.clear();
+            throw new WsIOException(new CloseReason(
+                    CloseCodes.PROTOCOL_ERROR,
+                    sm.getString("wsFrame.invalidOpCode",
+                            Integer.valueOf(opCode))));
+        }
+        controlBufferBinary.clear();
+        newFrame();
+        return true;
+    }
+
+
+    @SuppressWarnings("unchecked")
+    private void sendMessageText(boolean last) throws WsIOException {
+        if (textMsgHandler != null) {
+            if (textMsgHandler instanceof WrappedMessageHandler) {
+                long maxMessageSize =
+                        ((WrappedMessageHandler) textMsgHandler).getMaxMessageSize();
+                if (maxMessageSize > -1 &&
+                        messageBufferText.remaining() > maxMessageSize) {
+                    throw new WsIOException(new CloseReason(CloseCodes.TOO_BIG,
+                            sm.getString("wsFrame.messageTooBig",
+                                    Long.valueOf(messageBufferText.remaining()),
+                                    Long.valueOf(maxMessageSize))));
+                }
+            }
+
+            try {
+                if (textMsgHandler instanceof MessageHandler.Partial<?>) {
+                    ((MessageHandler.Partial<String>) textMsgHandler).onMessage(
+                            messageBufferText.toString(), last);
+                } else {
+                    // Caller ensures last == true if this branch is used
+                    ((MessageHandler.Whole<String>) textMsgHandler).onMessage(
+                            messageBufferText.toString());
+                }
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                wsSession.getLocal().onError(wsSession, t);
+            } finally {
+                messageBufferText.clear();
+            }
+        }
+    }
+
+
+    private boolean processDataText() throws IOException {
+        // Copy the available data to the buffer
+        while (!appendPayloadToMessage(messageBufferBinary)) {
+            // Frame not complete - we ran out of something
+            // Convert bytes to UTF-8
+            messageBufferBinary.flip();
+            while (true) {
+                CoderResult cr = utf8DecoderMessage.decode(
+                        messageBufferBinary, messageBufferText, false);
+                if (cr.isError()) {
+                    throw new WsIOException(new CloseReason(
+                            CloseCodes.NOT_CONSISTENT,
+                            sm.getString("wsFrame.invalidUtf8")));
+                } else if (cr.isOverflow()) {
+                    // Ran out of space in text buffer - flush it
+                    if (usePartial()) {
+                        messageBufferText.flip();
+                        sendMessageText(false);
+                        messageBufferText.clear();
+                    } else {
+                        throw new WsIOException(new CloseReason(
+                                CloseCodes.TOO_BIG,
+                                sm.getString("wsFrame.textMessageTooBig")));
+                    }
+                } else if (cr.isUnderflow()) {
+                    // Need more input
+                    // Compact what we have to create as much space as possible
+                    messageBufferBinary.compact();
+
+                    // What did we run out of?
+                    if (readPos == writePos) {
+                        // Ran out of input data - get some more
+                        return false;
+                    } else {
+                        // Ran out of message buffer - exit inner loop and
+                        // refill
+                        break;
+                    }
+                }
+            }
+        }
+
+        messageBufferBinary.flip();
+        boolean last = false;
+        // Frame is fully received
+        // Convert bytes to UTF-8
+        while (true) {
+            CoderResult cr = utf8DecoderMessage.decode(messageBufferBinary,
+                    messageBufferText, last);
+            if (cr.isError()) {
+                throw new WsIOException(new CloseReason(
+                        CloseCodes.NOT_CONSISTENT,
+                        sm.getString("wsFrame.invalidUtf8")));
+            } else if (cr.isOverflow()) {
+                // Ran out of space in text buffer - flush it
+                if (usePartial()) {
+                    messageBufferText.flip();
+                    sendMessageText(false);
+                    messageBufferText.clear();
+                } else {
+                    throw new WsIOException(new CloseReason(
+                            CloseCodes.TOO_BIG,
+                            sm.getString("wsFrame.textMessageTooBig")));
+                }
+            } else if (cr.isUnderflow() & !last) {
+                // End of frame and possible message as well.
+
+                if (continuationExpected) {
+                    // If partial messages are supported, send what we have
+                    // managed to decode
+                    if (usePartial()) {
+                        messageBufferText.flip();
+                        sendMessageText(false);
+                        messageBufferText.clear();
+                    }
+                    messageBufferBinary.compact();
+                    newFrame();
+                    // Process next frame
+                    return true;
+                } else {
+                    // Make sure coder has flushed all output
+                    last = true;
+                }
+            } else {
+                // End of message
+                messageBufferText.flip();
+                sendMessageText(true);
+
+                newMessage();
+                return true;
+            }
+        }
+    }
+
+
+    private boolean processDataBinary() throws IOException {
+        // Copy the available data to the buffer
+        while (!appendPayloadToMessage(messageBufferBinary)) {
+            // Frame not complete - what did we run out of?
+            if (readPos == writePos) {
+                // Ran out of input data - get some more
+                return false;
+            } else {
+                // Ran out of message buffer - flush it
+                if (!usePartial()) {
+                    CloseReason cr = new CloseReason(CloseCodes.TOO_BIG,
+                            sm.getString("wsFrame.bufferTooSmall",
+                                    Integer.valueOf(
+                                            messageBufferBinary.capacity()),
+                                    Long.valueOf(payloadLength)));
+                    throw new WsIOException(cr);
+                }
+                messageBufferBinary.flip();
+                ByteBuffer copy =
+                        ByteBuffer.allocate(messageBufferBinary.limit());
+                copy.put(messageBufferBinary);
+                copy.flip();
+                sendMessageBinary(copy, false);
+                messageBufferBinary.clear();
+            }
+        }
+
+        // Frame is fully received
+        // Send the message if either:
+        // - partial messages are supported
+        // - the message is complete
+        if (usePartial() || !continuationExpected) {
+            messageBufferBinary.flip();
+            ByteBuffer copy =
+                    ByteBuffer.allocate(messageBufferBinary.limit());
+            copy.put(messageBufferBinary);
+            copy.flip();
+            sendMessageBinary(copy, !continuationExpected);
+            messageBufferBinary.clear();
+        }
+
+        if (continuationExpected) {
+            // More data for this message expected, start a new frame
+            newFrame();
+        } else {
+            // Message is complete, start a new message
+            newMessage();
+        }
+
+        return true;
+    }
+
+
+    @SuppressWarnings("unchecked")
+    private void sendMessageBinary(ByteBuffer msg, boolean last)
+            throws WsIOException {
+        if (binaryMsgHandler != null) {
+            if (binaryMsgHandler instanceof WrappedMessageHandler) {
+                long maxMessageSize =
+                        ((WrappedMessageHandler) binaryMsgHandler).getMaxMessageSize();
+                if (maxMessageSize > -1 && msg.remaining() > maxMessageSize) {
+                    throw new WsIOException(new CloseReason(CloseCodes.TOO_BIG,
+                            sm.getString("wsFrame.messageTooBig",
+                                    Long.valueOf(msg.remaining()),
+                                    Long.valueOf(maxMessageSize))));
+                }
+            }
+            try {
+                if (binaryMsgHandler instanceof MessageHandler.Partial<?>) {
+                    ((MessageHandler.Partial<ByteBuffer>) binaryMsgHandler).onMessage(msg, last);
+                } else {
+                    // Caller ensures last == true if this branch is used
+                    ((MessageHandler.Whole<ByteBuffer>) binaryMsgHandler).onMessage(msg);
+                }
+            } catch(Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                wsSession.getLocal().onError(wsSession, t);
+            }
+        }
+    }
+
+
+    private void newMessage() {
+        messageBufferBinary.clear();
+        messageBufferText.clear();
+        utf8DecoderMessage.reset();
+        continuationExpected = false;
+        newFrame();
+    }
+
+
+    private void newFrame() {
+        if (readPos == writePos) {
+            readPos = 0;
+            writePos = 0;
+        }
+
+        maskIndex = 0;
+        payloadWritten = 0;
+        state = State.NEW_FRAME;
+
+        // These get reset in processInitialHeader()
+        // fin, rsv, opCode, payloadLength, mask
+
+        checkRoomHeaders();
+    }
+
+
+    private void checkRoomHeaders() {
+        // Is the start of the current frame too near the end of the input
+        // buffer?
+        if (inputBuffer.length - readPos < 131) {
+            // Limit based on a control frame with a full payload
+            makeRoom();
+        }
+    }
+
+
+    private void checkRoomPayload() {
+        if (inputBuffer.length - readPos - payloadLength + payloadWritten < 0) {
+            makeRoom();
+        }
+    }
+
+
+    private void makeRoom() {
+        System.arraycopy(inputBuffer, readPos, inputBuffer, 0,
+                writePos - readPos);
+        writePos = writePos - readPos;
+        readPos = 0;
+    }
+
+
+    private boolean usePartial() {
+        if (Util.isControl(opCode)) {
+            return false;
+        } else if (textMessage) {
+            if (textMsgHandler != null) {
+                return textMsgHandler instanceof MessageHandler.Partial<?>;
+            }
+            return false;
+        } else {
+            // Must be binary
+            if (binaryMsgHandler != null) {
+                return binaryMsgHandler instanceof MessageHandler.Partial<?>;
+            }
+            return false;
+        }
+    }
+
+
+    private boolean appendPayloadToMessage(ByteBuffer dest) {
+        if (isMasked()) {
+            while (payloadWritten < payloadLength && readPos < writePos &&
+                    dest.hasRemaining()) {
+                byte b = (byte) ((inputBuffer[readPos] ^ mask[maskIndex]) & 0xFF);
+                maskIndex++;
+                if (maskIndex == 4) {
+                    maskIndex = 0;
+                }
+                readPos++;
+                payloadWritten++;
+                dest.put(b);
+            }
+            return (payloadWritten == payloadLength);
+        } else {
+            long toWrite = Math.min(
+                    payloadLength - payloadWritten, writePos - readPos);
+            toWrite = Math.min(toWrite, dest.remaining());
+
+            dest.put(inputBuffer, readPos, (int) toWrite);
+            readPos += toWrite;
+            payloadWritten += toWrite;
+            return (payloadWritten == payloadLength);
+
+        }
+    }
+
+
+    protected static long byteArrayToLong(byte[] b, int start, int len)
+            throws IOException {
+        if (len > 8) {
+            throw new IOException(sm.getString("wsFrame.byteToLongFail",
+                    Long.valueOf(len)));
+        }
+        int shift = 0;
+        long result = 0;
+        for (int i = start + len - 1; i >= start; i--) {
+            result = result + ((b[i] & 0xFF) << shift);
+            shift += 8;
+        }
+        return result;
+    }
+
+
+    protected boolean isOpen() {
+        return open;
+    }
+
+
+    private static enum State {
+        NEW_FRAME, PARTIAL_HEADER, DATA
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/WsFrameClient.java b/java/org/apache/tomcat/websocket/WsFrameClient.java
new file mode 100644
index 0000000..bd40d43
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/WsFrameClient.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.CompletionHandler;
+
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+
+public class WsFrameClient extends WsFrameBase {
+
+    private final AsyncChannelWrapper channel;
+    private final CompletionHandler<Integer,Void> handler;
+    // Not final as it may need to be re-sized
+    private ByteBuffer response;
+
+    public WsFrameClient(ByteBuffer response, AsyncChannelWrapper channel,
+            WsSession wsSession) {
+        super(wsSession);
+        this.response = response;
+        this.channel = channel;
+        this.handler = new WsFrameClientCompletionHandler();
+
+        try {
+            processSocketRead();
+        } catch (IOException e) {
+            close(e);
+        }
+    }
+
+
+    private void processSocketRead() throws IOException {
+
+        while (response.hasRemaining()) {
+            int remaining = response.remaining();
+
+            int toCopy = Math.min(remaining, inputBuffer.length - writePos);
+
+            // Copy remaining bytes read in HTTP phase to input buffer used by
+            // frame processing
+            response.get(inputBuffer, writePos, toCopy);
+            writePos += toCopy;
+
+            // Process the data we have
+            processInputBuffer();
+        }
+        response.clear();
+
+        // Get some more data
+        if (isOpen()) {
+            channel.read(response, null, handler);
+        }
+    }
+
+
+    private final void close(Throwable t) {
+        CloseReason cr;
+        if (t instanceof WsIOException) {
+            cr = ((WsIOException) t).getCloseReason();
+        } else {
+            cr = new CloseReason(
+                CloseCodes.CLOSED_ABNORMALLY, t.getMessage());
+        }
+
+        try {
+            wsSession.close(cr);
+        } catch (IOException ignore) {
+            // Ignore
+        }
+    }
+
+
+    @Override
+    protected boolean isMasked() {
+        // Data is from the server so it is not masked
+        return false;
+    }
+
+
+    private class WsFrameClientCompletionHandler
+            implements CompletionHandler<Integer,Void> {
+
+        @Override
+        public void completed(Integer result, Void attachment) {
+            response.flip();
+            try {
+                processSocketRead();
+            } catch (IOException e) {
+                close(e);
+            }
+        }
+
+        @Override
+        public void failed(Throwable exc, Void attachment) {
+            if (exc instanceof ReadBufferOverflowException) {
+                // response will be empty if this exception is thrown
+                response = ByteBuffer.allocate(
+                        ((ReadBufferOverflowException) exc).getMinBufferSize());
+                response.flip();
+                try {
+                    processSocketRead();
+                } catch (IOException e) {
+                    close(e);
+                }
+            } else {
+                close(exc);
+            }
+        }
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java b/java/org/apache/tomcat/websocket/WsHandshakeResponse.java
similarity index 53%
copy from java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
copy to java/org/apache/tomcat/websocket/WsHandshakeResponse.java
index 813bea8..df2e271 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
+++ b/java/org/apache/tomcat/websocket/WsHandshakeResponse.java
@@ -6,7 +6,7 @@
  * (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
+ *     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,
@@ -14,35 +14,34 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.coyote.http11.upgrade;
+package org.apache.tomcat.websocket;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
+import javax.websocket.HandshakeResponse;
 
 /**
- * Allows data to be written to the upgraded connection.
+ * Represents the response to a WebSocket handshake.
  */
-public class UpgradeOutbound extends OutputStream {
+public class WsHandshakeResponse implements HandshakeResponse {
 
-    @Override
-    public void flush() throws IOException {
-        processor.flush();
-    }
+    private final Map<String,List<String>> headers;
 
-    private final UpgradeProcessor<?> processor;
 
-    public UpgradeOutbound(UpgradeProcessor<?> processor) {
-        this.processor = processor;
+    public WsHandshakeResponse() {
+        this(new HashMap<String,List<String>>());
     }
 
-    @Override
-    public void write(int b) throws IOException {
-        processor.write(b);
+
+    public WsHandshakeResponse(Map<String,List<String>> headers) {
+        this.headers = headers;
     }
 
+
     @Override
-    public void write(byte[] b, int off, int len) throws IOException {
-        processor.write(b, off, len);
+    public Map<String,List<String>> getHeaders() {
+        return headers;
     }
 }
diff --git a/java/org/apache/tomcat/util/net/SocketStatus.java b/java/org/apache/tomcat/websocket/WsIOException.java
similarity index 57%
copy from java/org/apache/tomcat/util/net/SocketStatus.java
copy to java/org/apache/tomcat/websocket/WsIOException.java
index bf53c2b..0b7b1d4 100644
--- a/java/org/apache/tomcat/util/net/SocketStatus.java
+++ b/java/org/apache/tomcat/websocket/WsIOException.java
@@ -14,14 +14,28 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
+package org.apache.tomcat.websocket;
 
-package org.apache.tomcat.util.net;
+import java.io.IOException;
+
+import javax.websocket.CloseReason;
 
 /**
- * Someone, please change the enum name.
- *
- * @author remm
+ * Allows the WebSocket implementation to throw an {@link IOException} that
+ * includes a {@link CloseReason} specific to the error that can be passed back
+ * to the client.
  */
-public enum SocketStatus {
-    OPEN, STOP, TIMEOUT, DISCONNECT, ERROR
+public class WsIOException extends IOException {
+
+    private static final long serialVersionUID = 1L;
+
+    private final CloseReason closeReason;
+
+    public WsIOException(CloseReason closeReason) {
+        this.closeReason = closeReason;
+    }
+
+    public CloseReason getCloseReason() {
+        return closeReason;
+    }
 }
diff --git a/java/javax/annotation/security/DeclareRoles.java b/java/org/apache/tomcat/websocket/WsPongMessage.java
similarity index 57%
copy from java/javax/annotation/security/DeclareRoles.java
copy to java/org/apache/tomcat/websocket/WsPongMessage.java
index d5d214a..c3a6c33 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/java/org/apache/tomcat/websocket/WsPongMessage.java
@@ -5,27 +5,35 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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.apache.tomcat.websocket;
 
+import java.nio.ByteBuffer;
 
-package javax.annotation.security;
+import javax.websocket.PongMessage;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+public class WsPongMessage implements PongMessage {
 
- at Target({ElementType.TYPE})
- at Retention(RetentionPolicy.RUNTIME)
+    private final ByteBuffer applicationData;
 
-public @interface DeclareRoles {
-    public String[] value();
+
+    public WsPongMessage(ByteBuffer applicationData) {
+        byte[] dst = new byte[applicationData.limit()];
+        applicationData.get(dst);
+        this.applicationData = ByteBuffer.wrap(dst);
+    }
+
+
+    @Override
+    public ByteBuffer getApplicationData() {
+        return applicationData;
+    }
 }
diff --git a/java/org/apache/tomcat/websocket/WsRemoteEndpointAsync.java b/java/org/apache/tomcat/websocket/WsRemoteEndpointAsync.java
new file mode 100644
index 0000000..964223b
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/WsRemoteEndpointAsync.java
@@ -0,0 +1,79 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.Future;
+
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.SendHandler;
+
+public class WsRemoteEndpointAsync extends WsRemoteEndpointBase
+        implements RemoteEndpoint.Async {
+
+    WsRemoteEndpointAsync(WsRemoteEndpointImplBase base) {
+        super(base);
+    }
+
+
+    @Override
+    public long getSendTimeout() {
+        return base.getSendTimeout();
+    }
+
+
+    @Override
+    public void setSendTimeout(long timeout) {
+        base.setSendTimeout(timeout);
+    }
+
+
+    @Override
+    public void sendText(String text, SendHandler completion) {
+        base.sendStringByCompletion(text, completion);
+    }
+
+
+    @Override
+    public Future<Void> sendText(String text) {
+        return base.sendStringByFuture(text);
+    }
+
+
+    @Override
+    public Future<Void> sendBinary(ByteBuffer data) {
+        return base.sendBytesByFuture(data);
+    }
+
+
+    @Override
+    public void sendBinary(ByteBuffer data, SendHandler completion) {
+        base.sendBytesByCompletion(data, completion);
+    }
+
+
+    @Override
+    public Future<Void> sendObject(Object obj) {
+        return base.sendObjectByFuture(obj);
+    }
+
+
+    @Override
+    public void sendObject(Object obj, SendHandler completion) {
+        base.sendObjectByCompletion(obj, completion);
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/WsRemoteEndpointBase.java b/java/org/apache/tomcat/websocket/WsRemoteEndpointBase.java
new file mode 100644
index 0000000..e883c2c
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/WsRemoteEndpointBase.java
@@ -0,0 +1,64 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import javax.websocket.RemoteEndpoint;
+
+public abstract class WsRemoteEndpointBase implements RemoteEndpoint {
+
+    protected final WsRemoteEndpointImplBase base;
+
+
+    WsRemoteEndpointBase(WsRemoteEndpointImplBase base) {
+        this.base = base;
+    }
+
+
+    @Override
+    public final void setBatchingAllowed(boolean batchingAllowed) throws IOException {
+        base.setBatchingAllowed(batchingAllowed);
+    }
+
+
+    @Override
+    public final boolean getBatchingAllowed() {
+        return base.getBatchingAllowed();
+    }
+
+
+    @Override
+    public final void flushBatch() throws IOException {
+        base.flushBatch();
+    }
+
+
+    @Override
+    public final void sendPing(ByteBuffer applicationData) throws IOException,
+            IllegalArgumentException {
+        base.sendPing(applicationData);
+    }
+
+
+    @Override
+    public final void sendPong(ByteBuffer applicationData) throws IOException,
+            IllegalArgumentException {
+        base.sendPong(applicationData);
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/WsRemoteEndpointBasic.java b/java/org/apache/tomcat/websocket/WsRemoteEndpointBasic.java
new file mode 100644
index 0000000..b0fc050
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/WsRemoteEndpointBasic.java
@@ -0,0 +1,76 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.RemoteEndpoint;
+
+public class WsRemoteEndpointBasic extends WsRemoteEndpointBase
+        implements RemoteEndpoint.Basic {
+
+    WsRemoteEndpointBasic(WsRemoteEndpointImplBase base) {
+        super(base);
+    }
+
+
+    @Override
+    public void sendText(String text) throws IOException {
+        base.sendString(text);
+    }
+
+
+    @Override
+    public void sendBinary(ByteBuffer data) throws IOException {
+        base.sendBytes(data);
+    }
+
+
+    @Override
+    public void sendText(String fragment, boolean isLast) throws IOException {
+        base.sendPartialString(fragment, isLast);
+    }
+
+
+    @Override
+    public void sendBinary(ByteBuffer partialByte, boolean isLast)
+            throws IOException {
+        base.sendPartialBytes(partialByte, isLast);
+    }
+
+
+    @Override
+    public OutputStream getSendStream() throws IOException {
+        return base.getSendStream();
+    }
+
+
+    @Override
+    public Writer getSendWriter() throws IOException {
+        return base.getSendWriter();
+    }
+
+
+    @Override
+    public void sendObject(Object o) throws IOException, EncodeException {
+        base.sendObject(o);
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java b/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
new file mode 100644
index 0000000..b90d50f
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
@@ -0,0 +1,949 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+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;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.SendHandler;
+import javax.websocket.SendResult;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.buf.Utf8Encoder;
+import org.apache.tomcat.util.res.StringManager;
+
+public abstract class WsRemoteEndpointImplBase implements RemoteEndpoint {
+
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+
+    // Milliseconds so this is 20 seconds
+    private static final long DEFAULT_BLOCKING_SEND_TIMEOUT = 20 * 1000;
+
+    public static final String BLOCKING_SEND_TIMEOUT_PROPERTY =
+            "org.apache.tomcat.websocket.BLOCKING_SEND_TIMEOUT";
+
+    private final Log log = LogFactory.getLog(WsRemoteEndpointImplBase.class);
+
+    private boolean messagePartInProgress = false;
+    private final Queue<MessagePart> messagePartQueue = new ArrayDeque<MessagePart>();
+    private final Object messagePartLock = new Object();
+    private boolean dataMessageInProgress = false;
+
+    // State
+    private boolean closed = false;
+    private boolean fragmented = false;
+    private boolean nextFragmented = false;
+    private boolean text = false;
+    private boolean nextText = false;
+
+    // Max size of WebSocket header is 14 bytes
+    private final ByteBuffer headerBuffer = ByteBuffer.allocate(14);
+    private final ByteBuffer outputBuffer = ByteBuffer.allocate(8192);
+    private final CharsetEncoder encoder = new Utf8Encoder();
+    private final ByteBuffer encoderBuffer = ByteBuffer.allocate(8192);
+    private final AtomicBoolean batchingAllowed = new AtomicBoolean(false);
+    private volatile long sendTimeout = -1;
+    private WsSession wsSession;
+    private List<EncoderEntry> encoderEntries = new ArrayList<EncoderEntry>();
+
+    public long getSendTimeout() {
+        return sendTimeout;
+    }
+
+
+    public void setSendTimeout(long timeout) {
+        this.sendTimeout = timeout;
+    }
+
+
+    @Override
+    public void setBatchingAllowed(boolean batchingAllowed) throws IOException {
+        boolean oldValue = this.batchingAllowed.getAndSet(batchingAllowed);
+
+        if (oldValue && !batchingAllowed) {
+            flushBatch();
+        }
+    }
+
+
+    @Override
+    public boolean getBatchingAllowed() {
+        return batchingAllowed.get();
+    }
+
+
+    @Override
+    public void flushBatch() throws IOException {
+        startMessageBlock(Constants.INTERNAL_OPCODE_FLUSH, null, true);
+    }
+
+
+    public void sendBytes(ByteBuffer data) throws IOException {
+        startMessageBlock(Constants.OPCODE_BINARY, data, true);
+    }
+
+
+    public Future<Void> sendBytesByFuture(ByteBuffer data) {
+        FutureToSendHandler f2sh = new FutureToSendHandler(wsSession);
+        sendBytesByCompletion(data, f2sh);
+        return f2sh;
+    }
+
+
+    public void sendBytesByCompletion(ByteBuffer data, SendHandler handler) {
+        startMessage(Constants.OPCODE_BINARY, data, true, handler);
+    }
+
+
+    public void sendPartialBytes(ByteBuffer partialByte, boolean last)
+            throws IOException {
+        startMessageBlock(Constants.OPCODE_BINARY, partialByte, last);
+    }
+
+
+    @Override
+    public void sendPing(ByteBuffer applicationData) throws IOException,
+            IllegalArgumentException {
+        startMessageBlock(Constants.OPCODE_PING, applicationData, true);
+    }
+
+
+    @Override
+    public void sendPong(ByteBuffer applicationData) throws IOException,
+            IllegalArgumentException {
+        startMessageBlock(Constants.OPCODE_PONG, applicationData, true);
+    }
+
+
+    public void sendString(String text) throws IOException {
+        sendPartialString(CharBuffer.wrap(text), true);
+    }
+
+
+    public Future<Void> sendStringByFuture(String text) {
+        FutureToSendHandler f2sh = new FutureToSendHandler(wsSession);
+        sendStringByCompletion(text, f2sh);
+        return f2sh;
+    }
+
+
+    public void sendStringByCompletion(String text, SendHandler handler) {
+        TextMessageSendHandler tmsh = new TextMessageSendHandler(handler,
+                CharBuffer.wrap(text), true, encoder, encoderBuffer, this);
+        tmsh.write();
+    }
+
+
+    public void sendPartialString(String fragment, boolean isLast)
+            throws IOException {
+        sendPartialString(CharBuffer.wrap(fragment), isLast);
+    }
+
+
+    public OutputStream getSendStream() {
+        return new WsOutputStream(this);
+    }
+
+
+    public Writer getSendWriter() {
+        return new WsWriter(this);
+    }
+
+
+    void sendPartialString(CharBuffer part, boolean last) throws IOException {
+        try {
+            // Get the timeout before we send the message. The message may
+            // trigger a session close and depending on timing the client
+            // session may close before we can read the timeout.
+            long timeout = getBlockingSendTimeout();
+            FutureToSendHandler f2sh = new FutureToSendHandler(wsSession);
+            TextMessageSendHandler tmsh = new TextMessageSendHandler(f2sh, part,
+                    last, encoder, encoderBuffer, this);
+            tmsh.write();
+            if (timeout == -1) {
+                f2sh.get();
+            } else {
+                f2sh.get(timeout, TimeUnit.MILLISECONDS);
+            }
+        } catch (InterruptedException e) {
+            throw new IOException(e);
+        } catch (ExecutionException e) {
+            throw new IOException(e);
+        } catch (TimeoutException e) {
+            throw new IOException(e);
+        }
+    }
+
+
+    void startMessageBlock(byte opCode, ByteBuffer payload, boolean last)
+            throws IOException {
+        // Get the timeout before we send the message. The message may
+        // trigger a session close and depending on timing the client
+        // session may close before we can read the timeout.
+        long timeout = getBlockingSendTimeout();
+        FutureToSendHandler f2sh = new FutureToSendHandler(wsSession);
+        startMessage(opCode, payload, last, f2sh);
+        try {
+            if (timeout == -1) {
+                f2sh.get();
+            } else {
+                f2sh.get(timeout, TimeUnit.MILLISECONDS);
+            }
+        } catch (InterruptedException e) {
+            throw new IOException(e);
+        } catch (ExecutionException e) {
+            throw new IOException(e);
+        } catch (TimeoutException e) {
+            throw new IOException(e);
+        }
+    }
+
+
+    void startMessage(byte opCode, ByteBuffer payload, boolean last,
+            SendHandler handler) {
+
+        wsSession.updateLastActive();
+
+        MessagePart mp = new MessagePart(opCode, payload, last, handler, this);
+
+        synchronized (messagePartLock) {
+            if (Constants.OPCODE_CLOSE == mp.getOpCode()) {
+                try {
+                    setBatchingAllowed(false);
+                } catch (IOException e) {
+                    log.warn(sm.getString(
+                            "wsRemoteEndpoint.flushOnCloseFailed"), e);
+                }
+            }
+            if (messagePartInProgress) {
+                if (!Util.isControl(opCode)) {
+                    if (dataMessageInProgress) {
+                        throw new IllegalStateException(
+                                sm.getString("wsRemoteEndpoint.inProgress"));
+                    } else {
+                        dataMessageInProgress = true;
+                    }
+                }
+                messagePartQueue.add(mp);
+            } else {
+                messagePartInProgress = true;
+                writeMessagePart(mp);
+            }
+        }
+    }
+
+
+    void endMessage(SendHandler handler, SendResult result,
+            boolean dataMessage) {
+        synchronized (messagePartLock) {
+
+            fragmented = nextFragmented;
+            text = nextText;
+
+            if (dataMessage) {
+                dataMessageInProgress = false;
+            }
+            MessagePart mpNext = messagePartQueue.poll();
+            if (mpNext == null) {
+                messagePartInProgress = false;
+            } else {
+                writeMessagePart(mpNext);
+            }
+        }
+
+        wsSession.updateLastActive();
+
+        handler.onResult(result);
+    }
+
+
+    void writeMessagePart(MessagePart mp) {
+
+        if (closed) {
+            throw new IllegalStateException(
+                    sm.getString("wsRemoteEndpoint.closed"));
+        }
+
+        if (Constants.INTERNAL_OPCODE_FLUSH == mp.getOpCode()) {
+            nextFragmented = fragmented;
+            nextText = text;
+            doWrite(mp.getHandler(), outputBuffer);
+            return;
+        }
+
+        // Control messages may be sent in the middle of fragmented message
+        // so they have no effect on the fragmented or text flags
+        boolean first;
+        if (Util.isControl(mp.getOpCode())) {
+            nextFragmented = fragmented;
+            nextText = text;
+            if (mp.getOpCode() == Constants.OPCODE_CLOSE) {
+                closed = true;
+            }
+            first = true;
+        } else {
+            boolean isText = Util.isText(mp.getOpCode());
+
+            if (fragmented) {
+                // Currently fragmented
+                if (text != isText) {
+                    throw new IllegalStateException(
+                            sm.getString("wsRemoteEndpoint.changeType"));
+                }
+                nextText = text;
+                nextFragmented = !mp.isLast();
+                first = false;
+            } else {
+                // Wasn't fragmented. Might be now
+                if (mp.isLast()) {
+                    nextFragmented = false;
+                } else {
+                    nextFragmented = true;
+                    nextText = isText;
+                }
+                first = true;
+            }
+        }
+
+        byte[] mask;
+
+        if (isMasked()) {
+            mask = Util.generateMask();
+        } else {
+            mask = null;
+        }
+
+        headerBuffer.clear();
+        writeHeader(headerBuffer, mp.getOpCode(), mp.getPayload(), first,
+                mp.isLast(), isMasked(), mask);
+        headerBuffer.flip();
+
+        if (getBatchingAllowed() || isMasked()) {
+            // Need to write via output buffer
+            OutputBufferSendHandler obsh = new OutputBufferSendHandler(
+                    mp.getHandler(), headerBuffer, mp.getPayload(), mask,
+                    outputBuffer, !getBatchingAllowed(), this);
+            obsh.write();
+        } else {
+            // Can write directly
+            doWrite(mp.getHandler(), headerBuffer, mp.getPayload());
+        }
+
+    }
+
+
+    private long getBlockingSendTimeout() {
+        Object obj = wsSession.getUserProperties().get(
+                BLOCKING_SEND_TIMEOUT_PROPERTY);
+        Long userTimeout = null;
+        if (obj instanceof Long) {
+            userTimeout = (Long) obj;
+        }
+        if (userTimeout == null) {
+            return DEFAULT_BLOCKING_SEND_TIMEOUT;
+        } else {
+            return userTimeout.longValue();
+        }
+    }
+
+
+    private static class MessagePart {
+        private final byte opCode;
+        private final ByteBuffer payload;
+        private final boolean last;
+        private final SendHandler handler;
+
+        public MessagePart(byte opCode, ByteBuffer payload, boolean last,
+                SendHandler handler, WsRemoteEndpointImplBase endpoint) {
+            this.opCode = opCode;
+            this.payload = payload;
+            this.last = last;
+            this.handler = new EndMessageHandler(
+                    endpoint, handler, !Util.isControl(opCode));
+        }
+
+
+        public byte getOpCode() {
+            return opCode;
+        }
+
+
+        public ByteBuffer getPayload() {
+            return payload;
+        }
+
+
+        public boolean isLast() {
+            return last;
+        }
+
+
+        public SendHandler getHandler() {
+            return handler;
+        }
+    }
+
+
+    /**
+     * Wraps the user provided handler so that the end point is notified when
+     * the message is complete.
+     */
+    private static class EndMessageHandler implements SendHandler {
+
+        private final WsRemoteEndpointImplBase endpoint;
+        private final SendHandler handler;
+        private final boolean dataMessage;
+
+        public EndMessageHandler(WsRemoteEndpointImplBase endpoint,
+                SendHandler handler, boolean dataMessage) {
+            this.endpoint = endpoint;
+            this.handler = handler;
+            this.dataMessage = dataMessage;
+        }
+
+
+        @Override
+        public void onResult(SendResult result) {
+            endpoint.endMessage(handler, result, dataMessage);
+        }
+    }
+
+
+    public void sendObject(Object obj) throws IOException {
+        Future<Void> f = sendObjectByFuture(obj);
+        try {
+            f.get();
+        } catch (InterruptedException e) {
+            throw new IOException(e);
+        } catch (ExecutionException e) {
+            throw new IOException(e);
+        }
+    }
+
+    public Future<Void> sendObjectByFuture(Object obj) {
+        FutureToSendHandler f2sh = new FutureToSendHandler(wsSession);
+        sendObjectByCompletion(obj, f2sh);
+        return f2sh;
+    }
+
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public void sendObjectByCompletion(Object obj, SendHandler completion) {
+
+        if (Util.isPrimitive(obj.getClass())) {
+            String msg = obj.toString();
+            sendStringByCompletion(msg, completion);
+            return;
+        }
+
+        Encoder encoder = findEncoder(obj);
+
+        try {
+            if (encoder instanceof Encoder.Text) {
+                String msg = ((Encoder.Text) encoder).encode(obj);
+                sendStringByCompletion(msg, completion);
+            } else if (encoder instanceof Encoder.TextStream) {
+                Writer w = null;
+                try {
+                    w = getSendWriter();
+                    ((Encoder.TextStream) encoder).encode(obj, w);
+                } finally {
+                    if (w != null) {
+                        try {
+                            w.close();
+                        } catch (IOException ioe) {
+                            // Ignore
+                        }
+                    }
+                }
+                completion.onResult(new SendResult());
+            } else if (encoder instanceof Encoder.Binary) {
+                ByteBuffer msg = ((Encoder.Binary) encoder).encode(obj);
+                sendBytesByCompletion(msg, completion);
+            } else if (encoder instanceof Encoder.BinaryStream) {
+                OutputStream os = null;
+                try {
+                    os = getSendStream();
+                    ((Encoder.BinaryStream) encoder).encode(obj, os);
+                } finally {
+                    if (os != null) {
+                        try {
+                            os.close();
+                        } catch (IOException ioe) {
+                            // Ignore
+                        }
+                    }
+                }
+                completion.onResult(new SendResult());
+            } else {
+                throw new EncodeException(obj, sm.getString(
+                        "wsRemoteEndpoint.noEncoder", obj.getClass()));
+            }
+        } catch (EncodeException e) {
+            SendResult sr = new SendResult(e);
+            completion.onResult(sr);
+        } catch (IOException e) {
+            SendResult sr = new SendResult(e);
+            completion.onResult(sr);
+        }
+    }
+
+
+    protected void setSession(WsSession wsSession) {
+        this.wsSession = wsSession;
+    }
+
+
+    protected void setEncoders(EndpointConfig endpointConfig)
+            throws DeploymentException {
+        encoderEntries.clear();
+        for (Class<? extends Encoder> encoderClazz :
+                endpointConfig.getEncoders()) {
+            Encoder instance;
+            try {
+                instance = encoderClazz.newInstance();
+                instance.init(endpointConfig);
+            } catch (InstantiationException e) {
+                throw new DeploymentException(
+                        sm.getString("wsRemoteEndpoint.invalidEncoder",
+                                encoderClazz.getName()), e);
+            } catch (IllegalAccessException e) {
+                throw new DeploymentException(
+                        sm.getString("wsRemoteEndpoint.invalidEncoder",
+                                encoderClazz.getName()), e);
+            }
+            EncoderEntry entry = new EncoderEntry(
+                    Util.getEncoderType(encoderClazz), instance);
+            encoderEntries.add(entry);
+        }
+    }
+
+
+    private Encoder findEncoder(Object obj) {
+        for (EncoderEntry entry : encoderEntries) {
+            if (entry.getClazz().isAssignableFrom(obj.getClass())) {
+                return entry.getEncoder();
+            }
+        }
+        return null;
+    }
+
+
+    public final void close() {
+        for (EncoderEntry entry : encoderEntries) {
+            entry.getEncoder().destroy();
+        }
+        doClose();
+    }
+
+
+    protected abstract void doWrite(SendHandler handler, ByteBuffer... data);
+    protected abstract boolean isMasked();
+    protected abstract void doClose();
+
+    private static void writeHeader(ByteBuffer headerBuffer, byte opCode,
+            ByteBuffer payload, boolean first, boolean last, boolean masked,
+            byte[] mask) {
+
+        byte b = 0;
+
+        if (last) {
+            // Set the fin bit
+            b = -128;
+        }
+
+        if (first) {
+            // This is the first fragment of this message
+            b = (byte) (b + opCode);
+        }
+        // If not the first fragment, it is a continuation with opCode of zero
+
+        headerBuffer.put(b);
+
+        if (masked) {
+            b = (byte) 0x80;
+        } else {
+            b = 0;
+        }
+
+        // Next write the mask && length length
+        if (payload.limit() < 126) {
+            headerBuffer.put((byte) (payload.limit() | b));
+        } else if (payload.limit() < 65536) {
+            headerBuffer.put((byte) (126 | b));
+            headerBuffer.put((byte) (payload.limit() >>> 8));
+            headerBuffer.put((byte) (payload.limit() & 0xFF));
+        } else {
+            // Will never be more than 2^31-1
+            headerBuffer.put((byte) (127 | b));
+            headerBuffer.put((byte) 0);
+            headerBuffer.put((byte) 0);
+            headerBuffer.put((byte) 0);
+            headerBuffer.put((byte) 0);
+            headerBuffer.put((byte) (payload.limit() >>> 24));
+            headerBuffer.put((byte) (payload.limit() >>> 16));
+            headerBuffer.put((byte) (payload.limit() >>> 8));
+            headerBuffer.put((byte) (payload.limit() & 0xFF));
+        }
+        if (masked) {
+            headerBuffer.put(mask[0]);
+            headerBuffer.put(mask[1]);
+            headerBuffer.put(mask[2]);
+            headerBuffer.put(mask[3]);
+        }
+    }
+
+
+    private static class TextMessageSendHandler implements SendHandler {
+
+        private final SendHandler handler;
+        private final CharBuffer message;
+        private final boolean isLast;
+        private final CharsetEncoder encoder;
+        private final ByteBuffer buffer;
+        private final WsRemoteEndpointImplBase endpoint;
+        private volatile boolean isDone = false;
+
+        public TextMessageSendHandler(SendHandler handler, CharBuffer message,
+                boolean isLast, CharsetEncoder encoder,
+                ByteBuffer encoderBuffer, WsRemoteEndpointImplBase endpoint) {
+            this.handler = handler;
+            this.message = message;
+            this.isLast = isLast;
+            this.encoder = encoder.reset();
+            this.buffer = encoderBuffer;
+            this.endpoint = endpoint;
+        }
+
+        public void write() {
+            buffer.clear();
+            CoderResult cr = encoder.encode(message, buffer, true);
+            if (cr.isError()) {
+                throw new IllegalArgumentException(cr.toString());
+            }
+            isDone = !cr.isOverflow();
+            buffer.flip();
+            endpoint.startMessage(Constants.OPCODE_TEXT, buffer,
+                    isDone && isLast, this);
+        }
+
+        @Override
+        public void onResult(SendResult result) {
+            if (isDone || !result.isOK()) {
+                handler.onResult(result);
+            } else {
+                write();
+            }
+        }
+    }
+
+
+    /**
+     * Used to write data to the output buffer, flushing the buffer if it fills
+     * up.
+     */
+    private static class OutputBufferSendHandler implements SendHandler {
+
+        private final SendHandler handler;
+        private final ByteBuffer headerBuffer;
+        private final ByteBuffer payload;
+        private final byte[] mask;
+        private final ByteBuffer outputBuffer;
+        private final boolean flushRequired;
+        private final WsRemoteEndpointImplBase endpoint;
+        private int maskIndex = 0;
+
+        public OutputBufferSendHandler(SendHandler completion,
+                ByteBuffer headerBuffer, ByteBuffer payload, byte[] mask,
+                ByteBuffer outputBuffer, boolean flushRequired,
+                WsRemoteEndpointImplBase endpoint) {
+            this.handler = completion;
+            this.headerBuffer = headerBuffer;
+            this.payload = payload;
+            this.mask = mask;
+            this.outputBuffer = outputBuffer;
+            this.flushRequired = flushRequired;
+            this.endpoint = endpoint;
+        }
+
+        public void write() {
+            // Write the header
+            while (headerBuffer.hasRemaining() && outputBuffer.hasRemaining()) {
+                outputBuffer.put(headerBuffer.get());
+            }
+            if (headerBuffer.hasRemaining()) {
+                // Still more headers to write, need to flush
+                outputBuffer.flip();
+                endpoint.doWrite(this, outputBuffer);
+                return;
+            }
+
+            // Write the payload
+            int payloadLeft = payload.remaining();
+            int payloadLimit = payload.limit();
+            int outputSpace = outputBuffer.remaining();
+            int toWrite = payloadLeft;
+
+            if (payloadLeft > outputSpace) {
+                toWrite = outputSpace;
+                // Temporarily reduce the limit
+                payload.limit(payload.position() + toWrite);
+            }
+
+            if (mask == null) {
+                // Use a bulk copy
+                outputBuffer.put(payload);
+            } else {
+                for (int i = 0; i < toWrite; i++) {
+                    outputBuffer.put(
+                            (byte) (payload.get() ^ (mask[maskIndex++] & 0xFF)));
+                    if (maskIndex > 3) {
+                        maskIndex = 0;
+                    }
+                }
+            }
+
+            if (payloadLeft > outputSpace) {
+                // Restore the original limit
+                payload.limit(payloadLimit);
+                // Still more headers to write, need to flush
+                outputBuffer.flip();
+                endpoint.doWrite(this, outputBuffer);
+                return;
+            }
+
+            if (flushRequired) {
+                outputBuffer.flip();
+                if (outputBuffer.remaining() == 0) {
+                    handler.onResult(new SendResult());
+                } else {
+                    endpoint.doWrite(this, outputBuffer);
+                }
+            } else {
+                handler.onResult(new SendResult());
+            }
+        }
+
+        // ------------------------------------------------- SendHandler methods
+        @Override
+        public void onResult(SendResult result) {
+            if (result.isOK()) {
+                if (outputBuffer.hasRemaining()) {
+                    endpoint.doWrite(this, outputBuffer);
+                } else {
+                    outputBuffer.clear();
+                    write();
+                }
+            } else {
+                handler.onResult(result);
+            }
+        }
+    }
+
+    private static class WsOutputStream extends OutputStream {
+
+        private final WsRemoteEndpointImplBase endpoint;
+        private final ByteBuffer buffer = ByteBuffer.allocate(8192);
+        private final Object closeLock = new Object();
+        private volatile boolean closed = false;
+
+        public WsOutputStream(WsRemoteEndpointImplBase endpoint) {
+            this.endpoint = endpoint;
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            if (closed) {
+                throw new IllegalStateException(
+                        sm.getString("wsRemoteEndpoint.closedOutputStream"));
+            }
+
+            if (buffer.remaining() == 0) {
+                flush();
+            }
+            buffer.put((byte) b);
+        }
+
+        @Override
+        public void write(byte[] b, int off, int len) throws IOException {
+            if (closed) {
+                throw new IllegalStateException(
+                        sm.getString("wsRemoteEndpoint.closedOutputStream"));
+            }
+            if (len == 0) {
+                return;
+            }
+            if ((off < 0) || (off > b.length) || (len < 0) ||
+                ((off + len) > b.length) || ((off + len) < 0)) {
+                throw new IndexOutOfBoundsException();
+            }
+
+            if (buffer.remaining() == 0) {
+                flush();
+            }
+            int remaining = buffer.remaining();
+            int written = 0;
+
+            while (remaining < len - written) {
+                buffer.put(b, off + written, remaining);
+                written += remaining;
+                flush();
+                remaining = buffer.remaining();
+            }
+            buffer.put(b, off + written, len - written);
+        }
+
+        @Override
+        public void flush() throws IOException {
+            if (closed) {
+                throw new IllegalStateException(
+                        sm.getString("wsRemoteEndpoint.closedOutputStream"));
+            }
+
+            doWrite(false);
+        }
+
+        @Override
+        public void close() throws IOException {
+            synchronized (closeLock) {
+                if (closed) {
+                    return;
+                }
+                closed = true;
+            }
+
+            doWrite(true);
+        }
+
+        private void doWrite(boolean last) throws IOException {
+            buffer.flip();
+            endpoint.sendPartialBytes(buffer, last);
+            buffer.clear();
+        }
+    }
+
+
+    private static class WsWriter extends Writer {
+
+        private final WsRemoteEndpointImplBase endpoint;
+        private final CharBuffer buffer = CharBuffer.allocate(8192);
+        private final Object closeLock = new Object();
+        private volatile boolean closed = false;
+
+        public WsWriter(WsRemoteEndpointImplBase endpoint) {
+            this.endpoint = endpoint;
+        }
+
+        @Override
+        public void write(char[] cbuf, int off, int len) throws IOException {
+            if (closed) {
+                throw new IllegalStateException(
+                        sm.getString("wsRemoteEndpoint.closedWriter"));
+            }
+            if (len == 0) {
+                return;
+            }
+            if ((off < 0) || (off > cbuf.length) || (len < 0) ||
+                    ((off + len) > cbuf.length) || ((off + len) < 0)) {
+                throw new IndexOutOfBoundsException();
+            }
+
+            if (buffer.remaining() == 0) {
+                flush();
+            }
+            int remaining = buffer.remaining();
+            int written = 0;
+
+            while (remaining < len - written) {
+                buffer.put(cbuf, off + written, remaining);
+                written += remaining;
+                flush();
+                remaining = buffer.remaining();
+            }
+            buffer.put(cbuf, off + written, len - written);
+        }
+
+        @Override
+        public void flush() throws IOException {
+            if (closed) {
+                throw new IllegalStateException(
+                        sm.getString("wsRemoteEndpoint.closedWriter"));
+            }
+
+            doWrite(false);
+        }
+
+        @Override
+        public void close() throws IOException {
+            synchronized (closeLock) {
+                if (closed) {
+                    return;
+                }
+                closed = true;
+            }
+
+            doWrite(true);
+        }
+
+        private void doWrite(boolean last) throws IOException {
+            buffer.flip();
+            endpoint.sendPartialString(buffer, last);
+            buffer.clear();
+        }
+    }
+
+
+    private static class EncoderEntry {
+
+        private final Class<?> clazz;
+        private final Encoder encoder;
+
+        public EncoderEntry(Class<?> clazz, Encoder encoder) {
+            this.clazz = clazz;
+            this.encoder = encoder;
+        }
+
+        public Class<?> getClazz() {
+            return clazz;
+        }
+
+        public Encoder getEncoder() {
+            return encoder;
+        }
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/WsRemoteEndpointImplClient.java b/java/org/apache/tomcat/websocket/WsRemoteEndpointImplClient.java
new file mode 100644
index 0000000..30d8b5e
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/WsRemoteEndpointImplClient.java
@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.SendHandler;
+
+public class WsRemoteEndpointImplClient extends WsRemoteEndpointImplBase {
+
+    private final AsyncChannelWrapper channel;
+
+    public WsRemoteEndpointImplClient(AsyncChannelWrapper channel) {
+        this.channel = channel;
+    }
+
+
+    @Override
+    protected boolean isMasked() {
+        return true;
+    }
+
+
+    @Override
+    protected void doWrite(SendHandler handler, ByteBuffer... data) {
+        long timeout = getSendTimeout();
+        if (timeout < 1) {
+            timeout = Long.MAX_VALUE;
+
+        }
+        SendHandlerToCompletionHandler sh2ch =
+                new SendHandlerToCompletionHandler(handler);
+        channel.write(data, 0, data.length, timeout, TimeUnit.MILLISECONDS,
+                null, sh2ch);
+    }
+
+    @Override
+    protected void doClose() {
+        channel.close();
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/WsSession.java b/java/org/apache/tomcat/websocket/WsSession.java
new file mode 100644
index 0000000..e10c3b7
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/WsSession.java
@@ -0,0 +1,644 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Extension;
+import javax.websocket.MessageHandler;
+import javax.websocket.PongMessage;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.SendResult;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+public class WsSession implements Session {
+
+    // An ellipsis is a single character that looks like three periods in a row
+    // and is used to indicate a continuation.
+    private static final byte[] ELLIPSIS_BYTES =
+            "\u2026".getBytes(StandardCharsets.UTF_8);
+    // An ellipsis is three bytes in UTF-8
+    private static final int ELLIPSIS_BYTES_LEN = ELLIPSIS_BYTES.length;
+
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+    private static AtomicLong ids = new AtomicLong(0);
+
+    private final Log log = LogFactory.getLog(WsSession.class);
+
+    private final Endpoint localEndpoint;
+    private final WsRemoteEndpointImplBase wsRemoteEndpoint;
+    private final RemoteEndpoint.Async remoteEndpointAsync;
+    private final RemoteEndpoint.Basic remoteEndpointBasic;
+    private final ClassLoader applicationClassLoader;
+    private final WsWebSocketContainer webSocketContainer;
+    private final URI requestUri;
+    private final Map<String,List<String>> requestParameterMap;
+    private final String queryString;
+    private final Principal userPrincipal;
+    private final EndpointConfig endpointConfig;
+
+    private final String subProtocol;
+    private final Map<String,String> pathParameters;
+    private final boolean secure;
+    private final String httpSessionId;
+    private final String id;
+
+    // Expected to handle message types of <String> only
+    private MessageHandler textMessageHandler = null;
+    // Expected to handle message types of <ByteBuffer> only
+    private MessageHandler binaryMessageHandler = null;
+    private MessageHandler.Whole<PongMessage> pongMessageHandler = null;
+    private volatile State state = State.OPEN;
+    private final Object stateLock = new Object();
+    private final Map<String,Object> userProperties = new ConcurrentHashMap<String, Object>();
+    private volatile int maxBinaryMessageBufferSize =
+            Constants.DEFAULT_BUFFER_SIZE;
+    private volatile int maxTextMessageBufferSize =
+            Constants.DEFAULT_BUFFER_SIZE;
+    private volatile long maxIdleTimeout = 0;
+    private volatile long lastActive = System.currentTimeMillis();
+    private Map<FutureToSendHandler,FutureToSendHandler> futures =
+            new ConcurrentHashMap<FutureToSendHandler,FutureToSendHandler>();
+
+    /**
+     * Creates a new WebSocket session for communication between the two
+     * provided end points. The result of {@link Thread#getContextClassLoader()}
+     * at the time this constructor is called will be used when calling
+     * {@link Endpoint#onClose(Session, CloseReason)}.
+     *
+     * @param localEndpoint
+     * @param wsRemoteEndpoint
+     * @throws DeploymentException
+     */
+    public WsSession(Endpoint localEndpoint,
+            WsRemoteEndpointImplBase wsRemoteEndpoint,
+            WsWebSocketContainer wsWebSocketContainer,
+            URI requestUri, Map<String,List<String>> requestParameterMap,
+            String queryString, Principal userPrincipal, String httpSessionId,
+            String subProtocol, Map<String,String> pathParameters,
+            boolean secure, EndpointConfig endpointConfig)
+                    throws DeploymentException {
+        this.localEndpoint = localEndpoint;
+        this.wsRemoteEndpoint = wsRemoteEndpoint;
+        this.wsRemoteEndpoint.setSession(this);
+        this.remoteEndpointAsync = new WsRemoteEndpointAsync(wsRemoteEndpoint);
+        this.remoteEndpointBasic = new WsRemoteEndpointBasic(wsRemoteEndpoint);
+        this.webSocketContainer = wsWebSocketContainer;
+        applicationClassLoader = Thread.currentThread().getContextClassLoader();
+        wsRemoteEndpoint.setSendTimeout(
+                wsWebSocketContainer.getDefaultAsyncSendTimeout());
+        this.maxBinaryMessageBufferSize =
+                webSocketContainer.getDefaultMaxBinaryMessageBufferSize();
+        this.maxTextMessageBufferSize =
+                webSocketContainer.getDefaultMaxTextMessageBufferSize();
+        this.maxIdleTimeout =
+                webSocketContainer.getDefaultMaxSessionIdleTimeout();
+        this.requestUri = requestUri;
+        if (requestParameterMap == null) {
+            this.requestParameterMap = Collections.emptyMap();
+        } else {
+            this.requestParameterMap = requestParameterMap;
+        }
+        this.queryString = queryString;
+        this.userPrincipal = userPrincipal;
+        this.httpSessionId = httpSessionId;
+        if (subProtocol == null) {
+            this.subProtocol = "";
+        } else {
+            this.subProtocol = subProtocol;
+        }
+        this.pathParameters = pathParameters;
+        this.secure = secure;
+        this.wsRemoteEndpoint.setEncoders(endpointConfig);
+        this.endpointConfig = endpointConfig;
+
+        this.userProperties.putAll(endpointConfig.getUserProperties());
+        this.id = Long.toHexString(ids.getAndIncrement());
+    }
+
+
+    @Override
+    public WebSocketContainer getContainer() {
+        checkState();
+        return webSocketContainer;
+    }
+
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void addMessageHandler(MessageHandler listener) {
+
+        checkState();
+
+        // Message handlers that require decoders may map to text messages,
+        // binary messages, both or neither.
+
+        // The frame processing code expects binary message handlers to
+        // accept ByteBuffer
+
+        // Use the POJO message handler wrappers as they are designed to wrap
+        // arbitrary objects with MessageHandlers and can wrap MessageHandlers
+        // just as easily.
+
+        Set<MessageHandlerResult> mhResults =
+                Util.getMessageHandlers(listener, endpointConfig);
+
+        for (MessageHandlerResult mhResult : mhResults) {
+            switch (mhResult.getType()) {
+                case TEXT: {
+                    if (textMessageHandler != null) {
+                        throw new IllegalStateException(
+                                sm.getString("wsSession.duplicateHandlerText"));
+                    }
+                    textMessageHandler = mhResult.getHandler();
+                    break;
+                }
+                case BINARY: {
+                    if (binaryMessageHandler != null) {
+                        throw new IllegalStateException(
+                                sm.getString("wsSession.duplicateHandlerBinary"));
+                    }
+                    binaryMessageHandler = mhResult.getHandler();
+                    break;
+                }
+                case PONG: {
+                    if (pongMessageHandler != null) {
+                        throw new IllegalStateException(
+                                sm.getString("wsSession.duplicateHandlerPong"));
+                    }
+                    MessageHandler handler = mhResult.getHandler();
+                    if (handler instanceof MessageHandler.Whole<?>) {
+                        pongMessageHandler =
+                                (MessageHandler.Whole<PongMessage>) handler;
+                    } else {
+                        throw new IllegalStateException(
+                                sm.getString("wsSession.invalidHandlerTypePong"));
+                    }
+
+                    break;
+                }
+                default: {
+                    throw new IllegalArgumentException(sm.getString(
+                            "wsSession.unknownHandlerType", listener,
+                            mhResult.getType()));
+                }
+            }
+        }
+    }
+
+
+    @Override
+    public Set<MessageHandler> getMessageHandlers() {
+        checkState();
+        Set<MessageHandler> result = new HashSet<MessageHandler>();
+        if (binaryMessageHandler != null) {
+            result.add(binaryMessageHandler);
+        }
+        if (textMessageHandler != null) {
+            result.add(textMessageHandler);
+        }
+        if (pongMessageHandler != null) {
+            result.add(pongMessageHandler);
+        }
+        return result;
+    }
+
+
+    @Override
+    public void removeMessageHandler(MessageHandler listener) {
+        checkState();
+        if (listener == null) {
+            return;
+        }
+
+        MessageHandler wrapped = null;
+
+        if (listener instanceof WrappedMessageHandler) {
+            wrapped = ((WrappedMessageHandler) listener).getWrappedHandler();
+        }
+
+        if (wrapped == null) {
+            wrapped = listener;
+        }
+
+        boolean removed = false;
+        if (wrapped.equals(textMessageHandler) ||
+                listener.equals(textMessageHandler)) {
+            textMessageHandler = null;
+            removed = true;
+        }
+
+        if (listener.equals(binaryMessageHandler) ||
+                listener.equals(binaryMessageHandler)) {
+            binaryMessageHandler = null;
+            removed = true;
+        }
+
+        if (listener.equals(pongMessageHandler) ||
+                listener.equals(pongMessageHandler)) {
+            pongMessageHandler = null;
+            removed = true;
+        }
+
+        if (!removed) {
+            // ISE for now. Could swallow this silently / log this if the ISE
+            // becomes a problem
+            throw new IllegalStateException(
+                    sm.getString("wsSession.removeHandlerFailed", listener));
+        }
+    }
+
+
+    @Override
+    public String getProtocolVersion() {
+        checkState();
+        return Constants.WS_VERSION_HEADER_VALUE;
+    }
+
+
+    @Override
+    public String getNegotiatedSubprotocol() {
+        checkState();
+        return subProtocol;
+    }
+
+
+    @Override
+    public List<Extension> getNegotiatedExtensions() {
+        checkState();
+        return Collections.emptyList();
+    }
+
+
+    @Override
+    public boolean isSecure() {
+        checkState();
+        return secure;
+    }
+
+
+    @Override
+    public boolean isOpen() {
+        return state == State.OPEN;
+    }
+
+
+    @Override
+    public long getMaxIdleTimeout() {
+        checkState();
+        return maxIdleTimeout;
+    }
+
+
+    @Override
+    public void setMaxIdleTimeout(long timeout) {
+        checkState();
+        this.maxIdleTimeout = timeout;
+    }
+
+
+    @Override
+    public void setMaxBinaryMessageBufferSize(int max) {
+        checkState();
+        this.maxBinaryMessageBufferSize = max;
+    }
+
+
+    @Override
+    public int getMaxBinaryMessageBufferSize() {
+        checkState();
+        return maxBinaryMessageBufferSize;
+    }
+
+
+    @Override
+    public void setMaxTextMessageBufferSize(int max) {
+        checkState();
+        this.maxTextMessageBufferSize = max;
+    }
+
+
+    @Override
+    public int getMaxTextMessageBufferSize() {
+        checkState();
+        return maxTextMessageBufferSize;
+    }
+
+
+    @Override
+    public Set<Session> getOpenSessions() {
+        checkState();
+        return webSocketContainer.getOpenSessions(localEndpoint.getClass());
+    }
+
+
+    @Override
+    public RemoteEndpoint.Async getAsyncRemote() {
+        checkState();
+        return remoteEndpointAsync;
+    }
+
+
+    @Override
+    public RemoteEndpoint.Basic getBasicRemote() {
+        checkState();
+        return remoteEndpointBasic;
+    }
+
+
+    @Override
+    public void close() throws IOException {
+        close(new CloseReason(CloseCodes.NORMAL_CLOSURE, ""));
+    }
+
+
+    @Override
+    public void close(CloseReason closeReason) throws IOException {
+        doClose(closeReason, closeReason);
+    }
+
+
+    /**
+     * WebSocket 1.0. Section 2.1.5.
+     * Need internal close method as spec requires that the local endpoint
+     * receives a 1006 on timeout.
+     */
+    private void doClose(CloseReason closeReasonMessage,
+            CloseReason closeReasonLocal) {
+        // Double-checked locking. OK because state is volatile
+        if (state != State.OPEN) {
+            return;
+        }
+
+        synchronized (stateLock) {
+            if (state != State.OPEN) {
+                return;
+            }
+
+            state = State.CLOSING;
+
+            sendCloseMessage(closeReasonMessage);
+            fireEndpointOnClose(closeReasonLocal);
+
+            state = State.CLOSED;
+        }
+
+        IOException ioe = new IOException(sm.getString("wsSession.messageFailed"));
+        SendResult sr = new SendResult(ioe);
+        for (FutureToSendHandler f2sh : futures.keySet()) {
+            f2sh.onResult(sr);
+        }
+    }
+
+
+    /**
+     * Called when a close message is received. Should only ever happen once.
+     * Also called after a protocol error when the ProtocolHandler needs to
+     * force the closing of the connection.
+     */
+    public void onClose(CloseReason closeReason) {
+
+        synchronized (stateLock) {
+            if (state == State.OPEN) {
+                sendCloseMessage(closeReason);
+                fireEndpointOnClose(closeReason);
+                state = State.CLOSED;
+            }
+
+            // Close the socket
+            wsRemoteEndpoint.close();
+        }
+    }
+
+
+    private void fireEndpointOnClose(CloseReason closeReason) {
+
+        // Fire the onClose event
+        Thread t = Thread.currentThread();
+        ClassLoader cl = t.getContextClassLoader();
+        t.setContextClassLoader(applicationClassLoader);
+        try {
+            localEndpoint.onClose(this, closeReason);
+        } finally {
+            t.setContextClassLoader(cl);
+        }
+    }
+
+
+    private void sendCloseMessage(CloseReason closeReason) {
+        // 125 is maximum size for the payload of a control message
+        ByteBuffer msg = ByteBuffer.allocate(125);
+        msg.putShort((short) closeReason.getCloseCode().getCode());
+
+        String reason = closeReason.getReasonPhrase();
+        if (reason != null && reason.length() > 0) {
+            appendCloseReasonWithTruncation(msg, reason);
+        }
+        msg.flip();
+        try {
+            wsRemoteEndpoint.startMessageBlock(
+                    Constants.OPCODE_CLOSE, msg, true);
+        } catch (IOException ioe) {
+            // Failed to send close message. Close the socket and let the caller
+            // deal with the Exception
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("wsSession.sendCloseFail"), ioe);
+            }
+            wsRemoteEndpoint.close();
+            localEndpoint.onError(this, ioe);
+        } finally {
+            webSocketContainer.unregisterSession(localEndpoint, this);
+        }
+    }
+
+
+    /**
+     * Use protected so unit tests can access this method directly.
+     */
+    protected static void appendCloseReasonWithTruncation(ByteBuffer msg,
+            String reason) {
+        // Once the close code has been added there are a maximum of 123 bytes
+        // left for the reason phrase. If it is truncated then care needs to be
+        // taken to ensure the bytes are not truncated in the middle of a
+        // multi-byte UTF-8 character.
+        byte[] reasonBytes = reason.getBytes(StandardCharsets.UTF_8);
+
+        if (reasonBytes.length  <= 123) {
+            // No need to truncate
+            msg.put(reasonBytes);
+        } else {
+            // Need to truncate
+            int remaining = 123 - ELLIPSIS_BYTES_LEN;
+            int pos = 0;
+            byte[] bytesNext = reason.substring(pos, pos + 1).getBytes(
+                    StandardCharsets.UTF_8);
+            while (remaining >= bytesNext.length) {
+                msg.put(bytesNext);
+                remaining -= bytesNext.length;
+                pos++;
+                bytesNext = reason.substring(pos, pos + 1).getBytes(
+                        StandardCharsets.UTF_8);
+            }
+            msg.put(ELLIPSIS_BYTES);
+        }
+    }
+
+
+    /**
+     * Make the session aware of a {@link FutureToSendHandler} that will need to
+     * be forcibly closed if the session closes before the
+     * {@link FutureToSendHandler} completes.
+     */
+    protected void registerFuture(FutureToSendHandler f2sh) {
+        futures.put(f2sh, f2sh);
+    }
+
+
+    /**
+     * Remove a {@link FutureToSendHandler} from the set of tracked instances.
+     */
+    protected void unregisterFuture(FutureToSendHandler f2sh) {
+        futures.remove(f2sh);
+    }
+
+
+    @Override
+    public URI getRequestURI() {
+        checkState();
+        return requestUri;
+    }
+
+
+    @Override
+    public Map<String,List<String>> getRequestParameterMap() {
+        checkState();
+        return requestParameterMap;
+    }
+
+
+    @Override
+    public String getQueryString() {
+        checkState();
+        return queryString;
+    }
+
+
+    @Override
+    public Principal getUserPrincipal() {
+        checkState();
+        return userPrincipal;
+    }
+
+
+    @Override
+    public Map<String,String> getPathParameters() {
+        checkState();
+        return pathParameters;
+    }
+
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+
+    @Override
+    public Map<String,Object> getUserProperties() {
+        checkState();
+        return userProperties;
+    }
+
+
+    public Endpoint getLocal() {
+        return localEndpoint;
+    }
+
+
+    public String getHttpSessionId() {
+        return httpSessionId;
+    }
+
+
+    protected MessageHandler getTextMessageHandler() {
+        return textMessageHandler;
+    }
+
+
+    protected MessageHandler getBinaryMessageHandler() {
+        return binaryMessageHandler;
+    }
+
+
+    protected MessageHandler.Whole<PongMessage> getPongMessageHandler() {
+        return pongMessageHandler;
+    }
+
+
+    protected void updateLastActive() {
+        lastActive = System.currentTimeMillis();
+    }
+
+
+    protected void checkExpiration() {
+        long timeout = maxIdleTimeout;
+        if (timeout < 1) {
+            return;
+        }
+
+        if (System.currentTimeMillis() - lastActive > timeout) {
+            String msg = sm.getString("wsSession.timeout");
+            doClose(new CloseReason(CloseCodes.GOING_AWAY, msg),
+                    new CloseReason(CloseCodes.CLOSED_ABNORMALLY, msg));
+        }
+    }
+
+
+    private void checkState() {
+        if (state == State.CLOSED) {
+            throw new IllegalStateException(sm.getString("wsSession.closed"));
+        }
+    }
+
+    private static enum State {
+        OPEN,
+        CLOSING,
+        CLOSED
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/WsWebSocketContainer.java b/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
new file mode 100644
index 0000000..3a0fe25
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
@@ -0,0 +1,848 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousChannelGroup;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.Arrays;
+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.Random;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.codec.binary.Base64;
+import org.apache.tomcat.util.res.StringManager;
+import org.apache.tomcat.util.threads.ThreadPoolExecutor;
+import org.apache.tomcat.websocket.pojo.PojoEndpointClient;
+
+public class WsWebSocketContainer
+        implements WebSocketContainer, BackgroundProcess {
+
+    /**
+     * Property name to set to configure the value that is passed to
+     * {@link SSLEngine#setEnabledProtocols(String[])}. The value should be a
+     * comma separated string.
+     */
+    public static final String SSL_PROTOCOLS_PROPERTY =
+            "org.apache.tomcat.websocket.SSL_PROTOCOLS";
+    public static final String SSL_TRUSTSTORE_PROPERTY =
+            "org.apache.tomcat.websocket.SSL_TRUSTSTORE";
+    public static final String SSL_TRUSTSTORE_PWD_PROPERTY =
+            "org.apache.tomcat.websocket.SSL_TRUSTSTORE_PWD";
+    public static final String SSL_TRUSTSTORE_PWD_DEFAULT = "changeit";
+
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+    private static final Random random = new Random();
+    private static final byte[] crlf = new byte[] {13, 10};
+    private static final AsynchronousChannelGroup asynchronousChannelGroup;
+
+    static {
+        AsynchronousChannelGroup result = null;
+
+        // Need to do this with the right thread context class loader else the
+        // first web app to call this will trigger a leak
+        ClassLoader original = Thread.currentThread().getContextClassLoader();
+
+        try {
+            Thread.currentThread().setContextClassLoader(
+                    AsyncIOThreadFactory.class.getClassLoader());
+
+            // These are the same settings as the default
+            // AsynchronousChannelGroup
+            int initialSize = Runtime.getRuntime().availableProcessors();
+            ExecutorService executorService = new ThreadPoolExecutor(
+                    0,
+                    Integer.MAX_VALUE,
+                    Long.MAX_VALUE, TimeUnit.MILLISECONDS,
+                    new SynchronousQueue<Runnable>(),
+                    new AsyncIOThreadFactory());
+
+            try {
+                result = AsynchronousChannelGroup.withCachedThreadPool(
+                        executorService, initialSize);
+            } catch (IOException e) {
+                // No good reason for this to happen.
+                throw new IllegalStateException(sm.getString(
+                        "wsWebSocketContainer.asynchronousChannelGroupFail"));
+            }
+        } finally {
+            Thread.currentThread().setContextClassLoader(original);
+        }
+
+        asynchronousChannelGroup = result;
+    }
+
+    private final Log log = LogFactory.getLog(WsWebSocketContainer.class);
+    private final Map<Class<?>, Set<WsSession>> endpointSessionMap =
+            new HashMap<Class<?>, Set<WsSession>>();
+    private final Map<WsSession,WsSession> sessions = new ConcurrentHashMap<WsSession, WsSession>();
+    private final Object endPointSessionMapLock = new Object();
+
+    private long defaultAsyncTimeout = -1;
+    private int maxBinaryMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;
+    private int maxTextMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;
+    private volatile long defaultMaxSessionIdleTimeout = 0;
+    private int backgroundProcessCount = 0;
+    private int processPeriod = 10;
+
+
+    @Override
+    public Session connectToServer(Object pojo, URI path)
+            throws DeploymentException {
+
+        ClientEndpoint annotation =
+                pojo.getClass().getAnnotation(ClientEndpoint.class);
+        if (annotation == null) {
+            throw new DeploymentException(
+                    sm.getString("wsWebSocketContainer.missingAnnotation",
+                            pojo.getClass().getName()));
+        }
+
+        Endpoint ep = new PojoEndpointClient(pojo, annotation.decoders());
+
+        Class<? extends ClientEndpointConfig.Configurator> configuratorClazz =
+                pojo.getClass().getAnnotation(
+                        ClientEndpoint.class).configurator();
+
+        ClientEndpointConfig.Configurator configurator = null;
+        if (!ClientEndpointConfig.Configurator.class.equals(
+                configuratorClazz)) {
+            try {
+                configurator = configuratorClazz.newInstance();
+            } catch (InstantiationException e) {
+                throw new DeploymentException(sm.getString(
+                        "wsWebSocketContainer.defaultConfiguratorFail"), e);
+            } catch (IllegalAccessException e) {
+                throw new DeploymentException(sm.getString(
+                        "wsWebSocketContainer.defaultConfiguratorFail"), e);
+            }
+        }
+
+        ClientEndpointConfig config = ClientEndpointConfig.Builder.create().
+                configurator(configurator).
+                decoders(Arrays.asList(annotation.decoders())).
+                encoders(Arrays.asList(annotation.encoders())).
+                build();
+        return connectToServer(ep, config, path);
+    }
+
+
+    @Override
+    public Session connectToServer(Class<?> annotatedEndpointClass, URI path)
+            throws DeploymentException {
+
+        Object pojo;
+        try {
+            pojo = annotatedEndpointClass.newInstance();
+        } catch (InstantiationException e) {
+            throw new DeploymentException(sm.getString(
+                    "wsWebSocketContainer.endpointCreateFail",
+                    annotatedEndpointClass.getName()), e);
+        } catch (IllegalAccessException e) {
+            throw new DeploymentException(sm.getString(
+                    "wsWebSocketContainer.endpointCreateFail",
+                    annotatedEndpointClass.getName()), e);
+        }
+
+        return connectToServer(pojo, path);
+    }
+
+
+    @Override
+    public Session connectToServer(Class<? extends Endpoint> clazz,
+            ClientEndpointConfig clientEndpointConfiguration, URI path)
+            throws DeploymentException {
+
+        Endpoint endpoint;
+        try {
+            endpoint = clazz.newInstance();
+        } catch (InstantiationException e) {
+            throw new DeploymentException(sm.getString(
+                    "wsWebSocketContainer.endpointCreateFail", clazz.getName()),
+                    e);
+        } catch (IllegalAccessException e) {
+            throw new DeploymentException(sm.getString(
+                    "wsWebSocketContainer.endpointCreateFail", clazz.getName()),
+                    e);
+        }
+
+        return connectToServer(endpoint, clientEndpointConfiguration, path);
+    }
+
+
+    @Override
+    public Session connectToServer(Endpoint endpoint,
+            ClientEndpointConfig clientEndpointConfiguration, URI path)
+            throws DeploymentException {
+
+        boolean secure = false;
+
+        String scheme = path.getScheme();
+        if (!("ws".equalsIgnoreCase(scheme) ||
+                "wss".equalsIgnoreCase(scheme))) {
+            throw new DeploymentException(sm.getString(
+                    "wsWebSocketContainer.pathWrongScheme", scheme));
+        }
+        String host = path.getHost();
+        if (host == null) {
+            throw new DeploymentException(
+                    sm.getString("wsWebSocketContainer.pathNoHost"));
+        }
+        int port = path.getPort();
+        Map<String,List<String>> reqHeaders = createRequestHeaders(host, port,
+                clientEndpointConfiguration.getPreferredSubprotocols(),
+                clientEndpointConfiguration.getExtensions());
+        clientEndpointConfiguration.getConfigurator().
+                beforeRequest(reqHeaders);
+
+        ByteBuffer request = createRequest(path, reqHeaders);
+
+        SocketAddress sa;
+        if (port == -1) {
+            if ("ws".equalsIgnoreCase(scheme)) {
+                sa = new InetSocketAddress(host, 80);
+            } else if ("wss".equalsIgnoreCase(scheme)) {
+                sa = new InetSocketAddress(host, 443);
+                secure = true;
+            } else {
+                throw new DeploymentException(
+                        sm.getString("wsWebSocketContainer.invalidScheme"));
+            }
+        } else {
+            if ("wss".equalsIgnoreCase(scheme)) {
+                secure = true;
+            }
+            sa = new InetSocketAddress(host, port);
+        }
+
+        AsynchronousSocketChannel socketChannel;
+        try {
+            socketChannel =
+                    AsynchronousSocketChannel.open(asynchronousChannelGroup);
+        } catch (IOException ioe) {
+            throw new DeploymentException(sm.getString(
+                    "wsWebSocketContainer.asynchronousSocketChannelFail"), ioe);
+        }
+
+        Future<Void> fConnect = socketChannel.connect(sa);
+
+        AsyncChannelWrapper channel;
+        if (secure) {
+            SSLEngine sslEngine = createSSLEngine(
+                    clientEndpointConfiguration.getUserProperties());
+            channel = new AsyncChannelWrapperSecure(socketChannel, sslEngine);
+        } else {
+            channel = new AsyncChannelWrapperNonSecure(socketChannel);
+        }
+
+        ByteBuffer response;
+        String subProtocol;
+        try {
+            fConnect.get();
+
+            Future<Void> fHandshake = channel.handshake();
+            fHandshake.get();
+
+            int toWrite = request.limit();
+
+            Future<Integer> fWrite = channel.write(request);
+            Integer thisWrite = fWrite.get();
+            toWrite -= thisWrite.intValue();
+
+            while (toWrite > 0) {
+                fWrite = channel.write(request);
+                thisWrite = fWrite.get();
+                toWrite -= thisWrite.intValue();
+            }
+            // Same size as the WsFrame input buffer
+            response = ByteBuffer.allocate(maxBinaryMessageBufferSize);
+
+            HandshakeResponse handshakeResponse =
+                    processResponse(response, channel);
+            clientEndpointConfiguration.getConfigurator().
+                    afterResponse(handshakeResponse);
+
+            // Sub-protocol
+            // Header names are always stored in lower case
+            List<String> values = handshakeResponse.getHeaders().get(
+                    Constants.WS_PROTOCOL_HEADER_NAME_LOWER);
+            if (values == null || values.size() == 0) {
+                subProtocol = null;
+            } else if (values.size() == 1) {
+                subProtocol = values.get(0);
+            } else {
+                throw new DeploymentException(
+                        sm.getString("Sec-WebSocket-Protocol"));
+            }
+        } catch (ExecutionException e) {
+            throw new DeploymentException(
+                    sm.getString("wsWebSocketContainer.httpRequestFailed"), e);
+        } catch (InterruptedException e) {
+            throw new DeploymentException(
+                    sm.getString("wsWebSocketContainer.httpRequestFailed"), e);
+        } catch (SSLException e) {
+            throw new DeploymentException(
+                    sm.getString("wsWebSocketContainer.httpRequestFailed"), e);
+        } catch (EOFException e) {
+            throw new DeploymentException(
+                    sm.getString("wsWebSocketContainer.httpRequestFailed"), e);
+        }
+
+        // Switch to WebSocket
+        WsRemoteEndpointImplClient wsRemoteEndpointClient =
+                new WsRemoteEndpointImplClient(channel);
+
+
+        WsSession wsSession = new WsSession(endpoint, wsRemoteEndpointClient,
+                this, null, null, null, null, null, subProtocol,
+                Collections.<String, String> emptyMap(), false,
+                clientEndpointConfiguration);
+        endpoint.onOpen(wsSession, clientEndpointConfiguration);
+        registerSession(endpoint, wsSession);
+
+        // Object creation will trigger input processing
+        @SuppressWarnings("unused")
+        WsFrameClient wsFrameClient = new WsFrameClient(response, channel,
+                wsSession);
+
+        return wsSession;
+    }
+
+
+    protected void registerSession(Endpoint endpoint, WsSession wsSession) {
+
+        Class<?> endpointClazz = endpoint.getClass();
+
+        if (!wsSession.isOpen()) {
+            // The session was closed during onOpen. No need to register it.
+            return;
+        }
+        synchronized (endPointSessionMapLock) {
+            if (endpointSessionMap.size() == 0) {
+                BackgroundProcessManager.getInstance().register(this);
+            }
+            Set<WsSession> wsSessions = endpointSessionMap.get(endpointClazz);
+            if (wsSessions == null) {
+                wsSessions = new HashSet<WsSession>();
+                endpointSessionMap.put(endpointClazz, wsSessions);
+            }
+            wsSessions.add(wsSession);
+        }
+        sessions.put(wsSession, wsSession);
+    }
+
+
+    protected void unregisterSession(Endpoint endpoint, WsSession wsSession) {
+
+        Class<?> endpointClazz = endpoint.getClass();
+
+        synchronized (endPointSessionMapLock) {
+            Set<WsSession> wsSessions = endpointSessionMap.get(endpointClazz);
+            if (wsSessions != null) {
+                wsSessions.remove(wsSession);
+                if (wsSessions.size() == 0) {
+                    endpointSessionMap.remove(endpointClazz);
+                }
+            }
+            if (endpointSessionMap.size() == 0) {
+                BackgroundProcessManager.getInstance().unregister(this);
+            }
+        }
+        sessions.remove(wsSession);
+    }
+
+
+    Set<Session> getOpenSessions(Class<?> endpoint) {
+        HashSet<Session> result = new HashSet<Session>();
+        synchronized (endPointSessionMapLock) {
+            Set<WsSession> sessions = endpointSessionMap.get(endpoint);
+            if (sessions != null) {
+                result.addAll(sessions);
+            }
+        }
+        return result;
+    }
+
+    private Map<String,List<String>> createRequestHeaders(String host,
+            int port, List<String> subProtocols, List<Extension> extensions) {
+
+        Map<String,List<String>> headers = new HashMap<String, List<String>>();
+
+        // Host header
+        List<String> hostValues = new ArrayList<String>(1);
+        if (port == -1) {
+            hostValues.add(host);
+        } else {
+            hostValues.add(host + ':' + port);
+        }
+
+        headers.put(Constants.HOST_HEADER_NAME, hostValues);
+
+        // Upgrade header
+        List<String> upgradeValues = new ArrayList<String>(1);
+        upgradeValues.add(Constants.UPGRADE_HEADER_VALUE);
+        headers.put(Constants.UPGRADE_HEADER_NAME, upgradeValues);
+
+        // Connection header
+        List<String> connectionValues = new ArrayList<String>(1);
+        connectionValues.add(Constants.CONNECTION_HEADER_VALUE);
+        headers.put(Constants.CONNECTION_HEADER_NAME, connectionValues);
+
+        // WebSocket version header
+        List<String> wsVersionValues = new ArrayList<String>(1);
+        wsVersionValues.add(Constants.WS_VERSION_HEADER_VALUE);
+        headers.put(Constants.WS_VERSION_HEADER_NAME, wsVersionValues);
+
+        // WebSocket key
+        List<String> wsKeyValues = new ArrayList<String>(1);
+        wsKeyValues.add(generateWsKeyValue());
+        headers.put(Constants.WS_KEY_HEADER_NAME, wsKeyValues);
+
+        // WebSocket sub-protocols
+        if (subProtocols != null && subProtocols.size() > 0) {
+            headers.put(Constants.WS_PROTOCOL_HEADER_NAME, subProtocols);
+        }
+
+        // WebSocket extensions
+        if (extensions != null && extensions.size() > 0) {
+            headers.put(Constants.WS_EXTENSIONS_HEADER_NAME,
+                    generateExtensionHeaders(extensions));
+        }
+
+        return headers;
+    }
+
+
+    private List<String> generateExtensionHeaders(List<Extension> extensions) {
+        List<String> result = new ArrayList<String>(extensions.size());
+        for (Extension extension : extensions) {
+            StringBuilder header = new StringBuilder();
+            header.append(extension.getName());
+            for (Extension.Parameter param : extension.getParameters()) {
+                header.append(';');
+                header.append(param.getName());
+                String value = param.getValue();
+                if (value != null && value.length() > 0) {
+                    header.append('=');
+                    header.append(value);
+                }
+            }
+        }
+        return result;
+    }
+
+
+    private String generateWsKeyValue() {
+        byte[] keyBytes = new byte[16];
+        random.nextBytes(keyBytes);
+        return Base64.encodeBase64String(keyBytes);
+    }
+
+
+    private ByteBuffer createRequest(URI uri,
+            Map<String,List<String>> reqHeaders) {
+        ByteBuffer result = ByteBuffer.allocate(4 * 1024);
+
+        // Request line
+        result.put("GET ".getBytes(StandardCharsets.ISO_8859_1));
+        result.put(uri.getRawPath().getBytes(StandardCharsets.ISO_8859_1));
+        String query = uri.getRawQuery();
+        if (query != null) {
+            result.put((byte) '?');
+            result.put(query.getBytes(StandardCharsets.ISO_8859_1));
+        }
+        result.put(" HTTP/1.1\r\n".getBytes(StandardCharsets.ISO_8859_1));
+
+        // Headers
+        Iterator<Entry<String,List<String>>> iter =
+                reqHeaders.entrySet().iterator();
+        while (iter.hasNext()) {
+            Entry<String,List<String>> entry = iter.next();
+            addHeader(result, entry.getKey(), entry.getValue());
+        }
+
+        // Terminating CRLF
+        result.put(crlf);
+
+        result.flip();
+
+        return result;
+    }
+
+
+    private void addHeader(ByteBuffer result, String key, List<String> values) {
+        StringBuilder sb = new StringBuilder();
+
+        Iterator<String> iter = values.iterator();
+        if (!iter.hasNext()) {
+            return;
+        }
+        sb.append(iter.next());
+        while (iter.hasNext()) {
+            sb.append(',');
+            sb.append(iter.next());
+        }
+
+        result.put(key.getBytes(StandardCharsets.ISO_8859_1));
+        result.put(": ".getBytes(StandardCharsets.ISO_8859_1));
+        result.put(sb.toString().getBytes(StandardCharsets.ISO_8859_1));
+        result.put(crlf);
+    }
+
+
+    /**
+     * Process response, blocking until HTTP response has been fully received.
+     * @throws ExecutionException
+     * @throws InterruptedException
+     * @throws DeploymentException
+     */
+    private HandshakeResponse processResponse(ByteBuffer response,
+            AsyncChannelWrapper channel) throws InterruptedException,
+            ExecutionException, DeploymentException, EOFException {
+
+        Map<String,List<String>> headers = new HashMap<String, List<String>>();
+
+        boolean readStatus = false;
+        boolean readHeaders = false;
+        String line = null;
+        while (!readHeaders) {
+            // Blocking read
+            Future<Integer> read = channel.read(response);
+            Integer bytesRead = read.get();
+            if (bytesRead.intValue() == -1) {
+                throw new EOFException();
+            }
+            response.flip();
+            while (response.hasRemaining() && !readHeaders) {
+                if (line == null) {
+                    line = readLine(response);
+                } else {
+                    line += readLine(response);
+                }
+                if ("\r\n".equals(line)) {
+                    readHeaders = true;
+                } else if (line.endsWith("\r\n")) {
+                    if (readStatus) {
+                        parseHeaders(line, headers);
+                    } else {
+                        parseStatus(line);
+                        readStatus = true;
+                    }
+                    line = null;
+                }
+            }
+        }
+
+        return new WsHandshakeResponse(headers);
+    }
+
+
+    private void parseStatus(String line) throws DeploymentException {
+        // This client only understands HTTP 1.1
+        // RFC2616 is case specific
+        if (!line.startsWith("HTTP/1.1 101")) {
+            throw new DeploymentException(sm.getString(
+                    "wsWebSocketContainer.invalidStatus", line));
+        }
+    }
+
+
+    private void parseHeaders(String line, Map<String,List<String>> headers) {
+        // Treat headers as single values by default.
+
+        int index = line.indexOf(':');
+        if (index == -1) {
+            log.warn(sm.getString("wsWebSocketContainer.invalidHeader", line));
+            return;
+        }
+        // Header names are case insensitive so always use lower case
+        String headerName = line.substring(0, index).trim().toLowerCase();
+        // TODO handle known multi-value headers
+        String headerValue = line.substring(index + 1).trim();
+
+        List<String> values = headers.get(headerName);
+        if (values == null) {
+            values = new ArrayList<String>(1);
+            headers.put(headerName, values);
+        }
+        values.add(headerValue);
+    }
+
+
+    private String readLine(ByteBuffer response) {
+        // All ISO-8859-1
+        StringBuilder sb = new StringBuilder();
+
+        char c = 0;
+        while (response.hasRemaining()) {
+            c = (char) response.get();
+            sb.append(c);
+            if (c == 10) {
+                break;
+            }
+        }
+
+        return sb.toString();
+    }
+
+
+    private SSLEngine createSSLEngine(Map<String,Object> userProperties)
+            throws DeploymentException {
+
+        try {
+            // Create the SSL Context
+            SSLContext sslContext = SSLContext.getInstance("TLS");
+
+            // Trust store
+            String sslTrustStoreValue =
+                    (String) userProperties.get(SSL_TRUSTSTORE_PROPERTY);
+            if (sslTrustStoreValue != null) {
+                String sslTrustStorePwdValue = (String) userProperties.get(
+                        SSL_TRUSTSTORE_PWD_PROPERTY);
+                if (sslTrustStorePwdValue == null) {
+                    sslTrustStorePwdValue = SSL_TRUSTSTORE_PWD_DEFAULT;
+                }
+
+                File keyStoreFile = new File(sslTrustStoreValue);
+                KeyStore ks = KeyStore.getInstance("JKS");
+                InputStream is = null;
+                try {
+                    is = new FileInputStream(keyStoreFile);
+                    ks.load(is, sslTrustStorePwdValue.toCharArray());
+                } finally {
+                    if (is != null) {
+                        try {
+                            is.close();
+                        } catch (IOException ioe) {
+                           // Ignore
+                        }
+                    }
+                }
+
+                TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+                        TrustManagerFactory.getDefaultAlgorithm());
+                tmf.init(ks);
+
+                sslContext.init(null, tmf.getTrustManagers(), null);
+            } else {
+                sslContext.init(null, null, null);
+            }
+
+            SSLEngine engine = sslContext.createSSLEngine();
+
+            String sslProtocolsValue =
+                    (String) userProperties.get(SSL_PROTOCOLS_PROPERTY);
+            if (sslProtocolsValue != null) {
+                engine.setEnabledProtocols(sslProtocolsValue.split(","));
+            }
+
+            engine.setUseClientMode(true);
+
+            return engine;
+        } catch (Exception e) {
+            throw new DeploymentException(sm.getString(
+                    "wsWebSocketContainer.sslEngineFail"), e);
+        }
+    }
+
+
+    @Override
+    public long getDefaultMaxSessionIdleTimeout() {
+        return defaultMaxSessionIdleTimeout;
+    }
+
+
+    @Override
+    public void setDefaultMaxSessionIdleTimeout(long timeout) {
+        this.defaultMaxSessionIdleTimeout = timeout;
+    }
+
+
+    @Override
+    public int getDefaultMaxBinaryMessageBufferSize() {
+        return maxBinaryMessageBufferSize;
+    }
+
+
+    @Override
+    public void setDefaultMaxBinaryMessageBufferSize(int max) {
+        maxBinaryMessageBufferSize = max;
+    }
+
+
+    @Override
+    public int getDefaultMaxTextMessageBufferSize() {
+        return maxTextMessageBufferSize;
+    }
+
+
+    @Override
+    public void setDefaultMaxTextMessageBufferSize(int max) {
+        maxTextMessageBufferSize = max;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     *
+     * Currently, this implementation does not support any extensions.
+     */
+    @Override
+    public Set<Extension> getInstalledExtensions() {
+        return Collections.emptySet();
+    }
+
+
+    /**
+     * {@inheritDoc}
+     *
+     * The default value for this implementation is -1.
+     */
+    @Override
+    public long getDefaultAsyncSendTimeout() {
+        return defaultAsyncTimeout;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     *
+     * The default value for this implementation is -1.
+     */
+    @Override
+    public void setAsyncSendTimeout(long timeout) {
+        this.defaultAsyncTimeout = timeout;
+    }
+
+
+    /**
+     * Cleans up the resources still in use by WebSocket sessions created from
+     * this container. This includes closing sessions and cancelling
+     * {@link Future}s associated with blocking read/writes.
+     */
+    public void destroy() {
+        CloseReason cr = new CloseReason(
+                CloseCodes.GOING_AWAY, sm.getString("wsWebSocketContainer.shutdown"));
+
+        for (WsSession session : sessions.keySet()) {
+            try {
+                session.close(cr);
+            } catch (IOException ioe) {
+                log.debug(sm.getString(
+                        "wsWebSocketContainer.sessionCloseFail", session.getId()), ioe);
+            }
+        }
+    }
+
+
+    // ----------------------------------------------- BackgroundProcess methods
+
+    @Override
+    public void backgroundProcess() {
+        // This method gets called once a second.
+        backgroundProcessCount ++;
+
+        if (backgroundProcessCount >= processPeriod) {
+            backgroundProcessCount = 0;
+
+            for (WsSession wsSession : sessions.keySet()) {
+                wsSession.checkExpiration();
+            }
+        }
+
+    }
+
+
+    @Override
+    public void setProcessPeriod(int period) {
+        this.processPeriod = period;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     *
+     * The default value is 10 which means session expirations are processed
+     * every 10 seconds.
+     */
+    @Override
+    public int getProcessPeriod() {
+        return processPeriod;
+    }
+
+
+    /**
+     * Create threads for AsyncIO that have the right context class loader to
+     * prevent memory leaks.
+     */
+    private static class AsyncIOThreadFactory implements ThreadFactory {
+
+        private AtomicInteger count = new AtomicInteger(0);
+
+        @Override
+        public Thread newThread(Runnable r) {
+            Thread t = new Thread(r);
+            t.setName("WebSocketClient-AsyncIO-" + count.incrementAndGet());
+            t.setContextClassLoader(this.getClass().getClassLoader());
+            t.setDaemon(true);
+            return t;
+        }
+    }
+}
diff --git a/java/javax/annotation/PreDestroy.java b/java/org/apache/tomcat/websocket/pojo/Constants.java
similarity index 71%
copy from java/javax/annotation/PreDestroy.java
copy to java/org/apache/tomcat/websocket/pojo/Constants.java
index d5be75a..bcc9978 100644
--- a/java/javax/annotation/PreDestroy.java
+++ b/java/org/apache/tomcat/websocket/pojo/Constants.java
@@ -5,27 +5,26 @@
  * The ASF licenses this file to You 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.apache.tomcat.websocket.pojo;
 
+/**
+ * Internal implementation constants.
+ */
+public class Constants {
 
-package javax.annotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
- at Target({ElementType.METHOD})
- at Retention(RetentionPolicy.RUNTIME)
+    protected static final String PACKAGE_NAME =
+            Constants.class.getPackage().getName();
 
-public @interface PreDestroy {
-    // No attributes
+    private Constants() {
+        // Hide default constructor
+    }
 }
diff --git a/java/org/apache/tomcat/websocket/pojo/LocalStrings.properties b/java/org/apache/tomcat/websocket/pojo/LocalStrings.properties
new file mode 100644
index 0000000..fc5e01c
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/pojo/LocalStrings.properties
@@ -0,0 +1,39 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+pojoEndpointBase.closeSessionFail=Failed to close WebSocket session during error handling
+pojoEndpointBase.onCloseFail=Failed to call onClose method of POJO end point for POJO of type [{0}]
+pojoEndpointBase.onError=No error handling configured for [{0}] and the following error occurred
+pojoEndpointBase.onErrorFail=Failed to call onError method of POJO end point for POJO of type [{0}]
+pojoEndpointBase.onOpenFail=Failed to call onOpen method of POJO end point for POJO of type [{0}]
+pojoEndpointServer.getPojoInstanceFail=Failed to create instance of POJO of type [{0}]
+pojoMethodMapping.decodePathParamFail=Failed to decode path parameter value [{0}] to expected type [{1}]
+pojoMethodMapping.duplicateAnnotation=Duplicate annotations [{0}] present on class [{1}]
+pojoMethodMapping.duplicateLastParam=Multiple boolean (last) parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
+pojoMethodMapping.duplicateMessageParam=Multiple message parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
+pojoMethodMapping.duplicatePongMessageParam=Multiple PongMessage parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
+pojoMethodMapping.duplicateSessionParam=Multiple session parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
+pojoMethodMapping.invalidDecoder=The specified decoder of type [{0}] could not be instantiated
+pojoMethodMapping.invalidPathParamType=Parameters annotated with @PathParam may only be Strings, Java primitives or a boxed version thereof
+pojoMethodMapping.methodNotPublic=The annotated method [{0}] is not public
+pojoMethodMapping.noPayload=No payload parameter present on the method [{0}] of class [{1}] that was annotated with OnMessage
+pojoMethodMapping.onErrorNoThrowable=No Throwable parameter was present on the method [{0}] of class [{1}] that was annotated with OnError
+pojoMethodMapping.paramWithoutAnnotation=A parameter of type [{0}] was found on method[{1}] of class [{2}] that did not have a @PathParam annotation
+pojoMethodMapping.partialInputStream=Invalid InputStream and boolean parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
+pojoMethodMapping.partialObject=Invalid Object and boolean parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
+pojoMethodMapping.partialPong=Invalid PongMesssge and boolean parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
+pojoMethodMapping.partialReader=Invalid Reader and boolean parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
+pojoMethodMapping.pongWithPayload=Invalid PongMessgae and Message parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
+pojoMessageHandlerWhole.decodeIoFail=IO error while decoding message
\ No newline at end of file
diff --git a/java/org/apache/tomcat/websocket/pojo/PojoEndpointBase.java b/java/org/apache/tomcat/websocket/pojo/PojoEndpointBase.java
new file mode 100644
index 0000000..e4efb91
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/pojo/PojoEndpointBase.java
@@ -0,0 +1,157 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.pojo;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Map;
+import java.util.Set;
+
+import javax.websocket.CloseReason;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Base implementation (client and server have different concrete
+ * implementations) of the wrapper that converts a POJO instance into a
+ * WebSocket endpoint instance.
+ */
+public abstract class PojoEndpointBase extends Endpoint {
+
+    private static final Log log = LogFactory.getLog(PojoEndpointBase.class);
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+
+    private Object pojo;
+    private Map<String,String> pathParameters;
+    private PojoMethodMapping methodMapping;
+
+
+    protected final void doOnOpen(Session session, EndpointConfig config) {
+        PojoMethodMapping methodMapping = getMethodMapping();
+        Object pojo = getPojo();
+        Map<String,String> pathParameters = getPathParameters();
+
+        if (methodMapping.getOnOpen() != null) {
+            try {
+                methodMapping.getOnOpen().invoke(pojo,
+                        methodMapping.getOnOpenArgs(
+                                pathParameters, session, config));
+
+            } catch (IllegalAccessException e) {
+                // Reflection related problems
+                log.error(sm.getString(
+                        "pojoEndpointBase.onOpenFail",
+                        pojo.getClass().getName()), e);
+                handleOnOpenError(session, e);
+                return;
+            } catch (InvocationTargetException e) {
+                Throwable cause = e.getCause();
+                handleOnOpenError(session, cause);
+                return;
+            } catch (Throwable t) {
+                handleOnOpenError(session, t);
+                return;
+            }
+        }
+
+        for (MessageHandler mh : methodMapping.getMessageHandlers(pojo,
+                pathParameters, session, config)) {
+            session.addMessageHandler(mh);
+        }
+    }
+
+
+    private void handleOnOpenError(Session session, Throwable t) {
+        // If really fatal - re-throw
+        ExceptionUtils.handleThrowable(t);
+
+        // Trigger the error handler and close the session
+        onError(session, t);
+        try {
+            session.close();
+        } catch (IOException ioe) {
+            log.warn(sm.getString("pojoEndpointBase.closeSessionFail"), ioe);
+        }
+    }
+
+    @Override
+    public final void onClose(Session session, CloseReason closeReason) {
+
+        if (methodMapping.getOnClose() != null) {
+            try {
+                methodMapping.getOnClose().invoke(pojo,
+                        methodMapping.getOnCloseArgs(pathParameters, session, closeReason));
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.error(sm.getString("pojoEndpointBase.onCloseFail",
+                        pojo.getClass().getName()), t);
+            }
+        }
+
+        // Trigger the destroy method for any associated decoders
+        Set<MessageHandler> messageHandlers = session.getMessageHandlers();
+        for (MessageHandler messageHandler : messageHandlers) {
+            if (messageHandler instanceof PojoMessageHandlerWholeBase<?>) {
+                ((PojoMessageHandlerWholeBase<?>) messageHandler).onClose();
+            }
+        }
+    }
+
+
+    @Override
+    public final void onError(Session session, Throwable throwable) {
+
+        if (methodMapping.getOnError() == null) {
+            log.error(sm.getString("pojoEndpointBase.onError",
+                    pojo.getClass().getName()), throwable);
+        } else {
+            try {
+                methodMapping.getOnError().invoke(
+                        pojo,
+                        methodMapping.getOnErrorArgs(pathParameters, session,
+                                throwable));
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.error(sm.getString("pojoEndpointBase.onErrorFail",
+                        pojo.getClass().getName()), t);
+            }
+        }
+    }
+
+    protected Object getPojo() { return pojo; }
+    protected void setPojo(Object pojo) { this.pojo = pojo; }
+
+
+    protected Map<String,String> getPathParameters() { return pathParameters; }
+    protected void setPathParameters(Map<String,String> pathParameters) {
+        this.pathParameters = pathParameters;
+    }
+
+
+    protected PojoMethodMapping getMethodMapping() { return methodMapping; }
+    protected void setMethodMapping(PojoMethodMapping methodMapping) {
+        this.methodMapping = methodMapping;
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/pojo/PojoEndpointClient.java b/java/org/apache/tomcat/websocket/pojo/PojoEndpointClient.java
new file mode 100644
index 0000000..4fb9918
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/pojo/PojoEndpointClient.java
@@ -0,0 +1,46 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.pojo;
+
+import java.util.Collections;
+
+import javax.websocket.Decoder;
+import javax.websocket.DeploymentException;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+
+
+/**
+ * Wrapper class for instances of POJOs annotated with
+ * {@link javax.websocket.ClientEndpoint} so they appear as standard
+ * {@link javax.websocket.Endpoint} instances.
+ */
+public class PojoEndpointClient extends PojoEndpointBase {
+
+    public PojoEndpointClient(Object pojo,
+            Class<? extends Decoder>[] decoders) throws DeploymentException {
+        setPojo(pojo);
+        setMethodMapping(
+                new PojoMethodMapping(pojo.getClass(), decoders, null));
+        setPathParameters(Collections.<String, String> emptyMap());
+    }
+
+    @Override
+    public void onOpen(Session session, EndpointConfig config) {
+        doOnOpen(session, config);
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/pojo/PojoEndpointServer.java b/java/org/apache/tomcat/websocket/pojo/PojoEndpointServer.java
new file mode 100644
index 0000000..77ef83d
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/pojo/PojoEndpointServer.java
@@ -0,0 +1,72 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.pojo;
+
+import java.util.Map;
+
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Wrapper class for instances of POJOs annotated with
+ * {@link javax.websocket.server.ServerEndpoint} so they appear as standard
+ * {@link javax.websocket.Endpoint} instances.
+ */
+public class PojoEndpointServer extends PojoEndpointBase {
+
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+
+    public static final String POJO_PATH_PARAM_KEY =
+            "org.apache.tomcat.websocket.pojo.PojoEndpoint.pathParams";
+    public static final String POJO_METHOD_MAPPING_KEY =
+            "org.apache.tomcat.websocket.pojo.PojoEndpoint.methodMapping";
+
+
+    @Override
+    public void onOpen(Session session, EndpointConfig endpointConfig) {
+
+        ServerEndpointConfig sec = (ServerEndpointConfig) endpointConfig;
+
+        Object pojo;
+        try {
+            pojo = sec.getConfigurator().getEndpointInstance(
+                    sec.getEndpointClass());
+        } catch (InstantiationException e) {
+            throw new IllegalArgumentException(sm.getString(
+                    "pojoEndpointServer.getPojoInstanceFail",
+                    sec.getEndpointClass().getName()), e);
+        }
+        setPojo(pojo);
+
+        @SuppressWarnings("unchecked")
+        Map<String,String> pathParameters =
+                (Map<String, String>) sec.getUserProperties().get(
+                        POJO_PATH_PARAM_KEY);
+        setPathParameters(pathParameters);
+
+        PojoMethodMapping methodMapping =
+                (PojoMethodMapping) sec.getUserProperties().get(
+                        POJO_METHOD_MAPPING_KEY);
+        setMethodMapping(methodMapping);
+
+        doOnOpen(session, endpointConfig);
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java
new file mode 100644
index 0000000..aa060b6
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket.pojo;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.MessageHandler;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
+
+import org.apache.tomcat.websocket.WrappedMessageHandler;
+
+/**
+ * Common implementation code for the POJO message handlers.
+ *
+ * @param <T>   The type of message to handle
+ */
+public abstract class PojoMessageHandlerBase<T>
+        implements WrappedMessageHandler {
+
+    protected final Object pojo;
+    protected final Method method;
+    protected final Session session;
+    protected final Object[] params;
+    protected final int indexPayload;
+    protected final boolean convert;
+    protected final int indexSession;
+    protected final long maxMessageSize;
+
+    public PojoMessageHandlerBase(Object pojo, Method method,
+            Session session, Object[] params, int indexPayload, boolean convert,
+            int indexSession, long maxMessageSize) {
+        this.pojo = pojo;
+        this.method = method;
+        this.session = session;
+        this.params = params;
+        this.indexPayload = indexPayload;
+        this.convert = convert;
+        this.indexSession = indexSession;
+        this.maxMessageSize = maxMessageSize;
+    }
+
+
+    protected final void processResult(Object result) {
+        if (result == null) {
+            return;
+        }
+
+        RemoteEndpoint.Basic remoteEndpoint = session.getBasicRemote();
+        try {
+            if (result instanceof String) {
+                remoteEndpoint.sendText((String) result);
+            } else if (result instanceof ByteBuffer) {
+                remoteEndpoint.sendBinary((ByteBuffer) result);
+            } else if (result instanceof byte[]) {
+                remoteEndpoint.sendBinary(ByteBuffer.wrap((byte[]) result));
+            } else {
+                remoteEndpoint.sendObject(result);
+            }
+        } catch (IOException ioe) {
+            throw new IllegalStateException(ioe);
+        } catch (EncodeException ee) {
+            throw new IllegalStateException(ee);
+        }
+    }
+
+
+    /**
+     * Expose the POJO if it is a message handler so the Session is able to
+     * match requests to remove handlers if the original handler has been
+     * wrapped.
+     */
+    @Override
+    public final MessageHandler getWrappedHandler() {
+        if (pojo instanceof MessageHandler) {
+            return (MessageHandler) pojo;
+        } else {
+            return null;
+        }
+    }
+
+
+    @Override
+    public final long getMaxMessageSize() {
+        return maxMessageSize;
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialBase.java b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialBase.java
new file mode 100644
index 0000000..8981d70
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialBase.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket.pojo;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+
+import javax.websocket.DecodeException;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+
+import org.apache.tomcat.websocket.WsSession;
+
+/**
+ * Common implementation code for the POJO partial message handlers. All
+ * the real work is done in this class and in the superclass.
+ *
+ * @param <T>   The type of message to handle
+ */
+public abstract class PojoMessageHandlerPartialBase<T>
+        extends PojoMessageHandlerBase<T> implements MessageHandler.Partial<T> {
+
+    private final int indexBoolean;
+
+    public PojoMessageHandlerPartialBase(Object pojo, Method method,
+            Session session, Object[] params, int indexPayload,
+            boolean convert, int indexBoolean, int indexSession,
+            long maxMessageSize) {
+        super(pojo, method, session, params, indexPayload, convert,
+                indexSession, maxMessageSize);
+        this.indexBoolean = indexBoolean;
+    }
+
+
+    @Override
+    public final void onMessage(T message, boolean last) {
+        if (params.length == 1 && params[0] instanceof DecodeException) {
+            ((WsSession) session).getLocal().onError(session,
+                    (DecodeException) params[0]);
+            return;
+        }
+        Object[] parameters = params.clone();
+        if (indexBoolean != -1) {
+            parameters[indexBoolean] = Boolean.valueOf(last);
+        }
+        if (indexSession != -1) {
+            parameters[indexSession] = session;
+        }
+        if (convert) {
+            parameters[indexPayload] = ((ByteBuffer) message).array();
+        } else {
+            parameters[indexPayload] = message;
+        }
+        Object result;
+        try {
+            result = method.invoke(pojo, parameters);
+        } catch (IllegalAccessException e) {
+            throw new IllegalArgumentException(e);
+        } catch (InvocationTargetException e) {
+            throw new IllegalArgumentException(e);
+        }
+        processResult(result);
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialBinary.java
similarity index 51%
copy from java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
copy to java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialBinary.java
index 813bea8..97f7660 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
+++ b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialBinary.java
@@ -6,7 +6,7 @@
  * (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
+ *     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,
@@ -14,35 +14,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.coyote.http11.upgrade;
+package org.apache.tomcat.websocket.pojo;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
 
+import javax.websocket.Session;
 
 /**
- * Allows data to be written to the upgraded connection.
+ * ByteBuffer specific concrete implementation for handling partial messages.
  */
-public class UpgradeOutbound extends OutputStream {
-
-    @Override
-    public void flush() throws IOException {
-        processor.flush();
-    }
-
-    private final UpgradeProcessor<?> processor;
-
-    public UpgradeOutbound(UpgradeProcessor<?> processor) {
-        this.processor = processor;
-    }
-
-    @Override
-    public void write(int b) throws IOException {
-        processor.write(b);
-    }
-
-    @Override
-    public void write(byte[] b, int off, int len) throws IOException {
-        processor.write(b, off, len);
+public class PojoMessageHandlerPartialBinary
+        extends PojoMessageHandlerPartialBase<ByteBuffer>{
+
+    public PojoMessageHandlerPartialBinary(Object pojo, Method method,
+            Session session, Object[] params, int indexPayload, boolean convert,
+            int indexBoolean, int indexSession, long maxMessageSize) {
+        super(pojo, method, session, params, indexPayload, convert, indexBoolean,
+                indexSession, maxMessageSize);
     }
 }
diff --git a/java/javax/annotation/Generated.java b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialText.java
similarity index 51%
copy from java/javax/annotation/Generated.java
copy to java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialText.java
index d4721db..dc35eb7 100644
--- a/java/javax/annotation/Generated.java
+++ b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialText.java
@@ -5,31 +5,31 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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.apache.tomcat.websocket.pojo;
 
+import java.lang.reflect.Method;
 
-package javax.annotation;
+import javax.websocket.Session;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
- at Target({ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, 
-    ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, 
-    ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE})
- at Retention(RetentionPolicy.SOURCE)
+/**
+ * Text specific concrete implementation for handling partial messages.
+ */
+public class PojoMessageHandlerPartialText
+        extends PojoMessageHandlerPartialBase<String>{
 
-public @interface Generated {
-    public String[] value();
-    public String date() default "";
-    public String comment() default "";
+    public PojoMessageHandlerPartialText(Object pojo, Method method,
+            Session session, Object[] params, int indexPayload, boolean convert,
+            int indexBoolean, int indexSession, long maxMessageSize) {
+        super(pojo, method, session, params, indexPayload, convert, indexBoolean,
+                indexSession, maxMessageSize);
+    }
 }
diff --git a/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeBase.java b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeBase.java
new file mode 100644
index 0000000..6d25d6e
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeBase.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket.pojo;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import javax.websocket.DecodeException;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+
+import org.apache.tomcat.websocket.WsSession;
+
+/**
+ * Common implementation code for the POJO whole message handlers. All the real
+ * work is done in this class and in the superclass.
+ *
+ * @param <T>   The type of message to handle
+ */
+public abstract class PojoMessageHandlerWholeBase<T>
+        extends PojoMessageHandlerBase<T> implements MessageHandler.Whole<T> {
+
+    public PojoMessageHandlerWholeBase(Object pojo, Method method,
+            Session session, Object[] params, int indexPayload,
+            boolean convert, int indexSession, long maxMessageSize) {
+        super(pojo, method, session, params, indexPayload, convert,
+                indexSession, maxMessageSize);
+    }
+
+
+    @Override
+    public final void onMessage(T message) {
+
+        if (params.length == 1 && params[0] instanceof DecodeException) {
+            ((WsSession) session).getLocal().onError(session,
+                    (DecodeException) params[0]);
+            return;
+        }
+
+        // Can this message be decoded?
+        Object payload;
+        try {
+            payload = decode(message);
+        } catch (DecodeException de) {
+            ((WsSession) session).getLocal().onError(session, de);
+            return;
+        }
+
+        if (payload == null) {
+            // Not decoded. Convert if required.
+            if (convert) {
+                payload = convert(message);
+            } else {
+                payload = message;
+            }
+        }
+
+        Object[] parameters = params.clone();
+        if (indexSession != -1) {
+            parameters[indexSession] = session;
+        }
+        parameters[indexPayload] = payload;
+
+        Object result;
+        try {
+            result = method.invoke(pojo, parameters);
+        } catch (IllegalAccessException e) {
+            throw new IllegalArgumentException(e);
+        } catch (InvocationTargetException e) {
+            throw new IllegalArgumentException(e);
+        }
+        processResult(result);
+    }
+
+    protected Object convert(T message) {
+        return message;
+    }
+
+
+    protected abstract Object decode(T message) throws DecodeException;
+    protected abstract void onClose();
+}
diff --git a/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeBinary.java b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeBinary.java
new file mode 100644
index 0000000..ae642b2
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeBinary.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket.pojo;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.Decoder.Binary;
+import javax.websocket.Decoder.BinaryStream;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * ByteBuffer specific concrete implementation for handling whole messages.
+ */
+public class PojoMessageHandlerWholeBinary
+        extends PojoMessageHandlerWholeBase<ByteBuffer> {
+
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+
+    private final List<Decoder> decoders = new ArrayList<Decoder>();
+
+    private final boolean isForInputStream;
+
+    public PojoMessageHandlerWholeBinary(Object pojo, Method method,
+            Session session, EndpointConfig config,
+            List<Class<? extends Decoder>> decoderClazzes, Object[] params,
+            int indexPayload, boolean convert, int indexSession,
+            boolean isForInputStream, long maxMessageSize) {
+        super(pojo, method, session, params, indexPayload, convert,
+                indexSession, maxMessageSize);
+        try {
+            if (decoderClazzes != null) {
+                for (Class<? extends Decoder> decoderClazz : decoderClazzes) {
+                    if (Binary.class.isAssignableFrom(decoderClazz)) {
+                        Binary<?> decoder =
+                                (Binary<?>) decoderClazz.newInstance();
+                        decoder.init(config);
+                        decoders.add(decoder);
+                    } else if (BinaryStream.class.isAssignableFrom(
+                            decoderClazz)) {
+                        BinaryStream<?> decoder =
+                                (BinaryStream<?>) decoderClazz.newInstance();
+                        decoder.init(config);
+                        decoders.add(decoder);
+                    } else {
+                        // Text decoder - ignore it
+                    }
+                }
+            }
+        } catch (IllegalAccessException e) {
+            throw new IllegalArgumentException(e);
+        } catch (InstantiationException e) {
+            throw new IllegalArgumentException(e);
+        }
+        this.isForInputStream = isForInputStream;
+    }
+
+
+    @Override
+    protected Object decode(ByteBuffer message) throws DecodeException {
+        for (Decoder decoder : decoders) {
+            if (decoder instanceof Binary) {
+                if (((Binary<?>) decoder).willDecode(message)) {
+                    return ((Binary<?>) decoder).decode(message);
+                }
+            } else {
+                byte[] array = new byte[message.limit() - message.position()];
+                message.get(array);
+                ByteArrayInputStream bais = new ByteArrayInputStream(array);
+                try {
+                    return ((BinaryStream<?>) decoder).decode(bais);
+                } catch (IOException ioe) {
+                    throw new DecodeException(message, sm.getString(
+                            "pojoMessageHandlerWhole.decodeIoFail"), ioe);
+                }
+            }
+        }
+        return null;
+    }
+
+
+    @Override
+    protected Object convert(ByteBuffer message) {
+        byte[] array = new byte[message.remaining()];
+        message.get(array);
+        if (isForInputStream) {
+            return new ByteArrayInputStream(array);
+        } else {
+            return array;
+        }
+    }
+
+
+    @Override
+    protected void onClose() {
+        for (Decoder decoder : decoders) {
+            decoder.destroy();
+        }
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholePong.java
similarity index 50%
copy from java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
copy to java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholePong.java
index 813bea8..762814d 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
+++ b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholePong.java
@@ -6,7 +6,7 @@
  * (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
+ *     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,
@@ -14,35 +14,35 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.coyote.http11.upgrade;
+package org.apache.tomcat.websocket.pojo;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import java.lang.reflect.Method;
 
+import javax.websocket.PongMessage;
+import javax.websocket.Session;
 
 /**
- * Allows data to be written to the upgraded connection.
+ * PongMessage specific concrete implementation for handling whole messages.
  */
-public class UpgradeOutbound extends OutputStream {
-
-    @Override
-    public void flush() throws IOException {
-        processor.flush();
-    }
-
-    private final UpgradeProcessor<?> processor;
-
-    public UpgradeOutbound(UpgradeProcessor<?> processor) {
-        this.processor = processor;
+public class PojoMessageHandlerWholePong
+        extends PojoMessageHandlerWholeBase<PongMessage> {
+
+    public PojoMessageHandlerWholePong(Object pojo, Method method,
+            Session session, Object[] params, int indexPayload, boolean convert,
+            int indexSession) {
+        super(pojo, method, session, params, indexPayload, convert,
+                indexSession, -1);
     }
 
     @Override
-    public void write(int b) throws IOException {
-        processor.write(b);
+    protected Object decode(PongMessage message) {
+        // Never decoded
+        return null;
     }
 
+
     @Override
-    public void write(byte[] b, int off, int len) throws IOException {
-        processor.write(b, off, len);
+    protected void onClose() {
+        // NO-OP
     }
 }
diff --git a/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeText.java b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeText.java
new file mode 100644
index 0000000..9ce4ae7
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeText.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket.pojo;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.Decoder.Text;
+import javax.websocket.Decoder.TextStream;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+
+import org.apache.tomcat.util.res.StringManager;
+import org.apache.tomcat.websocket.Util;
+
+
+/**
+ * Text specific concrete implementation for handling whole messages.
+ */
+public class PojoMessageHandlerWholeText
+        extends PojoMessageHandlerWholeBase<String> {
+
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+
+    private final List<Decoder> decoders = new ArrayList<Decoder>();
+    private final Class<?> primitiveType;
+
+    public PojoMessageHandlerWholeText(Object pojo, Method method,
+            Session session, EndpointConfig config,
+            List<Class<? extends Decoder>> decoderClazzes, Object[] params,
+            int indexPayload, boolean convert, int indexSession,
+            long maxMessageSize) {
+        super(pojo, method, session, params, indexPayload, convert,
+                indexSession, maxMessageSize);
+
+        // Check for primitives
+        Class<?> type = method.getParameterTypes()[indexPayload];
+        if (Util.isPrimitive(type)) {
+            primitiveType = type;
+            return;
+        } else {
+            primitiveType = null;
+        }
+
+        try {
+            if (decoderClazzes != null) {
+                for (Class<? extends Decoder> decoderClazz : decoderClazzes) {
+                    if (Text.class.isAssignableFrom(decoderClazz)) {
+                        Text<?> decoder = (Text<?>) decoderClazz.newInstance();
+                        decoder.init(config);
+                        decoders.add(decoder);
+                    } else if (TextStream.class.isAssignableFrom(
+                            decoderClazz)) {
+                        TextStream<?> decoder =
+                                (TextStream<?>) decoderClazz.newInstance();
+                        decoder.init(config);
+                        decoders.add(decoder);
+                    } else {
+                        // Binary decoder - ignore it
+                    }
+                }
+            }
+        } catch (IllegalAccessException e) {
+            throw new IllegalArgumentException(e);
+        } catch (InstantiationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+
+    @Override
+    protected Object decode(String message) throws DecodeException {
+        // Handle primitives
+        if (primitiveType != null) {
+            return Util.coerceToType(primitiveType, message);
+        }
+        // Handle full decoders
+        for (Decoder decoder : decoders) {
+            if (decoder instanceof Text) {
+                if (((Text<?>) decoder).willDecode(message)) {
+                    return ((Text<?>) decoder).decode(message);
+                }
+            } else {
+                StringReader r = new StringReader(message);
+                try {
+                    return ((TextStream<?>) decoder).decode(r);
+                } catch (IOException ioe) {
+                    throw new DecodeException(message, sm.getString(
+                            "pojoMessageHandlerWhole.decodeIoFail"), ioe);
+                }
+            }
+        }
+        return null;
+    }
+
+
+    @Override
+    protected Object convert(String message) {
+        return new StringReader(message);
+    }
+
+
+    @Override
+    protected void onClose() {
+        for (Decoder decoder : decoders) {
+            decoder.destroy();
+        }
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java b/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java
new file mode 100644
index 0000000..8c1808f
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java
@@ -0,0 +1,606 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.pojo;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.websocket.CloseReason;
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.DeploymentException;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.PongMessage;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+
+import org.apache.tomcat.util.res.StringManager;
+import org.apache.tomcat.websocket.DecoderEntry;
+import org.apache.tomcat.websocket.Util;
+import org.apache.tomcat.websocket.Util.DecoderMatch;
+
+/**
+ * For a POJO class annotated with
+ * {@link javax.websocket.server.ServerEndpoint}, an instance of this class
+ * creates and caches the method handler, method information and parameter
+ * information for the onXXX calls.
+ */
+public class PojoMethodMapping {
+
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+
+    private final Method onOpen;
+    private final Method onClose;
+    private final Method onError;
+    private final PojoPathParam[] onOpenParams;
+    private final PojoPathParam[] onCloseParams;
+    private final PojoPathParam[] onErrorParams;
+    private final Set<MessageHandlerInfo> onMessage = new HashSet<MessageHandlerInfo>();
+    private final String wsPath;
+
+
+    public PojoMethodMapping(Class<?> clazzPojo,
+            Class<? extends Decoder>[] decoderClazzes, String wsPath)
+                    throws DeploymentException {
+
+        this.wsPath = wsPath;
+
+        List<DecoderEntry> decoders = Util.getDecoders(decoderClazzes);
+        Method open = null;
+        Method close = null;
+        Method error = null;
+        for (Method method : clazzPojo.getDeclaredMethods()) {
+            if (method.getAnnotation(OnOpen.class) != null) {
+                checkPublic(method);
+                if (open == null) {
+                    open = method;
+                } else {
+                    // Duplicate annotation
+                    throw new DeploymentException(sm.getString(
+                            "pojoMethodMapping.duplicateAnnotation",
+                            OnOpen.class, clazzPojo));
+                }
+            } else if (method.getAnnotation(OnClose.class) != null) {
+                checkPublic(method);
+                if (close == null) {
+                    close = method;
+                } else {
+                    // Duplicate annotation
+                    throw new DeploymentException(sm.getString(
+                            "pojoMethodMapping.duplicateAnnotation",
+                            OnClose.class, clazzPojo));
+                }
+            } else if (method.getAnnotation(OnError.class) != null) {
+                checkPublic(method);
+                if (error == null) {
+                    error = method;
+                } else {
+                    // Duplicate annotation
+                    throw new DeploymentException(sm.getString(
+                            "pojoMethodMapping.duplicateAnnotation",
+                            OnError.class, clazzPojo));
+                }
+            } else if (method.getAnnotation(OnMessage.class) != null) {
+                checkPublic(method);
+                onMessage.add(new MessageHandlerInfo(method, decoders));
+            } else {
+                // Method not annotated
+            }
+        }
+        this.onOpen = open;
+        this.onClose = close;
+        this.onError = error;
+        onOpenParams = getPathParams(onOpen, MethodType.ON_OPEN);
+        onCloseParams = getPathParams(onClose, MethodType.ON_CLOSE);
+        onErrorParams = getPathParams(onError, MethodType.ON_ERROR);
+    }
+
+
+    private void checkPublic(Method m) throws DeploymentException {
+        if (!Modifier.isPublic(m.getModifiers())) {
+            throw new DeploymentException(sm.getString(
+                    "pojoMethodMapping.methodNotPublic", m.getName()));
+        }
+    }
+
+
+    public String getWsPath() {
+        return wsPath;
+    }
+
+
+    public Method getOnOpen() {
+        return onOpen;
+    }
+
+
+    public Object[] getOnOpenArgs(Map<String,String> pathParameters,
+            Session session, EndpointConfig config) throws DecodeException {
+        return buildArgs(onOpenParams, pathParameters, session, config, null,
+                null);
+    }
+
+
+    public Method getOnClose() {
+        return onClose;
+    }
+
+
+    public Object[] getOnCloseArgs(Map<String,String> pathParameters,
+            Session session, CloseReason closeReason) throws DecodeException {
+        return buildArgs(onCloseParams, pathParameters, session, null, null,
+                closeReason);
+    }
+
+
+    public Method getOnError() {
+        return onError;
+    }
+
+
+    public Object[] getOnErrorArgs(Map<String,String> pathParameters,
+            Session session, Throwable throwable) throws DecodeException {
+        return buildArgs(onErrorParams, pathParameters, session, null,
+                throwable, null);
+    }
+
+
+    public Set<MessageHandler> getMessageHandlers(Object pojo,
+            Map<String,String> pathParameters, Session session,
+            EndpointConfig config) {
+        Set<MessageHandler> result = new HashSet<MessageHandler>();
+        for (MessageHandlerInfo messageMethod : onMessage) {
+            result.addAll(messageMethod.getMessageHandlers(pojo, pathParameters,
+                    session, config));
+        }
+        return result;
+    }
+
+
+    private static PojoPathParam[] getPathParams(Method m,
+            MethodType methodType) throws DeploymentException {
+        if (m == null) {
+            return new PojoPathParam[0];
+        }
+        boolean foundThrowable = false;
+        Class<?>[] types = m.getParameterTypes();
+        Annotation[][] paramsAnnotations = m.getParameterAnnotations();
+        PojoPathParam[] result = new PojoPathParam[types.length];
+        for (int i = 0; i < types.length; i++) {
+            Class<?> type = types[i];
+            if (type.equals(Session.class)) {
+                result[i] = new PojoPathParam(type, null);
+            } else if (methodType == MethodType.ON_OPEN &&
+                    type.equals(EndpointConfig.class)) {
+                result[i] = new PojoPathParam(type, null);
+            } else if (methodType == MethodType.ON_ERROR
+                    && type.equals(Throwable.class)) {
+                foundThrowable = true;
+                result[i] = new PojoPathParam(type, null);
+            } else if (methodType == MethodType.ON_CLOSE &&
+                    type.equals(CloseReason.class)) {
+                result[i] = new PojoPathParam(type, null);
+            } else {
+                Annotation[] paramAnnotations = paramsAnnotations[i];
+                for (Annotation paramAnnotation : paramAnnotations) {
+                    if (paramAnnotation.annotationType().equals(
+                            PathParam.class)) {
+                        // Check that the type is valid. "0" coerces to every
+                        // valid type
+                        try {
+                            Util.coerceToType(type, "0");
+                        } catch (IllegalArgumentException iae) {
+                            throw new DeploymentException(sm.getString(
+                                    "pojoMethodMapping.invalidPathParamType"),
+                                    iae);
+                        }
+                        result[i] = new PojoPathParam(type,
+                                ((PathParam) paramAnnotation).value());
+                        break;
+                    }
+                }
+                // Parameters without annotations are not permitted
+                if (result[i] == null) {
+                    throw new DeploymentException(sm.getString(
+                            "pojoMethodMapping.paramWithoutAnnotation",
+                            type, m.getName(), m.getClass().getName()));
+                }
+            }
+        }
+        if (methodType == MethodType.ON_ERROR && !foundThrowable) {
+            throw new DeploymentException(sm.getString(
+                    "pojoMethodMapping.onErrorNoThrowable",
+                    m.getName(), m.getDeclaringClass().getName()));
+        }
+        return result;
+    }
+
+
+    private static Object[] buildArgs(PojoPathParam[] pathParams,
+            Map<String,String> pathParameters, Session session,
+            EndpointConfig config, Throwable throwable, CloseReason closeReason)
+            throws DecodeException {
+        Object[] result = new Object[pathParams.length];
+        for (int i = 0; i < pathParams.length; i++) {
+            Class<?> type = pathParams[i].getType();
+            if (type.equals(Session.class)) {
+                result[i] = session;
+            } else if (type.equals(EndpointConfig.class)) {
+                result[i] = config;
+            } else if (type.equals(Throwable.class)) {
+                result[i] = throwable;
+            } else if (type.equals(CloseReason.class)) {
+                result[i] = closeReason;
+            } else {
+                String name = pathParams[i].getName();
+                String value = pathParameters.get(name);
+                try {
+                    result[i] = Util.coerceToType(type, value);
+                } catch (Exception e) {
+                    throw new DecodeException(value, sm.getString(
+                            "pojoMethodMapping.decodePathParamFail",
+                            value, type), e);
+                }
+            }
+        }
+        return result;
+    }
+
+
+    private static class MessageHandlerInfo {
+
+        private final Method m;
+        private int indexString = -1;
+        private int indexByteArray = -1;
+        private int indexByteBuffer = -1;
+        private int indexPong = -1;
+        private int indexBoolean = -1;
+        private int indexSession = -1;
+        private int indexInputStream = -1;
+        private int indexReader = -1;
+        private int indexPrimitive = -1;
+        private Map<Integer,PojoPathParam> indexPathParams = new HashMap<Integer, PojoPathParam>();
+        private int indexPayload = -1;
+        private DecoderMatch decoderMatch = null;
+        private long maxMessageSize = -1;
+
+        public MessageHandlerInfo(Method m, List<DecoderEntry> decoderEntries) {
+            this.m = m;
+
+            Class<?>[] types = m.getParameterTypes();
+            Annotation[][] paramsAnnotations = m.getParameterAnnotations();
+
+            for (int i = 0; i < types.length; i++) {
+                boolean paramFound = false;
+                Annotation[] paramAnnotations = paramsAnnotations[i];
+                for (Annotation paramAnnotation : paramAnnotations) {
+                    if (paramAnnotation.annotationType().equals(
+                            PathParam.class)) {
+                        indexPathParams.put(
+                                Integer.valueOf(i), new PojoPathParam(types[i],
+                                        ((PathParam) paramAnnotation).value()));
+                        paramFound = true;
+                        break;
+                    }
+                }
+                if (paramFound) {
+                    continue;
+                }
+                if (String.class.isAssignableFrom(types[i])) {
+                    if (indexString == -1) {
+                        indexString = i;
+                    } else {
+                        throw new IllegalArgumentException(sm.getString(
+                                "pojoMethodMapping.duplicateMessageParam",
+                                m.getName(), m.getDeclaringClass().getName()));
+                    }
+                } else if (Reader.class.isAssignableFrom(types[i])) {
+                    if (indexReader == -1) {
+                        indexReader = i;
+                    } else {
+                        throw new IllegalArgumentException(sm.getString(
+                                "pojoMethodMapping.duplicateMessageParam",
+                                m.getName(), m.getDeclaringClass().getName()));
+                    }
+                } else if (boolean.class == types[i]) {
+                    if (indexBoolean == -1) {
+                        indexBoolean = i;
+                    } else {
+                        throw new IllegalArgumentException(sm.getString(
+                                "pojoMethodMapping.duplicateLastParam",
+                                m.getName(), m.getDeclaringClass().getName()));
+                    }
+                } else if (ByteBuffer.class.isAssignableFrom(types[i])) {
+                    if (indexByteBuffer == -1) {
+                        indexByteBuffer = i;
+                    } else {
+                        throw new IllegalArgumentException(sm.getString(
+                                "pojoMethodMapping.duplicateMessageParam",
+                                m.getName(), m.getDeclaringClass().getName()));
+                    }
+                } else if (byte[].class == types[i]) {
+                    if (indexByteArray == -1) {
+                        indexByteArray = i;
+                    } else {
+                        throw new IllegalArgumentException(sm.getString(
+                                "pojoMethodMapping.duplicateMessageParam",
+                                m.getName(), m.getDeclaringClass().getName()));
+                    }
+                } else if (InputStream.class.isAssignableFrom(types[i])) {
+                    if (indexInputStream == -1) {
+                        indexInputStream = i;
+                    } else {
+                        throw new IllegalArgumentException(sm.getString(
+                                "pojoMethodMapping.duplicateMessageParam",
+                                m.getName(), m.getDeclaringClass().getName()));
+                    }
+                } else if (Util.isPrimitive(types[i])) {
+                    if (indexPrimitive == -1) {
+                        indexPrimitive = i;
+                    } else {
+                        throw new IllegalArgumentException(sm.getString(
+                                "pojoMethodMapping.duplicateMessageParam",
+                                m.getName(), m.getDeclaringClass().getName()));
+                    }
+                } else if (Session.class.isAssignableFrom(types[i])) {
+                    if (indexSession == -1) {
+                        indexSession = i;
+                    } else {
+                        throw new IllegalArgumentException(sm.getString(
+                                "pojoMethodMapping.duplicateSessionParam",
+                                m.getName(), m.getDeclaringClass().getName()));
+                    }
+                } else if (PongMessage.class.isAssignableFrom(types[i])) {
+                    if (indexPong == -1) {
+                        indexPong = i;
+                    } else {
+                        throw new IllegalArgumentException(sm.getString(
+                                "pojoMethodMapping.duplicatePongMessageParam",
+                                m.getName(), m.getDeclaringClass().getName()));
+                    }
+                } else {
+                    if (decoderMatch != null && decoderMatch.hasMatches()) {
+                        throw new IllegalArgumentException(sm.getString(
+                                "pojoMethodMapping.duplicateMessageParam",
+                                m.getName(), m.getDeclaringClass().getName()));
+                    }
+                    decoderMatch = new DecoderMatch(types[i], decoderEntries);
+
+                    if (decoderMatch.hasMatches()) {
+                        indexPayload = i;
+                    }
+                }
+            }
+
+            // Additional checks required
+            if (indexString != -1) {
+                if (indexPayload != -1) {
+                    throw new IllegalArgumentException(sm.getString(
+                            "pojoMethodMapping.duplicateMessageParam",
+                            m.getName(), m.getDeclaringClass().getName()));
+                } else {
+                    indexPayload = indexString;
+                }
+            }
+            if (indexReader != -1) {
+                if (indexPayload != -1) {
+                    throw new IllegalArgumentException(sm.getString(
+                            "pojoMethodMapping.duplicateMessageParam",
+                            m.getName(), m.getDeclaringClass().getName()));
+                } else {
+                    indexPayload = indexReader;
+                }
+            }
+            if (indexByteArray != -1) {
+                if (indexPayload != -1) {
+                    throw new IllegalArgumentException(sm.getString(
+                            "pojoMethodMapping.duplicateMessageParam",
+                            m.getName(), m.getDeclaringClass().getName()));
+                } else {
+                    indexPayload = indexByteArray;
+                }
+            }
+            if (indexByteBuffer != -1) {
+                if (indexPayload != -1) {
+                    throw new IllegalArgumentException(sm.getString(
+                            "pojoMethodMapping.duplicateMessageParam",
+                            m.getName(), m.getDeclaringClass().getName()));
+                } else {
+                    indexPayload = indexByteBuffer;
+                }
+            }
+            if (indexInputStream != -1) {
+                if (indexPayload != -1) {
+                    throw new IllegalArgumentException(sm.getString(
+                            "pojoMethodMapping.duplicateMessageParam",
+                            m.getName(), m.getDeclaringClass().getName()));
+                } else {
+                    indexPayload = indexInputStream;
+                }
+            }
+            if (indexPrimitive != -1) {
+                if (indexPayload != -1) {
+                    throw new IllegalArgumentException(sm.getString(
+                            "pojoMethodMapping.duplicateMessageParam",
+                            m.getName(), m.getDeclaringClass().getName()));
+                } else {
+                    indexPayload = indexPrimitive;
+                }
+            }
+            if (indexPong != -1) {
+                if (indexPayload != -1) {
+                    throw new IllegalArgumentException(sm.getString(
+                            "pojoMethodMapping.pongWithPayload",
+                            m.getName(), m.getDeclaringClass().getName()));
+                } else {
+                    indexPayload = indexPong;
+                }
+            }
+            if (indexPayload == -1 && indexPrimitive == -1 &&
+                    indexBoolean != -1) {
+                // The boolean we found is a payload, not a last flag
+                indexPayload = indexBoolean;
+                indexPrimitive = indexBoolean;
+                indexBoolean = -1;
+            }
+            if (indexPayload == -1) {
+                throw new IllegalArgumentException(sm.getString(
+                        "pojoMethodMapping.noPayload",
+                        m.getName(), m.getDeclaringClass().getName()));
+            }
+            if (indexPong != -1 && indexBoolean != -1) {
+                throw new IllegalArgumentException(sm.getString(
+                        "pojoMethodMapping.partialPong",
+                        m.getName(), m.getDeclaringClass().getName()));
+            }
+            if(indexReader != -1 && indexBoolean != -1) {
+                throw new IllegalArgumentException(sm.getString(
+                        "pojoMethodMapping.partialReader",
+                        m.getName(), m.getDeclaringClass().getName()));
+            }
+            if(indexInputStream != -1 && indexBoolean != -1) {
+                throw new IllegalArgumentException(sm.getString(
+                        "pojoMethodMapping.partialInputStream",
+                        m.getName(), m.getDeclaringClass().getName()));
+            }
+            if (decoderMatch != null && decoderMatch.hasMatches() &&
+                    indexBoolean != -1) {
+                throw new IllegalArgumentException(sm.getString(
+                        "pojoMethodMapping.partialObject",
+                        m.getName(), m.getDeclaringClass().getName()));
+            }
+
+            maxMessageSize = m.getAnnotation(OnMessage.class).maxMessageSize();
+        }
+
+
+        public Set<MessageHandler> getMessageHandlers(Object pojo,
+                Map<String,String> pathParameters, Session session,
+                EndpointConfig config) {
+            Object[] params = new Object[m.getParameterTypes().length];
+
+            for (Map.Entry<Integer,PojoPathParam> entry :
+                    indexPathParams.entrySet()) {
+                PojoPathParam pathParam = entry.getValue();
+                String valueString = pathParameters.get(pathParam.getName());
+                Object value = null;
+                try {
+                    value = Util.coerceToType(pathParam.getType(), valueString);
+                } catch (Exception e) {
+                    DecodeException de =  new DecodeException(valueString,
+                            sm.getString(
+                                    "pojoMethodMapping.decodePathParamFail",
+                                    valueString, pathParam.getType()), e);
+                    params = new Object[] { de };
+                }
+                params[entry.getKey().intValue()] = value;
+            }
+
+            Set<MessageHandler> results = new HashSet<MessageHandler>(2);
+            if (indexBoolean == -1) {
+                // Basic
+                if (indexString != -1 || indexPrimitive != -1) {
+                    MessageHandler mh = new PojoMessageHandlerWholeText(pojo, m,
+                            session, config, null, params, indexPayload, false,
+                            indexSession, maxMessageSize);
+                    results.add(mh);
+                } else if (indexReader != -1) {
+                    MessageHandler mh = new PojoMessageHandlerWholeText(pojo, m,
+                            session, config, null, params, indexReader, true,
+                            indexSession, maxMessageSize);
+                    results.add(mh);
+                } else if (indexByteArray != -1) {
+                    MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo,
+                            m, session, config, null, params, indexByteArray,
+                            true, indexSession, false, maxMessageSize);
+                    results.add(mh);
+                } else if (indexByteBuffer != -1) {
+                    MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo,
+                            m, session, config, null, params, indexByteBuffer,
+                            false, indexSession, false, maxMessageSize);
+                    results.add(mh);
+                } else if (indexInputStream != -1) {
+                    MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo,
+                            m, session, config, null, params, indexInputStream,
+                            true, indexSession, true, maxMessageSize);
+                    results.add(mh);
+                } else if (decoderMatch != null && decoderMatch.hasMatches()) {
+                    if (decoderMatch.getBinaryDecoders().size() > 0) {
+                        MessageHandler mh = new PojoMessageHandlerWholeBinary(
+                                pojo, m, session, config,
+                                decoderMatch.getBinaryDecoders(), params,
+                                indexPayload, true, indexSession, true,
+                                maxMessageSize);
+                        results.add(mh);
+                    }
+                    if (decoderMatch.getTextDecoders().size() > 0) {
+                        MessageHandler mh = new PojoMessageHandlerWholeText(
+                                pojo, m, session, config,
+                                decoderMatch.getTextDecoders(), params,
+                                indexPayload, true, indexSession, maxMessageSize);
+                        results.add(mh);
+                    }
+                } else {
+                    MessageHandler mh = new PojoMessageHandlerWholePong(pojo, m,
+                            session, params, indexPong, false, indexSession);
+                    results.add(mh);
+                }
+            } else {
+                // ASync
+                if (indexString != -1) {
+                    MessageHandler mh = new PojoMessageHandlerPartialText(pojo,
+                            m, session, params, indexString, false,
+                            indexBoolean, indexSession, maxMessageSize);
+                    results.add(mh);
+                } else if (indexByteArray != -1) {
+                    MessageHandler mh = new PojoMessageHandlerPartialBinary(
+                            pojo, m, session, params, indexByteArray, true,
+                            indexBoolean, indexSession, maxMessageSize);
+                    results.add(mh);
+                } else {
+                    MessageHandler mh = new PojoMessageHandlerPartialBinary(
+                            pojo, m, session, params, indexByteBuffer, false,
+                            indexBoolean, indexSession, maxMessageSize);
+                    results.add(mh);
+                }
+            }
+            return results;
+        }
+    }
+
+
+    private static enum MethodType {
+        ON_OPEN,
+        ON_CLOSE,
+        ON_ERROR
+    }
+}
diff --git a/java/org/apache/tomcat/util/net/SocketStatus.java b/java/org/apache/tomcat/websocket/pojo/PojoPathParam.java
similarity index 52%
copy from java/org/apache/tomcat/util/net/SocketStatus.java
copy to java/org/apache/tomcat/websocket/pojo/PojoPathParam.java
index bf53c2b..51cdab4 100644
--- a/java/org/apache/tomcat/util/net/SocketStatus.java
+++ b/java/org/apache/tomcat/websocket/pojo/PojoPathParam.java
@@ -14,14 +14,34 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-
-package org.apache.tomcat.util.net;
+package org.apache.tomcat.websocket.pojo;
 
 /**
- * Someone, please change the enum name.
- *
- * @author remm
+ * Stores the parameter type and name for a parameter that needs to be passed to
+ * an onXxx method of {@link javax.websocket.Endpoint}. The name is only present
+ * for parameters annotated with
+ * {@link javax.websocket.server.PathParam}. For the
+ * {@link javax.websocket.Session} and {@link java.lang.Throwable} parameters,
+ * {@link #getName()} will always return <code>null</code>.
  */
-public enum SocketStatus {
-    OPEN, STOP, TIMEOUT, DISCONNECT, ERROR
+public class PojoPathParam {
+
+    private final Class<?> type;
+    private final String name;
+
+
+    public PojoPathParam(Class<?> type, String name) {
+        this.type = type;
+        this.name = name;
+    }
+
+
+    public Class<?> getType() {
+        return type;
+    }
+
+
+    public String getName() {
+        return name;
+    }
 }
diff --git a/java/javax/annotation/PreDestroy.java b/java/org/apache/tomcat/websocket/pojo/package-info.java
similarity index 71%
copy from java/javax/annotation/PreDestroy.java
copy to java/org/apache/tomcat/websocket/pojo/package-info.java
index d5be75a..28daf9d 100644
--- a/java/javax/annotation/PreDestroy.java
+++ b/java/org/apache/tomcat/websocket/pojo/package-info.java
@@ -5,27 +5,17 @@
  * The ASF licenses this file to You 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 javax.annotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
- at Target({ElementType.METHOD})
- at Retention(RetentionPolicy.RUNTIME)
-
-public @interface PreDestroy {
-    // No attributes
-}
+/**
+ * This package provides the necessary plumbing to convert an annotated POJO
+ * into a WebSocket {@link javax.websocket.Endpoint}.
+ */
+package org.apache.tomcat.websocket.pojo;
\ No newline at end of file
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java b/java/org/apache/tomcat/websocket/server/Constants.java
similarity index 50%
copy from java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
copy to java/org/apache/tomcat/websocket/server/Constants.java
index 813bea8..9081e23 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
+++ b/java/org/apache/tomcat/websocket/server/Constants.java
@@ -14,35 +14,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.coyote.http11.upgrade;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
+package org.apache.tomcat.websocket.server;
 
 /**
- * Allows data to be written to the upgraded connection.
+ * Internal implementation constants.
  */
-public class UpgradeOutbound extends OutputStream {
+public class Constants {
 
-    @Override
-    public void flush() throws IOException {
-        processor.flush();
-    }
+    protected static final String PACKAGE_NAME =
+            Constants.class.getPackage().getName();
 
-    private final UpgradeProcessor<?> processor;
+    public static final String BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM =
+            "org.apache.tomcat.websocket.binaryBufferSize";
+    public static final String TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM =
+            "org.apache.tomcat.websocket.textBufferSize";
+    public static final String ENFORCE_NO_ADD_AFTER_HANDSHAKE_CONTEXT_INIT_PARAM =
+            "org.apache.tomcat.websocket.noAddAfterHandshake";
 
-    public UpgradeOutbound(UpgradeProcessor<?> processor) {
-        this.processor = processor;
-    }
+    public static final String SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE =
+            "javax.websocket.server.ServerContainer";
 
-    @Override
-    public void write(int b) throws IOException {
-        processor.write(b);
-    }
 
-    @Override
-    public void write(byte[] b, int off, int len) throws IOException {
-        processor.write(b, off, len);
+    private Constants() {
+        // Hide default constructor
     }
 }
diff --git a/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java b/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java
new file mode 100644
index 0000000..670f076
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket.server;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+
+public class DefaultServerEndpointConfigurator
+        extends ServerEndpointConfig.Configurator {
+
+    @Override
+    public <T> T getEndpointInstance(Class<T> clazz)
+            throws InstantiationException {
+        try {
+            return clazz.newInstance();
+        } catch (IllegalAccessException e) {
+            InstantiationException ie = new InstantiationException();
+            ie.initCause(e);
+            throw ie;
+        }
+    }
+
+
+    @Override
+    public String getNegotiatedSubprotocol(List<String> supported,
+            List<String> requested) {
+
+        for (String request : requested) {
+            if (supported.contains(request)) {
+                return request;
+            }
+        }
+        return "";
+    }
+
+
+    @Override
+    public List<Extension> getNegotiatedExtensions(List<Extension> installed,
+            List<Extension> requested) {
+
+        List<Extension> result = new ArrayList<Extension>();
+        for (Extension request : requested) {
+            if (installed.contains(request)) {
+                result.add(request);
+            }
+        }
+        return result;
+    }
+
+
+    @Override
+    public boolean checkOrigin(String originHeaderValue) {
+        return true;
+    }
+
+    @Override
+    public void modifyHandshake(ServerEndpointConfig sec,
+            HandshakeRequest request, HandshakeResponse response) {
+        // NO-OP
+    }
+
+}
diff --git a/java/org/apache/tomcat/websocket/server/LocalStrings.properties b/java/org/apache/tomcat/websocket/server/LocalStrings.properties
new file mode 100644
index 0000000..4afbb89
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/server/LocalStrings.properties
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+sci.noWebSocketSupport=JSR 356 WebSocket (Java WebSocket 1.0) support is not available when running on Java 6. To suppress this message, run Tomcat on Java 7, remove the WebSocket JARs from $CATALINA_HOME/lib or add the WebSocketJARs to the tomcat.util.scan.DefaultJarScanner.jarsToSkip property in $CATALINA_BASE/conf/catalina.properties. Note that the deprecated Tomcat 7 WebSocket API will be available. 
+
+serverContainer.addNotAllowed=No further Endpoints may be registered once an attempt has been made to use one of the previously registered endpoints
+serverContainer.configuratorFail=Failed to create configurator of type [{0}] for POJO of type [{1}]
+serverContainer.duplicatePaths=Multiple Endpoints may not be deployed to using the same path [{0}]
+serverContainer.encoderFail=Unable to create encoder of type [{0}]
+serverContainer.endpointDeploy=Endpoint class [{0}] deploying to path [{1}] in ServletContext [{2}]
+serverContainer.missingAnnotation=Cannot deploy POJO class [{0}] as it is not annotated with @ServerEndpoint
+serverContainer.missingEndpoint=An Endpoint instance has been request for path [{0}] but no matching Endpoint class was found
+serverContainer.pojoDeploy=POJO class [{0}] deploying to path [{1}] in ServletContext [{2}]
+serverContainer.servletContextMismatch=Attempted to register a POJO annotated for WebSocket at path [{0}] in the ServletContext with context path [{1}] when the WebSocket ServerContainer is allocated to the ServletContext with context path [{2}]
+serverContainer.servletContextMissing=No ServletContext was specified
+
+uriTemplate.duplicateParameter=The parameter [{0}] appears more than once in the path which is not permitted
+uriTemplate.emptySegment=The path [{0}] contains one or more empty segments which are is not permitted
+uriTemplate.invalidPath=The path [{0}] is not valid.
+uriTemplate.invalidSegment=The segment [{0}] is not valid in the provided path [{1}]
+
+wsHttpUpgradeHandler.destroyFailed=Failed to close WebConnection while destroying the WebSocket HttpUpgradeHandler
+wsHttpUpgradeHandler.noPreInit=The preInit() method must be called to configure the WebSocket HttpUpgradeHandler before the container calls init(). Usually, this means the Servlet that created the WsHttpUpgradeHandler instance should also call preInit()
+
+wsRemoteEndpointServer.closeFailed=Failed to close the ServletOutputStream connection cleanly
\ No newline at end of file
diff --git a/java/org/apache/tomcat/websocket/server/UpgradeUtil.java b/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
new file mode 100644
index 0000000..f02f6fa
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket.server;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+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.Map.Entry;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestWrapper;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.websocket.Endpoint;
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.apache.catalina.connector.RequestFacade;
+import org.apache.tomcat.util.codec.binary.Base64;
+import org.apache.tomcat.websocket.Constants;
+import org.apache.tomcat.websocket.WsHandshakeResponse;
+import org.apache.tomcat.websocket.pojo.PojoEndpointServer;
+
+public class UpgradeUtil {
+
+    private static final byte[] WS_ACCEPT =
+            "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(
+                    StandardCharsets.ISO_8859_1);
+    private static final Queue<MessageDigest> sha1Helpers =
+            new ConcurrentLinkedQueue<MessageDigest>();
+
+    private UpgradeUtil() {
+        // Utility class. Hide default constructor.
+    }
+
+    /**
+     * Checks to see if this is an HTTP request that includes a valid upgrade
+     * request to web socket.
+     * <p>
+     * Note: RFC 2616 does not limit HTTP upgrade to GET requests but the Java
+     *       WebSocket spec 1.0, section 8.2 implies such a limitation and RFC
+     *       6455 section 4.1 requires that a WebSocket Upgrade uses GET.
+     */
+    public static boolean isWebSocketUpgrageRequest(ServletRequest request,
+            ServletResponse response) {
+
+        return ((request instanceof HttpServletRequest) &&
+                (response instanceof HttpServletResponse) &&
+                headerContainsToken((HttpServletRequest) request,
+                        Constants.UPGRADE_HEADER_NAME,
+                        Constants.UPGRADE_HEADER_VALUE) &&
+                "GET".equals(((HttpServletRequest) request).getMethod()));
+    }
+
+
+    public static void doUpgrade(WsServerContainer sc, HttpServletRequest req,
+            HttpServletResponse resp, ServerEndpointConfig sec,
+            Map<String,String> pathParams)
+            throws ServletException, IOException {
+
+        // Validate the rest of the headers and reject the request if that
+        // validation fails
+        String key;
+        String subProtocol = null;
+        List<Extension> extensions = Collections.emptyList();
+        if (!headerContainsToken(req, Constants.CONNECTION_HEADER_NAME,
+                Constants.CONNECTION_HEADER_VALUE)) {
+            resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        if (!headerContainsToken(req, Constants.WS_VERSION_HEADER_NAME,
+                Constants.WS_VERSION_HEADER_VALUE)) {
+            resp.setStatus(426);
+            resp.setHeader(Constants.WS_VERSION_HEADER_NAME,
+                    Constants.WS_VERSION_HEADER_VALUE);
+            return;
+        }
+        key = req.getHeader(Constants.WS_KEY_HEADER_NAME);
+        if (key == null) {
+            resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+
+
+        // Origin check
+        String origin = req.getHeader("Origin");
+        if (!sec.getConfigurator().checkOrigin(origin)) {
+            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }
+        // Sub-protocols
+        List<String> subProtocols = getTokensFromHeader(req,
+                "Sec-WebSocket-Protocol");
+        if (!subProtocols.isEmpty()) {
+            subProtocol = sec.getConfigurator().
+                    getNegotiatedSubprotocol(
+                            sec.getSubprotocols(), subProtocols);
+        }
+
+        // Extensions
+        // Currently no extensions are supported by this implementation
+
+        // If we got this far, all is good. Accept the connection.
+        resp.setHeader(Constants.UPGRADE_HEADER_NAME,
+                Constants.UPGRADE_HEADER_VALUE);
+        resp.setHeader(Constants.CONNECTION_HEADER_NAME,
+                Constants.CONNECTION_HEADER_VALUE);
+        resp.setHeader(HandshakeResponse.SEC_WEBSOCKET_ACCEPT,
+                getWebSocketAccept(key));
+        if (subProtocol != null) {
+            resp.setHeader("Sec-WebSocket-Protocol", subProtocol);
+        }
+        if (!extensions.isEmpty()) {
+            StringBuilder sb = new StringBuilder();
+            Iterator<Extension> iter = extensions.iterator();
+            // There must be at least one
+            sb.append(iter.next());
+            while (iter.hasNext()) {
+                sb.append(',');
+                sb.append(iter.next().getName());
+            }
+            resp.setHeader("Sec-WebSocket-Extensions", sb.toString());
+        }
+        Endpoint ep;
+        try {
+            Class<?> clazz = sec.getEndpointClass();
+            if (Endpoint.class.isAssignableFrom(clazz)) {
+                ep = (Endpoint) sec.getConfigurator().getEndpointInstance(
+                        clazz);
+            } else {
+                ep = new PojoEndpointServer();
+            }
+        } catch (InstantiationException e) {
+            throw new ServletException(e);
+        }
+
+        WsHandshakeRequest wsRequest = new WsHandshakeRequest(req);
+        WsHandshakeResponse wsResponse = new WsHandshakeResponse();
+        sec.getConfigurator().modifyHandshake(sec, wsRequest, wsResponse);
+        wsRequest.finished();
+
+        // Add any additional headers
+        for (Entry<String,List<String>> entry :
+                wsResponse.getHeaders().entrySet()) {
+            for (String headerValue: entry.getValue()) {
+                resp.addHeader(entry.getKey(), headerValue);
+            }
+        }
+
+        // Small hack until the Servlet API provides a way to do this.
+        ServletRequest inner = req;
+        // Unwrap the request
+        while (inner instanceof ServletRequestWrapper) {
+            inner = ((ServletRequestWrapper) inner).getRequest();
+        }
+        if (inner instanceof RequestFacade) {
+            WsHttpUpgradeHandler wsHandler =
+                    ((RequestFacade) inner).upgrade(WsHttpUpgradeHandler.class);
+            wsHandler.preInit(ep, sec, sc, wsRequest, subProtocol,
+                    pathParams, req.isSecure());
+        } else {
+            throw new ServletException("Upgrade failed");
+        }
+    }
+
+
+    /*
+     * This only works for tokens. Quoted strings need more sophisticated
+     * parsing.
+     */
+    private static boolean headerContainsToken(HttpServletRequest req,
+            String headerName, String target) {
+        Enumeration<String> headers = req.getHeaders(headerName);
+        while (headers.hasMoreElements()) {
+            String header = headers.nextElement();
+            String[] tokens = header.split(",");
+            for (String token : tokens) {
+                if (target.equalsIgnoreCase(token.trim())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+
+    /*
+     * This only works for tokens. Quoted strings need more sophisticated
+     * parsing.
+     */
+    private static List<String> getTokensFromHeader(HttpServletRequest req,
+            String headerName) {
+        List<String> result = new ArrayList<String>();
+        Enumeration<String> headers = req.getHeaders(headerName);
+        while (headers.hasMoreElements()) {
+            String header = headers.nextElement();
+            String[] tokens = header.split(",");
+            for (String token : tokens) {
+                result.add(token.trim());
+            }
+        }
+        return result;
+    }
+
+
+    private static String getWebSocketAccept(String key) throws ServletException {
+        MessageDigest sha1Helper = sha1Helpers.poll();
+        if (sha1Helper == null) {
+            try {
+                sha1Helper = MessageDigest.getInstance("SHA1");
+            } catch (NoSuchAlgorithmException e) {
+                throw new ServletException(e);
+            }
+        }
+        sha1Helper.reset();
+        sha1Helper.update(key.getBytes(StandardCharsets.ISO_8859_1));
+        String result = Base64.encodeBase64String(sha1Helper.digest(WS_ACCEPT));
+        sha1Helpers.add(sha1Helper);
+        return result;
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/server/UriTemplate.java b/java/org/apache/tomcat/websocket/server/UriTemplate.java
new file mode 100644
index 0000000..26ea7f3
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/server/UriTemplate.java
@@ -0,0 +1,178 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.server;
+
+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.Set;
+
+import javax.websocket.DeploymentException;
+
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Extracts path parameters from URIs used to create web socket connections
+ * using the URI template defined for the associated Endpoint.
+ */
+public class UriTemplate {
+
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+
+    private final String normalized;
+    private final List<Segment> segments = new ArrayList<Segment>();
+    private final boolean hasParameters;
+
+
+    public UriTemplate(String path) throws DeploymentException {
+
+        if (path == null || path.length() ==0 || !path.startsWith("/")) {
+            throw new DeploymentException(
+                    sm.getString("uriTemplate.invalidPath", path));
+        }
+
+        StringBuilder normalized = new StringBuilder(path.length());
+        Set<String> paramNames = new HashSet<String>();
+
+        // Include empty segments.
+        String[] segments = path.split("/", -1);
+        int paramCount = 0;
+        int segmentCount = 0;
+
+        for (int i = 0; i < segments.length; i++) {
+            String segment = segments[i];
+            if (segment.length() == 0) {
+                if (i == 0 || (i == segments.length - 1 && paramCount == 0)) {
+                    // Ignore the first empty segment as the path must always
+                    // start with '/'
+                    // Ending with a '/' is also OK for instances used for
+                    // matches but not for parameterised templates.
+                    continue;
+                } else {
+                    // As per EG discussion, all other empty segments are
+                    // invalid
+                    throw new IllegalArgumentException(sm.getString(
+                            "uriTemplate.emptySegment", path));
+                }
+            }
+            normalized.append('/');
+            int index = -1;
+            if (segment.startsWith("{") && segment.endsWith("}")) {
+                index = segmentCount;
+                segment = segment.substring(1, segment.length() - 1);
+                normalized.append('{');
+                normalized.append(paramCount++);
+                normalized.append('}');
+                if (!paramNames.add(segment)) {
+                    throw new IllegalArgumentException(sm.getString(
+                            "uriTemplate.duplicateParameter", segment));
+                }
+            } else {
+                if (segment.contains("{") || segment.contains("}")) {
+                    throw new IllegalArgumentException(sm.getString(
+                            "uriTemplate.invalidSegment", segment, path));
+                }
+                normalized.append(segment);
+            }
+            this.segments.add(new Segment(index, segment));
+            segmentCount++;
+        }
+
+        this.normalized = normalized.toString();
+        this.hasParameters = paramCount > 0;
+    }
+
+
+    public Map<String,String> match(UriTemplate candidate) {
+
+        Map<String,String> result = new HashMap<String, String>();
+
+        // Should not happen but for safety
+        if (candidate.getSegmentCount() != getSegmentCount()) {
+            return null;
+        }
+
+        Iterator<Segment> candidateSegments =
+                candidate.getSegments().iterator();
+        Iterator<Segment> targetSegments = segments.iterator();
+
+        while (candidateSegments.hasNext()) {
+            Segment candidateSegment = candidateSegments.next();
+            Segment targetSegment = targetSegments.next();
+
+            if (targetSegment.getParameterIndex() == -1) {
+                // Not a parameter - values must match
+                if (!targetSegment.getValue().equals(
+                        candidateSegment.getValue())) {
+                    // Not a match. Stop here
+                    return null;
+                }
+            } else {
+                // Parameter
+                result.put(targetSegment.getValue(),
+                        candidateSegment.getValue());
+            }
+        }
+
+        return result;
+    }
+
+
+    public boolean hasParameters() {
+        return hasParameters;
+    }
+
+
+    public int getSegmentCount() {
+        return segments.size();
+    }
+
+
+    public String getNormalizedPath() {
+        return normalized;
+    }
+
+
+    private List<Segment> getSegments() {
+        return segments;
+    }
+
+
+    private static class Segment {
+        private final int parameterIndex;
+        private final String value;
+
+        public Segment(int parameterIndex, String value) {
+            this.parameterIndex = parameterIndex;
+            this.value = value;
+        }
+
+
+        public int getParameterIndex() {
+            return parameterIndex;
+        }
+
+
+        public String getValue() {
+            return value;
+        }
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/server/WsContextListener.java b/java/org/apache/tomcat/websocket/server/WsContextListener.java
new file mode 100644
index 0000000..27ea702
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/server/WsContextListener.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.server;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+/**
+ * In normal usage, this {@link ServletContextListener} does not need to be
+ * explicitly configured as the {@link WsSci} performs all the necessary
+ * bootstrap and installs this listener in the {@link ServletContext}. If the
+ * {@link WsSci} is disabled, this listener must be added manually to every
+ * {@link ServletContext} that uses WebSocket to bootstrap the
+ * {@link WsServerContainer} correctly.
+ */
+public class WsContextListener implements ServletContextListener {
+
+    @Override
+    public void contextInitialized(ServletContextEvent sce) {
+        ServletContext sc = sce.getServletContext();
+        // Don't trigger WebSocket initialization if a WebSocket Server
+        // Container is already present
+        if (sc.getAttribute(Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE) == null) {
+            WsSci.init(sce.getServletContext(), false);
+        }
+    }
+
+    @Override
+    public void contextDestroyed(ServletContextEvent sce) {
+        ServletContext sc = sce.getServletContext();
+        Object obj = sc.getAttribute(Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
+        if (obj instanceof WsServerContainer) {
+            ((WsServerContainer) obj).destroy();
+        }
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/server/WsFilter.java b/java/org/apache/tomcat/websocket/server/WsFilter.java
new file mode 100644
index 0000000..8aadd00
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/server/WsFilter.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket.server;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Handles the initial HTTP connection for WebSocket connections.
+ */
+public class WsFilter implements Filter {
+
+    private WsServerContainer sc;
+
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        sc = (WsServerContainer) filterConfig.getServletContext().getAttribute(
+                Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
+    }
+
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response,
+            FilterChain chain) throws IOException, ServletException {
+
+        // This filter only needs to handle WebSocket upgrade requests
+        if (!UpgradeUtil.isWebSocketUpgrageRequest(request, response)) {
+            chain.doFilter(request, response);
+            return;
+        }
+
+        // HTTP request with an upgrade header for WebSocket present
+        HttpServletRequest req = (HttpServletRequest) request;
+        HttpServletResponse resp = (HttpServletResponse) response;
+
+        // Check to see if this WebSocket implementation has a matching mapping
+        String path;
+        String pathInfo = req.getPathInfo();
+        if (pathInfo == null) {
+            path = req.getServletPath();
+        } else {
+            path = req.getServletPath() + pathInfo;
+        }
+        WsMappingResult mappingResult = sc.findMapping(path);
+
+        if (mappingResult == null) {
+            // No endpoint registered for the requested path. Let the
+            // application handle it (it might redirect or forward for example)
+            chain.doFilter(request, response);
+            return;
+        }
+
+        UpgradeUtil.doUpgrade(sc, req, resp, mappingResult.getConfig(),
+                mappingResult.getPathParams());
+    }
+
+
+    @Override
+    public void destroy() {
+        // NO-OP
+    }
+
+
+}
diff --git a/java/org/apache/tomcat/websocket/server/WsFrameServer.java b/java/org/apache/tomcat/websocket/server/WsFrameServer.java
new file mode 100644
index 0000000..747d020
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/server/WsFrameServer.java
@@ -0,0 +1,65 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.server;
+
+import java.io.EOFException;
+import java.io.IOException;
+
+import org.apache.coyote.http11.upgrade.AbstractServletInputStream;
+import org.apache.tomcat.websocket.WsFrameBase;
+import org.apache.tomcat.websocket.WsSession;
+
+public class WsFrameServer extends WsFrameBase {
+
+    private final AbstractServletInputStream sis;
+    private final Object connectionReadLock = new Object();
+
+
+    public WsFrameServer(AbstractServletInputStream sis, WsSession wsSession) {
+        super(wsSession);
+        this.sis = sis;
+    }
+
+
+    /**
+     * Called when there is data in the ServletInputStream to process.
+     */
+    public void onDataAvailable() throws IOException {
+        synchronized (connectionReadLock) {
+            while (isOpen() && sis.isReady()) {
+                // Fill up the input buffer with as much data as we can
+                int read = sis.read(
+                        inputBuffer, writePos, inputBuffer.length - writePos);
+                if (read == 0) {
+                    return;
+                }
+                if (read == -1) {
+                    throw new EOFException();
+                }
+                writePos += read;
+                processInputBuffer();
+            }
+        }
+    }
+
+
+    @Override
+    protected boolean isMasked() {
+        // Data is from the client so it should be masked
+        return true;
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java b/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
new file mode 100644
index 0000000..f3407cd
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket.server;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.websocket.server.HandshakeRequest;
+
+/**
+ * Represents the request that this session was opened under.
+ */
+public class WsHandshakeRequest implements HandshakeRequest {
+
+    private final URI requestUri;
+    private final Map<String,List<String>> parameterMap;
+    private final String queryString;
+    private final Principal userPrincipal;
+    private final Map<String,List<String>> headers;
+    private final Object httpSession;
+
+    private volatile HttpServletRequest request;
+
+
+    public WsHandshakeRequest(HttpServletRequest request) {
+
+        this.request = request;
+
+        queryString = request.getQueryString();
+        userPrincipal = request.getUserPrincipal();
+        httpSession = request.getSession(false);
+
+        // URI
+        StringBuilder sb = new StringBuilder(request.getRequestURI());
+        if (queryString != null) {
+            sb.append("?");
+            sb.append(queryString);
+        }
+        try {
+            requestUri = new URI(sb.toString());
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException(e);
+        }
+
+        // ParameterMap
+        Map<String,String[]> originalParameters = request.getParameterMap();
+        Map<String,List<String>> newParameters =
+                new HashMap<String, List<String>>(originalParameters.size());
+        for (Entry<String,String[]> entry : originalParameters.entrySet()) {
+            newParameters.put(entry.getKey(),
+                    Collections.unmodifiableList(
+                            Arrays.asList(entry.getValue())));
+        }
+        parameterMap = Collections.unmodifiableMap(newParameters);
+
+        // Headers
+        Map<String,List<String>> newHeaders = new HashMap<String, List<String>>();
+
+        Enumeration<String> headerNames = request.getHeaderNames();
+        while (headerNames.hasMoreElements()) {
+            String headerName = headerNames.nextElement();
+
+            newHeaders.put(headerName, Collections.unmodifiableList(
+                    Collections.list(request.getHeaders(headerName))));
+        }
+
+        headers = Collections.unmodifiableMap(newHeaders);
+    }
+
+    @Override
+    public URI getRequestURI() {
+        return requestUri;
+    }
+
+    @Override
+    public Map<String,List<String>> getParameterMap() {
+        return parameterMap;
+    }
+
+    @Override
+    public String getQueryString() {
+        return queryString;
+    }
+
+    @Override
+    public Principal getUserPrincipal() {
+        return userPrincipal;
+    }
+
+    @Override
+    public Map<String,List<String>> getHeaders() {
+        return headers;
+    }
+
+    @Override
+    public boolean isUserInRole(String role) {
+        if (request == null) {
+            throw new IllegalStateException();
+        }
+
+        return request.isUserInRole(role);
+    }
+
+    @Override
+    public Object getHttpSession() {
+        return httpSession;
+    }
+
+    /**
+     * Called when the HandshakeRequest is no longer required. Since an instance
+     * of this class retains a reference to the current HttpServletRequest that
+     * reference needs to be cleared as the HttpServletRequest may be reused.
+     *
+     * There is no reason for instances of this class to be accessed once the
+     * handshake has been completed.
+     */
+    void finished() {
+        request = null;
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java b/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java
new file mode 100644
index 0000000..b611671
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java
@@ -0,0 +1,243 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.server;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.http.HttpSession;
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+
+import org.apache.coyote.http11.upgrade.AbstractServletInputStream;
+import org.apache.coyote.http11.upgrade.AbstractServletOutputStream;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
+import org.apache.coyote.http11.upgrade.servlet31.ReadListener;
+import org.apache.coyote.http11.upgrade.servlet31.WebConnection;
+import org.apache.coyote.http11.upgrade.servlet31.WriteListener;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+import org.apache.tomcat.websocket.WsIOException;
+import org.apache.tomcat.websocket.WsSession;
+
+/**
+ * Servlet 3.1 HTTP upgrade handler for WebSocket connections.
+ */
+public class WsHttpUpgradeHandler implements HttpUpgradeHandler {
+
+    private static final Log log =
+            LogFactory.getLog(WsHttpUpgradeHandler.class);
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+
+    private final ClassLoader applicationClassLoader;
+
+    private Endpoint ep;
+    private EndpointConfig endpointConfig;
+    private WsServerContainer webSocketContainer;
+    private WsHandshakeRequest handshakeRequest;
+    private String subProtocol;
+    private Map<String,String> pathParameters;
+    private boolean secure;
+    private WebConnection connection;
+
+    private WsSession wsSession;
+
+
+    public WsHttpUpgradeHandler() {
+        applicationClassLoader = Thread.currentThread().getContextClassLoader();
+    }
+
+
+    public void preInit(Endpoint ep, EndpointConfig endpointConfig,
+            WsServerContainer wsc, WsHandshakeRequest handshakeRequest,
+            String subProtocol, Map<String,String> pathParameters,
+            boolean secure) {
+        this.ep = ep;
+        this.endpointConfig = endpointConfig;
+        this.webSocketContainer = wsc;
+        this.handshakeRequest = handshakeRequest;
+        this.subProtocol = subProtocol;
+        this.pathParameters = pathParameters;
+        this.secure = secure;
+    }
+
+
+    @Override
+    public void init(WebConnection connection) {
+        if (ep == null) {
+            throw new IllegalStateException(
+                    sm.getString("wsHttpUpgradeHandler.noPreInit"));
+        }
+
+        this.connection = connection;
+
+        AbstractServletInputStream sis;
+        AbstractServletOutputStream sos;
+        try {
+            sis = connection.getInputStream();
+            sos = connection.getOutputStream();
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+
+        String httpSessionId = null;
+        Object session = handshakeRequest.getHttpSession();
+        if (session != null ) {
+            httpSessionId = ((HttpSession) session).getId();
+        }
+
+        // Need to call onOpen using the web application's class loader
+        // Create the frame using the application's class loader so it can pick
+        // up application specific config from the ServerContainerImpl
+        Thread t = Thread.currentThread();
+        ClassLoader cl = t.getContextClassLoader();
+        t.setContextClassLoader(applicationClassLoader);
+        try {
+            WsRemoteEndpointImplServer wsRemoteEndpointServer =
+                    new WsRemoteEndpointImplServer(sos, webSocketContainer);
+            wsSession = new WsSession(ep, wsRemoteEndpointServer,
+                    webSocketContainer, handshakeRequest.getRequestURI(),
+                    handshakeRequest.getParameterMap(),
+                    handshakeRequest.getQueryString(),
+                    handshakeRequest.getUserPrincipal(), httpSessionId,
+                    subProtocol, pathParameters, secure, endpointConfig);
+            WsFrameServer wsFrame = new WsFrameServer(
+                    sis,
+                    wsSession);
+            sos.setWriteListener(
+                    new WsWriteListener(this, wsRemoteEndpointServer));
+            ep.onOpen(wsSession, endpointConfig);
+            webSocketContainer.registerSession(ep, wsSession);
+            sis.setReadListener(new WsReadListener(this, wsFrame));
+        } catch (DeploymentException e) {
+            throw new IllegalArgumentException(e);
+        } finally {
+            t.setContextClassLoader(cl);
+        }
+    }
+
+
+    @Override
+    public void destroy() {
+        try {
+            connection.close();
+        } catch (Exception e) {
+            log.error(sm.getString("wsHttpUpgradeHandler.destroyFailed"), e);
+        }
+    }
+
+
+    private void onError(Throwable throwable) {
+        // Need to call onError using the web application's class loader
+        Thread t = Thread.currentThread();
+        ClassLoader cl = t.getContextClassLoader();
+        t.setContextClassLoader(applicationClassLoader);
+        try {
+            ep.onError(wsSession, throwable);
+        } finally {
+            t.setContextClassLoader(cl);
+        }
+    }
+
+
+    private void close(CloseReason cr) {
+        /*
+         * Any call to this method is a result of a problem reading from the
+         * client. At this point that state of the connection is unknown.
+         * Attempt to send a close frame to the client and then close the socket
+         * immediately. There is no point in waiting for a close frame from the
+         * client because there is no guarantee that we can recover from
+         * whatever messed up state the client put the connection into.
+         */
+        wsSession.onClose(cr);
+    }
+
+
+    private static class WsReadListener implements ReadListener {
+
+        private final WsHttpUpgradeHandler wsProtocolHandler;
+        private final WsFrameServer wsFrame;
+
+
+        private WsReadListener(WsHttpUpgradeHandler wsProtocolHandler,
+                WsFrameServer wsFrame) {
+            this.wsProtocolHandler = wsProtocolHandler;
+            this.wsFrame = wsFrame;
+        }
+
+
+        @Override
+        public void onDataAvailable() {
+            try {
+                wsFrame.onDataAvailable();
+            } catch (WsIOException ws) {
+                wsProtocolHandler.close(ws.getCloseReason());
+            } catch (EOFException eof) {
+                CloseReason cr = new CloseReason(
+                        CloseCodes.CLOSED_ABNORMALLY, eof.getMessage());
+                wsProtocolHandler.close(cr);
+            } catch (IOException ioe) {
+                onError(ioe);
+            }
+        }
+
+
+        @Override
+        public void onAllDataRead() {
+            // Will never happen with WebSocket
+            throw new IllegalStateException();
+        }
+
+
+        @Override
+        public void onError(Throwable throwable) {
+            wsProtocolHandler.onError(throwable);
+        }
+    }
+
+
+    private static class WsWriteListener implements WriteListener {
+
+        private final WsHttpUpgradeHandler wsProtocolHandler;
+        private final WsRemoteEndpointImplServer wsRemoteEndpointServer;
+
+        private WsWriteListener(WsHttpUpgradeHandler wsProtocolHandler,
+                WsRemoteEndpointImplServer wsRemoteEndpointServer) {
+            this.wsProtocolHandler = wsProtocolHandler;
+            this.wsRemoteEndpointServer = wsRemoteEndpointServer;
+        }
+
+
+        @Override
+        public void onWritePossible() {
+            wsRemoteEndpointServer.onWritePossible();
+        }
+
+
+        @Override
+        public void onError(Throwable throwable) {
+            wsProtocolHandler.onError(throwable);
+            wsRemoteEndpointServer.close();
+        }
+    }
+}
diff --git a/java/javax/annotation/security/DeclareRoles.java b/java/org/apache/tomcat/websocket/server/WsMappingResult.java
similarity index 55%
copy from java/javax/annotation/security/DeclareRoles.java
copy to java/org/apache/tomcat/websocket/server/WsMappingResult.java
index d5d214a..097386b 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/java/org/apache/tomcat/websocket/server/WsMappingResult.java
@@ -5,27 +5,40 @@
  * The ASF licenses this file to You 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
- * 
+ *
+ *     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.apache.tomcat.websocket.server;
 
+import java.util.Map;
 
-package javax.annotation.security;
+import javax.websocket.server.ServerEndpointConfig;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+class WsMappingResult {
 
- at Target({ElementType.TYPE})
- at Retention(RetentionPolicy.RUNTIME)
+    private final ServerEndpointConfig config;
+    private final Map<String,String> pathParams;
 
-public @interface DeclareRoles {
-    public String[] value();
+
+    WsMappingResult(ServerEndpointConfig config,
+            Map<String,String> pathParams) {
+        this.config = config;
+        this.pathParams = pathParams;
+    }
+
+
+    ServerEndpointConfig getConfig() {
+        return config;
+    }
+
+
+    Map<String,String> getPathParams() {
+        return pathParams;
+    }
 }
diff --git a/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java b/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
new file mode 100644
index 0000000..435d9a5
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket.server;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+
+import javax.websocket.SendHandler;
+import javax.websocket.SendResult;
+
+import org.apache.coyote.http11.upgrade.AbstractServletOutputStream;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+import org.apache.tomcat.websocket.WsRemoteEndpointImplBase;
+
+/**
+ * This is the server side {@link javax.websocket.RemoteEndpoint} implementation
+ * - i.e. what the server uses to send data to the client. Communication is over
+ * a {@link javax.servlet.ServletOutputStream}.
+ */
+public class WsRemoteEndpointImplServer extends WsRemoteEndpointImplBase {
+
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+    private static final Log log =
+            LogFactory.getLog(WsHttpUpgradeHandler.class);
+
+    private final AbstractServletOutputStream sos;
+    private final WsWriteTimeout wsWriteTimeout;
+    private volatile SendHandler handler = null;
+    private volatile ByteBuffer[] buffers = null;
+
+    private volatile long timeoutExpiry = -1;
+    private volatile boolean close;
+
+
+    public WsRemoteEndpointImplServer(AbstractServletOutputStream sos,
+            WsServerContainer serverContainer) {
+        this.sos = sos;
+        this.wsWriteTimeout = serverContainer.getTimeout();
+    }
+
+
+    @Override
+    protected final boolean isMasked() {
+        return false;
+    }
+
+
+    @Override
+    protected void doWrite(SendHandler handler, ByteBuffer... buffers) {
+        this.handler = handler;
+        this.buffers = buffers;
+        onWritePossible();
+    }
+
+
+    public void onWritePossible() {
+        boolean complete = true;
+        try {
+            // If this is false there will be a call back when it is true
+            while (sos.isReady()) {
+                complete = true;
+                for (ByteBuffer buffer : buffers) {
+                    if (buffer.hasRemaining()) {
+                        complete = false;
+                        sos.write(buffer.array(), buffer.arrayOffset(),
+                                buffer.limit());
+                        buffer.position(buffer.limit());
+                        break;
+                    }
+                }
+                if (complete) {
+                    wsWriteTimeout.unregister(this);
+                    if (close) {
+                        close();
+                    }
+                    // Setting the result marks this (partial) message as
+                    // complete which means the next one may be sent which
+                    // could update the value of the handler. Therefore, keep a
+                    // local copy before signalling the end of the (partial)
+                    // message.
+                    clearHandler(null);
+                    break;
+                }
+            }
+
+        } catch (IOException ioe) {
+            wsWriteTimeout.unregister(this);
+            close();
+            clearHandler(ioe);
+        }
+        if (!complete) {
+            // Async write is in progress
+
+            long timeout = getSendTimeout();
+            if (timeout > 0) {
+                // Register with timeout thread
+                timeoutExpiry = timeout + System.currentTimeMillis();
+                wsWriteTimeout.register(this);
+            }
+        }
+    }
+
+
+    @Override
+    protected void doClose() {
+        if (handler != null) {
+            clearHandler(new EOFException());
+        }
+        try {
+            sos.close();
+        } catch (IOException e) {
+            if (log.isInfoEnabled()) {
+                log.info(sm.getString("wsRemoteEndpointServer.closeFailed"), e);
+            }
+        }
+        wsWriteTimeout.unregister(this);
+    }
+
+
+    protected long getTimeoutExpiry() {
+        return timeoutExpiry;
+    }
+
+
+    protected void onTimeout() {
+        if (handler != null) {
+            clearHandler(new SocketTimeoutException());
+        }
+        close();
+    }
+
+
+    private void clearHandler(Throwable t) {
+        SendHandler sh = handler;
+        handler = null;
+        if (sh != null) {
+            if (t == null) {
+                sh.onResult(new SendResult());
+            } else {
+                sh.onResult(new SendResult(t));
+            }
+        }
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/server/WsSci.java b/java/org/apache/tomcat/websocket/server/WsSci.java
new file mode 100644
index 0000000..cfa4c70
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/server/WsSci.java
@@ -0,0 +1,178 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.server;
+
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.HandlesTypes;
+import javax.websocket.ContainerProvider;
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.server.ServerApplicationConfig;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Registers an interest in any class that is annotated with
+ * {@link ServerEndpoint} so that Endpoint can be published via the WebSocket
+ * server.
+ */
+ at HandlesTypes({ServerEndpoint.class, ServerApplicationConfig.class,
+        Endpoint.class})
+public class WsSci implements ServletContainerInitializer {
+
+    private static boolean logMessageWritten = false;
+
+    private static final Log log =
+            LogFactory.getLog(WsSci.class);
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+
+    @Override
+    public void onStartup(Set<Class<?>> clazzes, ServletContext ctx)
+            throws ServletException {
+
+        if (!isJava7OrLater()) {
+            // The WebSocket implementation requires Java 7 so don't initialise
+            // it if Java 7 is not available.
+            if (!logMessageWritten) {
+                logMessageWritten = true;
+                log.info(sm.getString("sci.noWebSocketSupport"));
+            }
+            return;
+        }
+
+        WsServerContainer sc = init(ctx, true);
+
+        if (clazzes == null || clazzes.size() == 0) {
+            return;
+        }
+
+        // Group the discovered classes by type
+        Set<ServerApplicationConfig> serverApplicationConfigs = new HashSet<ServerApplicationConfig>();
+        Set<Class<? extends Endpoint>> scannedEndpointClazzes = new HashSet<Class<? extends Endpoint>>();
+        Set<Class<?>> scannedPojoEndpoints = new HashSet<Class<?>>();
+
+        try {
+            // wsPackage is "javax.websocket."
+            String wsPackage = ContainerProvider.class.getName();
+            wsPackage = wsPackage.substring(0, wsPackage.lastIndexOf('.') + 1);
+            for (Class<?> clazz : clazzes) {
+                int modifiers = clazz.getModifiers();
+                if (!Modifier.isPublic(modifiers) ||
+                        Modifier.isAbstract(modifiers)) {
+                    // Non-public or abstract - skip it.
+                    continue;
+                }
+                // Protect against scanning the WebSocket API JARs
+                if (clazz.getName().startsWith(wsPackage)) {
+                    continue;
+                }
+                if (ServerApplicationConfig.class.isAssignableFrom(clazz)) {
+                    serverApplicationConfigs.add(
+                            (ServerApplicationConfig) clazz.newInstance());
+                }
+                if (Endpoint.class.isAssignableFrom(clazz)) {
+                    @SuppressWarnings("unchecked")
+                    Class<? extends Endpoint> endpoint =
+                            (Class<? extends Endpoint>) clazz;
+                    scannedEndpointClazzes.add(endpoint);
+                }
+                if (clazz.isAnnotationPresent(ServerEndpoint.class)) {
+                    scannedPojoEndpoints.add(clazz);
+                }
+            }
+        } catch (InstantiationException e) {
+            throw new ServletException(e);
+        } catch (IllegalAccessException e) {
+            throw new ServletException(e);
+        }
+
+        // Filter the results
+        Set<ServerEndpointConfig> filteredEndpointConfigs = new HashSet<ServerEndpointConfig>();
+        Set<Class<?>> filteredPojoEndpoints = new HashSet<Class<?>>();
+
+        if (serverApplicationConfigs.isEmpty()) {
+            filteredPojoEndpoints.addAll(scannedPojoEndpoints);
+        } else {
+            for (ServerApplicationConfig config : serverApplicationConfigs) {
+                Set<ServerEndpointConfig> configFilteredEndpoints =
+                        config.getEndpointConfigs(scannedEndpointClazzes);
+                if (configFilteredEndpoints != null) {
+                    filteredEndpointConfigs.addAll(configFilteredEndpoints);
+                }
+                Set<Class<?>> configFilteredPojos =
+                        config.getAnnotatedEndpointClasses(
+                                scannedPojoEndpoints);
+                if (configFilteredPojos != null) {
+                    filteredPojoEndpoints.addAll(configFilteredPojos);
+                }
+            }
+        }
+
+        try {
+            // Deploy endpoints
+            for (ServerEndpointConfig config : filteredEndpointConfigs) {
+                sc.addEndpoint(config);
+            }
+            // Deploy POJOs
+            for (Class<?> clazz : filteredPojoEndpoints) {
+                sc.addEndpoint(clazz);
+            }
+        } catch (DeploymentException e) {
+            throw new ServletException(e);
+        }
+    }
+
+
+    static WsServerContainer init(ServletContext servletContext,
+            boolean initBySciMechanism) {
+
+        WsServerContainer sc = new WsServerContainer(servletContext);
+
+        servletContext.setAttribute(
+                Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE, sc);
+
+        servletContext.addListener(new WsSessionListener(sc));
+        // Can't register the ContextListener again if the ContextListener is
+        // calling this method
+        if (initBySciMechanism) {
+            servletContext.addListener(new WsContextListener());
+        }
+
+        return sc;
+    }
+
+
+    private static boolean isJava7OrLater() {
+        try {
+            Class.forName("java.nio.channels.AsynchronousSocketChannel");
+        } catch (ClassNotFoundException e) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/java/org/apache/tomcat/websocket/server/WsServerContainer.java b/java/org/apache/tomcat/websocket/server/WsServerContainer.java
new file mode 100644
index 0000000..81405ac
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/server/WsServerContainer.java
@@ -0,0 +1,452 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.server;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.FilterRegistration;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.DeploymentException;
+import javax.websocket.Encoder;
+import javax.websocket.Endpoint;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+import javax.websocket.server.ServerEndpointConfig.Configurator;
+
+import org.apache.tomcat.util.res.StringManager;
+import org.apache.tomcat.websocket.WsSession;
+import org.apache.tomcat.websocket.WsWebSocketContainer;
+import org.apache.tomcat.websocket.pojo.PojoEndpointServer;
+import org.apache.tomcat.websocket.pojo.PojoMethodMapping;
+
+/**
+ * Provides a per class loader (i.e. per web application) instance of a
+ * ServerContainer. Web application wide defaults may be configured by setting
+ * the following servlet context initialisation parameters to the desired
+ * values.
+ * <ul>
+ * <li>{@link Constants#BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM}</li>
+ * <li>{@link Constants#TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM}</li>
+ * </ul>
+ */
+public class WsServerContainer extends WsWebSocketContainer
+        implements ServerContainer {
+
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+    private static final CloseReason AUTHENTICATED_HTTP_SESSION_CLOSED =
+            new CloseReason(CloseCodes.VIOLATED_POLICY,
+                    "This connection was established under an authenticated " +
+                    "HTTP session that has ended.");
+
+    private final WsWriteTimeout wsWriteTimeout = new WsWriteTimeout();
+
+    private final ServletContext servletContext;
+    private final Map<String,ServerEndpointConfig> configExactMatchMap =
+            new ConcurrentHashMap<String, ServerEndpointConfig>();
+    private final ConcurrentHashMap<Integer,SortedSet<TemplatePathMatch>>
+            configTemplateMatchMap = new ConcurrentHashMap<Integer, SortedSet<TemplatePathMatch>>();
+    private volatile boolean enforceNoAddAfterHandshake =
+            org.apache.tomcat.websocket.Constants.STRICT_SPEC_COMPLIANCE;
+    private volatile boolean addAllowed = true;
+    private final ConcurrentHashMap<String,Set<WsSession>> authenticatedSessions =
+            new ConcurrentHashMap<String, Set<WsSession>>();
+
+    WsServerContainer(ServletContext servletContext) {
+
+        this.servletContext = servletContext;
+
+        // Configure servlet context wide defaults
+        String value = servletContext.getInitParameter(
+                Constants.BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM);
+        if (value != null) {
+            setDefaultMaxBinaryMessageBufferSize(Integer.parseInt(value));
+        }
+
+        value = servletContext.getInitParameter(
+                Constants.TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM);
+        if (value != null) {
+            setDefaultMaxTextMessageBufferSize(Integer.parseInt(value));
+        }
+
+        value = servletContext.getInitParameter(
+                Constants.ENFORCE_NO_ADD_AFTER_HANDSHAKE_CONTEXT_INIT_PARAM);
+        if (value != null) {
+            setEnforceNoAddAfterHandshake(Boolean.parseBoolean(value));
+        }
+
+        FilterRegistration.Dynamic fr = servletContext.addFilter(
+                WsFilter.class.getName(), new WsFilter());
+        fr.setAsyncSupported(true);
+
+        EnumSet<DispatcherType> types = EnumSet.of(DispatcherType.REQUEST,
+                DispatcherType.FORWARD);
+
+        fr.addMappingForUrlPatterns(types, true, "/*");
+    }
+
+
+    /**
+     * Published the provided endpoint implementation at the specified path with
+     * the specified configuration. {@link #WsServerContainer(ServletContext)}
+     * must be called before calling this method.
+     *
+     * @param sec   The configuration to use when creating endpoint instances
+     * @throws DeploymentException
+     */
+    @Override
+    public void addEndpoint(ServerEndpointConfig sec)
+            throws DeploymentException {
+
+        if (enforceNoAddAfterHandshake && !addAllowed) {
+            throw new DeploymentException(
+                    sm.getString("serverContainer.addNotAllowed"));
+        }
+
+        if (servletContext == null) {
+            throw new DeploymentException(
+                    sm.getString("serverContainer.servletContextMissing"));
+        }
+        String path = sec.getPath();
+
+        UriTemplate uriTemplate = new UriTemplate(path);
+        if (uriTemplate.hasParameters()) {
+            Integer key = Integer.valueOf(uriTemplate.getSegmentCount());
+            SortedSet<TemplatePathMatch> templateMatches =
+                    configTemplateMatchMap.get(key);
+            if (templateMatches == null) {
+                // Ensure that if concurrent threads execute this block they
+                // both end up using the same TreeSet instance
+                templateMatches = new TreeSet<TemplatePathMatch>(
+                        TemplatePathMatchComparator.getInstance());
+                configTemplateMatchMap.putIfAbsent(key, templateMatches);
+                templateMatches = configTemplateMatchMap.get(key);
+            }
+            if (!templateMatches.add(new TemplatePathMatch(sec, uriTemplate))) {
+                // Duplicate uriTemplate;
+                throw new DeploymentException(
+                        sm.getString("serverContainer.duplicatePaths", path));
+            }
+        } else {
+            // Exact match
+            ServerEndpointConfig old = configExactMatchMap.put(path, sec);
+            if (old != null) {
+                // Duplicate path mappings
+                throw new DeploymentException(
+                        sm.getString("serverContainer.duplicatePaths", path));
+            }
+        }
+    }
+
+
+    /**
+     * Provides the equivalent of {@link #addEndpoint(ServerEndpointConfig)}
+     * for publishing plain old java objects (POJOs) that have been annotated as
+     * WebSocket endpoints.
+     *
+     * @param pojo   The annotated POJO
+     */
+    @Override
+    public void addEndpoint(Class<?> pojo) throws DeploymentException {
+
+        ServerEndpoint annotation = pojo.getAnnotation(ServerEndpoint.class);
+        if (annotation == null) {
+            throw new DeploymentException(
+                    sm.getString("serverContainer.missingAnnotation",
+                            pojo.getName()));
+        }
+        String path = annotation.value();
+
+        // Validate encoders
+        validateEncoders(annotation.encoders());
+
+        // Method mapping
+        PojoMethodMapping methodMapping = new PojoMethodMapping(pojo,
+                annotation.decoders(), path);
+
+        // ServerEndpointConfig
+        ServerEndpointConfig sec;
+        Class<? extends Configurator> configuratorClazz =
+                annotation.configurator();
+        Configurator configurator = null;
+        if (!configuratorClazz.equals(Configurator.class)) {
+            try {
+                configurator = annotation.configurator().newInstance();
+            } catch (InstantiationException e) {
+                throw new DeploymentException(sm.getString(
+                        "serverContainer.configuratorFail",
+                        annotation.configurator().getName(),
+                        pojo.getClass().getName()), e);
+            } catch (IllegalAccessException e) {
+                throw new DeploymentException(sm.getString(
+                        "serverContainer.configuratorFail",
+                        annotation.configurator().getName(),
+                        pojo.getClass().getName()), e);
+            }
+        }
+        sec = ServerEndpointConfig.Builder.create(pojo, path).
+                decoders(Arrays.asList(annotation.decoders())).
+                encoders(Arrays.asList(annotation.encoders())).
+                subprotocols(Arrays.asList(annotation.subprotocols())).
+                configurator(configurator).
+                build();
+        sec.getUserProperties().put(
+                PojoEndpointServer.POJO_METHOD_MAPPING_KEY,
+                methodMapping);
+
+        addEndpoint(sec);
+    }
+
+
+    public void doUpgrade(HttpServletRequest request,
+            HttpServletResponse response, ServerEndpointConfig sec,
+            Map<String,String> pathParams)
+            throws ServletException, IOException {
+        UpgradeUtil.doUpgrade(this, request, response, sec, pathParams);
+    }
+
+
+    public WsMappingResult findMapping(String path) {
+
+        // Prevent registering additional endpoints once the first attempt has
+        // been made to use one
+        if (addAllowed) {
+            addAllowed = false;
+        }
+
+        // Check an exact match. Simple case as there are no templates.
+        ServerEndpointConfig sec = configExactMatchMap.get(path);
+        if (sec != null) {
+            return new WsMappingResult(sec,
+                    Collections.<String, String> emptyMap());
+        }
+
+        // No exact match. Need to look for template matches.
+        UriTemplate pathUriTemplate = null;
+        try {
+            pathUriTemplate = new UriTemplate(path);
+        } catch (DeploymentException e) {
+            // Path is not valid so can't be matched to a WebSocketEndpoint
+            return null;
+        }
+
+        // Number of segments has to match
+        Integer key = Integer.valueOf(pathUriTemplate.getSegmentCount());
+        SortedSet<TemplatePathMatch> templateMatches =
+                configTemplateMatchMap.get(key);
+
+        if (templateMatches == null) {
+            // No templates with an equal number of segments so there will be
+            // no matches
+            return null;
+        }
+
+        // List is in alphabetical order of normalised templates.
+        // Correct match is the first one that matches.
+        Map<String,String> pathParams = null;
+        for (TemplatePathMatch templateMatch : templateMatches) {
+            pathParams = templateMatch.getUriTemplate().match(pathUriTemplate);
+            if (pathParams != null) {
+                sec = templateMatch.getConfig();
+                break;
+            }
+        }
+
+        if (sec == null) {
+            // No match
+            return null;
+        }
+
+        if (!PojoEndpointServer.class.isAssignableFrom(sec.getEndpointClass())) {
+            // Need to make path params available to POJO
+            sec.getUserProperties().put(
+                    PojoEndpointServer.POJO_PATH_PARAM_KEY,
+                    pathParams);
+        }
+
+        return new WsMappingResult(sec, pathParams);
+    }
+
+
+
+    public boolean isEnforceNoAddAfterHandshake() {
+        return enforceNoAddAfterHandshake;
+    }
+
+
+    public void setEnforceNoAddAfterHandshake(
+            boolean enforceNoAddAfterHandshake) {
+        this.enforceNoAddAfterHandshake = enforceNoAddAfterHandshake;
+    }
+
+
+    protected WsWriteTimeout getTimeout() {
+        return wsWriteTimeout;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     *
+     * Overridden to make it visible to other classes in this package.
+     */
+    @Override
+    protected void registerSession(Endpoint endpoint, WsSession wsSession) {
+        super.registerSession(endpoint, wsSession);
+        if (wsSession.isOpen() &&
+                wsSession.getUserPrincipal() != null &&
+                wsSession.getHttpSessionId() != null) {
+            registerAuthenticatedSession(wsSession,
+                    wsSession.getHttpSessionId());
+        }
+    }
+
+
+    /**
+     * {@inheritDoc}
+     *
+     * Overridden to make it visible to other classes in this package.
+     */
+    @Override
+    protected void unregisterSession(Endpoint endpoint, WsSession wsSession) {
+        if (wsSession.getUserPrincipal() != null &&
+                wsSession.getHttpSessionId() != null) {
+            unregisterAuthenticatedSession(wsSession,
+                    wsSession.getHttpSessionId());
+        }
+        super.unregisterSession(endpoint, wsSession);
+    }
+
+
+    private void registerAuthenticatedSession(WsSession wsSession,
+            String httpSessionId) {
+        Set<WsSession> wsSessions = authenticatedSessions.get(httpSessionId);
+        if (wsSessions == null) {
+            wsSessions = Collections.newSetFromMap(
+                     new ConcurrentHashMap<WsSession,Boolean>());
+             authenticatedSessions.putIfAbsent(httpSessionId, wsSessions);
+             wsSessions = authenticatedSessions.get(httpSessionId);
+        }
+        wsSessions.add(wsSession);
+    }
+
+
+    private void unregisterAuthenticatedSession(WsSession wsSession,
+            String httpSessionId) {
+        Set<WsSession> wsSessions = authenticatedSessions.get(httpSessionId);
+        wsSessions.remove(wsSession);
+    }
+
+
+    public void closeAuthenticatedSession(String httpSessionId) {
+        Set<WsSession> wsSessions = authenticatedSessions.remove(httpSessionId);
+
+        if (wsSessions != null && !wsSessions.isEmpty()) {
+            for (WsSession wsSession : wsSessions) {
+                try {
+                    wsSession.close(AUTHENTICATED_HTTP_SESSION_CLOSED);
+                } catch (IOException e) {
+                    // Any IOExceptions during close will have been caught and the
+                    // onError method called.
+                }
+            }
+        }
+    }
+
+    private static void validateEncoders(Class<? extends Encoder>[] encoders)
+            throws DeploymentException {
+
+        for (Class<? extends Encoder> encoder : encoders) {
+            // Need to instantiate decoder to ensure it is valid and that
+            // deployment can be failed if it is not
+            @SuppressWarnings("unused")
+            Encoder instance;
+            try {
+                encoder.newInstance();
+            } catch(InstantiationException e) {
+                throw new DeploymentException(sm.getString(
+                        "serverContainer.encoderFail", encoder.getName()), e);
+            } catch (IllegalAccessException e) {
+                throw new DeploymentException(sm.getString(
+                        "serverContainer.encoderFail", encoder.getName()), e);
+            }
+        }
+    }
+
+    private static class TemplatePathMatch {
+        private final ServerEndpointConfig config;
+        private final UriTemplate uriTemplate;
+
+        public TemplatePathMatch(ServerEndpointConfig config,
+                UriTemplate uriTemplate) {
+            this.config = config;
+            this.uriTemplate = uriTemplate;
+        }
+
+
+        public ServerEndpointConfig getConfig() {
+            return config;
+        }
+
+
+        public UriTemplate getUriTemplate() {
+            return uriTemplate;
+        }
+    }
+
+
+    /**
+     * This Comparator implementation is thread-safe so only create a single
+     * instance.
+     */
+    private static class TemplatePathMatchComparator
+            implements Comparator<TemplatePathMatch> {
+
+        private static final TemplatePathMatchComparator INSTANCE =
+                new TemplatePathMatchComparator();
+
+        public static TemplatePathMatchComparator getInstance() {
+            return INSTANCE;
+        }
+
+        private TemplatePathMatchComparator() {
+            // Hide default constructor
+        }
+
+        @Override
+        public int compare(TemplatePathMatch tpm1, TemplatePathMatch tpm2) {
+            return tpm1.getUriTemplate().getNormalizedPath().compareTo(
+                    tpm2.getUriTemplate().getNormalizedPath());
+        }
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java b/java/org/apache/tomcat/websocket/server/WsSessionListener.java
similarity index 53%
copy from java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
copy to java/org/apache/tomcat/websocket/server/WsSessionListener.java
index 813bea8..37bdabe 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
+++ b/java/org/apache/tomcat/websocket/server/WsSessionListener.java
@@ -6,7 +6,7 @@
  * (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
+ *     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,
@@ -14,35 +14,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.coyote.http11.upgrade;
+package org.apache.tomcat.websocket.server;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
 
+public class WsSessionListener implements HttpSessionListener{
 
-/**
- * Allows data to be written to the upgraded connection.
- */
-public class UpgradeOutbound extends OutputStream {
-
-    @Override
-    public void flush() throws IOException {
-        processor.flush();
-    }
+    private final WsServerContainer wsServerContainer;
 
-    private final UpgradeProcessor<?> processor;
 
-    public UpgradeOutbound(UpgradeProcessor<?> processor) {
-        this.processor = processor;
+    public WsSessionListener(WsServerContainer wsServerContainer) {
+        this.wsServerContainer = wsServerContainer;
     }
 
+
     @Override
-    public void write(int b) throws IOException {
-        processor.write(b);
+    public void sessionCreated(HttpSessionEvent se) {
+        // NO-OP
     }
 
+
     @Override
-    public void write(byte[] b, int off, int len) throws IOException {
-        processor.write(b, off, len);
+    public void sessionDestroyed(HttpSessionEvent se) {
+        wsServerContainer.closeAuthenticatedSession(se.getSession().getId());
     }
 }
diff --git a/java/org/apache/tomcat/websocket/server/WsWriteTimeout.java b/java/org/apache/tomcat/websocket/server/WsWriteTimeout.java
new file mode 100644
index 0000000..9a26c6b
--- /dev/null
+++ b/java/org/apache/tomcat/websocket/server/WsWriteTimeout.java
@@ -0,0 +1,129 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.server;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.tomcat.websocket.BackgroundProcess;
+import org.apache.tomcat.websocket.BackgroundProcessManager;
+
+/**
+ * Provides timeouts for asynchronous web socket writes. On the server side we
+ * only have access to {@link javax.servlet.ServletOutputStream} and
+ * {@link javax.servlet.ServletInputStream} so there is no way to set a timeout
+ * for writes to the client.
+ */
+public class WsWriteTimeout implements BackgroundProcess {
+
+    private final Set<WsRemoteEndpointImplServer> endpoints =
+            new ConcurrentSkipListSet<WsRemoteEndpointImplServer>(new EndpointComparator());
+    private final AtomicInteger count = new AtomicInteger(0);
+    private int backgroundProcessCount = 0;
+    private volatile int processPeriod = 1;
+
+    @Override
+    public void backgroundProcess() {
+        // This method gets called once a second.
+        backgroundProcessCount ++;
+
+        if (backgroundProcessCount >= processPeriod) {
+            backgroundProcessCount = 0;
+
+            long now = System.currentTimeMillis();
+            Iterator<WsRemoteEndpointImplServer> iter = endpoints.iterator();
+            while (iter.hasNext()) {
+                WsRemoteEndpointImplServer endpoint = iter.next();
+                if (endpoint.getTimeoutExpiry() < now) {
+                    endpoint.onTimeout();
+                } else {
+                    // Endpoints are ordered by timeout expiry so if this point
+                    // is reached there is no need to check the remaining
+                    // endpoints
+                    break;
+                }
+            }
+        }
+    }
+
+
+    @Override
+    public void setProcessPeriod(int period) {
+        this.processPeriod = period;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     *
+     * The default value is 1 which means asynchronous write timeouts are
+     * processed every 1 second.
+     */
+    @Override
+    public int getProcessPeriod() {
+        return processPeriod;
+    }
+
+
+    public void register(WsRemoteEndpointImplServer endpoint) {
+        boolean result = endpoints.add(endpoint);
+        if (result) {
+            int newCount = count.incrementAndGet();
+            if (newCount == 1) {
+                BackgroundProcessManager.getInstance().register(this);
+            }
+        }
+    }
+
+
+    public void unregister(WsRemoteEndpointImplServer endpoint) {
+        boolean result = endpoints.remove(endpoint);
+        if (result) {
+            int newCount = count.decrementAndGet();
+            if (newCount == 0) {
+                BackgroundProcessManager.getInstance().unregister(this);
+            }
+        }
+    }
+
+
+    /**
+     * Note: this comparator imposes orderings that are inconsistent with equals
+     */
+    private static class EndpointComparator implements
+            Comparator<WsRemoteEndpointImplServer> {
+
+        @Override
+        public int compare(WsRemoteEndpointImplServer o1,
+                WsRemoteEndpointImplServer o2) {
+
+            long t1 = o1.getTimeoutExpiry();
+            long t2 = o2.getTimeoutExpiry();
+
+            if (t1 < t2) {
+                return -1;
+            } else if (t1 == t2) {
+                return 0;
+            } else {
+                return 1;
+            }
+        }
+    }
+}
diff --git a/java/javax/annotation/PreDestroy.java b/java/org/apache/tomcat/websocket/server/package-info.java
similarity index 71%
copy from java/javax/annotation/PreDestroy.java
copy to java/org/apache/tomcat/websocket/server/package-info.java
index d5be75a..75ca74c 100644
--- a/java/javax/annotation/PreDestroy.java
+++ b/java/org/apache/tomcat/websocket/server/package-info.java
@@ -5,27 +5,17 @@
  * The ASF licenses this file to You 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 javax.annotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
- at Target({ElementType.METHOD})
- at Retention(RetentionPolicy.RUNTIME)
-
-public @interface PreDestroy {
-    // No attributes
-}
+/**
+ * Server-side specific implementation classes. These are in a separate package
+ * to make packaging a pure client JAR simpler.
+ */
+package org.apache.tomcat.websocket.server;
\ No newline at end of file
diff --git a/modules/jdbc-pool/doc/jdbc-pool.xml b/modules/jdbc-pool/doc/jdbc-pool.xml
index b25a801..26dd512 100644
--- a/modules/jdbc-pool/doc/jdbc-pool.xml
+++ b/modules/jdbc-pool/doc/jdbc-pool.xml
@@ -277,6 +277,15 @@
          Example values are <code>SELECT 1</code>(mysql), <code>select 1 from dual</code>(oracle), <code>SELECT 1</code>(MS Sql Server)
       </p>
     </attribute>
+    
+    <attribute name="validationQueryTimeout" required="false">
+      <p>(int) The timeout in seconds before a connection validation queries fail.  This works by calling 
+         <code>java.sql.Statement.setQueryTimeout(seconds)</code> on the statement that executes the <code>validationQuery</code>.
+         The pool itself doesn't timeout the query, it is still up to the JDBC driver to enforce query timeouts. 
+         A value less than or equal to zero will disable this feature. 
+         The default value is <code>-1</code>.
+      </p>
+    </attribute>
 
     <attribute name="validatorClassName" required="false">
       <p>(String) The name of a class which implements the
@@ -495,6 +504,13 @@
       <p>(boolean) Set this to true to propagate the interrupt state for a thread that has been interrupted (not clearing the interrupt state). Default value is <code>false</code> for backwards compatibility.
       </p>
     </attribute>
+    <attribute name="ignoreExceptionOnPreLoad" required="false">
+      <p>(boolean) Flag whether ignore error of connection creation while initializing the pool.
+         Set to true if you want to ignore error of connection creation while initializing the pool.
+         Set to false if you want to fail the initialization of the pool by throwing exception.
+         The default value is <code>false</code>.
+      </p>
+    </attribute>
 
   </attributes>
   </subsection>
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java
index a10ab23..d7f9f85 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java
@@ -392,8 +392,6 @@ public class ConnectionPool {
             } catch (InterruptedException ex) {
                 if (getPoolProperties().getPropagateInterruptState()) {
                     Thread.currentThread().interrupt();
-                } else {
-                    Thread.interrupted();
                 }
             }
             if (pool.size()==0 && force && pool!=busy) pool = busy;
@@ -489,9 +487,12 @@ public class ConnectionPool {
             } //for
 
         } catch (SQLException x) {
-            if (jmxPool!=null) jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.NOTIFY_INIT, getStackTrace(x));
-            close(true);
-            throw x;
+            log.error("Unable to create initial connections of pool.", x);
+            if (!poolProperties.isIgnoreExceptionOnPreLoad()) {
+                if (jmxPool!=null) jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.NOTIFY_INIT, getStackTrace(x));
+                close(true);
+                throw x;
+            }
         } finally {
             //return the members as idle to the pool
             for (int i = 0; i < initialPool.length; i++) {
@@ -650,8 +651,6 @@ public class ConnectionPool {
             } catch (InterruptedException ex) {
                 if (getPoolProperties().getPropagateInterruptState()) {
                     Thread.currentThread().interrupt();
-                } else {
-                    Thread.interrupted();
                 }
                 SQLException sx = new SQLException("Pool wait interrupted.");
                 sx.initCause(ex);
@@ -713,7 +712,7 @@ public class ConnectionPool {
             } else {
                 //validation failed, make sure we disconnect
                 //and clean up
-                error =true;
+                throw new SQLException("Validation Query Failed, enable logValidationErrors for more details.");
             } //end if
         } catch (Exception e) {
             error = true;
@@ -733,7 +732,6 @@ public class ConnectionPool {
             }
             con.unlock();
         }//catch
-        return null;
     }
 
     /**
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java
index 0ab9eba..4db76e2 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java
@@ -80,6 +80,7 @@ public class DataSourceFactory implements ObjectFactory {
     protected static final String PROP_TESTWHILEIDLE = "testWhileIdle";
     protected static final String PROP_TESTONCONNECT = "testOnConnect";
     protected static final String PROP_VALIDATIONQUERY = "validationQuery";
+    protected static final String PROP_VALIDATIONQUERY_TIMEOUT = "validationQueryTimeout";
     protected static final String PROP_VALIDATOR_CLASS_NAME = "validatorClassName";
 
     protected static final String PROP_NUMTESTSPEREVICTIONRUN = "numTestsPerEvictionRun";
@@ -122,6 +123,8 @@ public class DataSourceFactory implements ObjectFactory {
 
     protected static final String PROP_PROPAGATEINTERRUPTSTATE = "propagateInterruptState";
 
+    protected static final String PROP_IGNOREEXCEPTIONONPRELOAD = "ignoreExceptionOnPreLoad";
+
     public static final int UNKNOWN_TRANSACTIONISOLATION = -1;
 
     public static final String OBJECT_NAME = "object_name";
@@ -149,6 +152,7 @@ public class DataSourceFactory implements ObjectFactory {
         PROP_URL,
         PROP_USERNAME,
         PROP_VALIDATIONQUERY,
+        PROP_VALIDATIONQUERY_TIMEOUT,
         PROP_VALIDATOR_CLASS_NAME,
         PROP_VALIDATIONINTERVAL,
         PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED,
@@ -175,7 +179,8 @@ public class DataSourceFactory implements ObjectFactory {
         PROP_ROLLBACKONRETURN,
         PROP_USEDISPOSABLECONNECTIONFACADE,
         PROP_LOGVALIDATIONERRORS,
-        PROP_PROPAGATEINTERRUPTSTATE
+        PROP_PROPAGATEINTERRUPTSTATE,
+        PROP_IGNOREEXCEPTIONONPRELOAD
     };
 
     // -------------------------------------------------- ObjectFactory Methods
@@ -367,6 +372,11 @@ public class DataSourceFactory implements ObjectFactory {
             poolProperties.setValidationQuery(value);
         }
 
+        value = properties.getProperty(PROP_VALIDATIONQUERY_TIMEOUT);
+        if (value != null) {
+            poolProperties.setValidationQueryTimeout(Integer.parseInt(value));
+        }
+
         value = properties.getProperty(PROP_VALIDATOR_CLASS_NAME);
         if (value != null) {
             poolProperties.setValidatorClassName(value);
@@ -514,6 +524,11 @@ public class DataSourceFactory implements ObjectFactory {
             poolProperties.setPropagateInterruptState(Boolean.parseBoolean(value));
         }
 
+        value = properties.getProperty(PROP_IGNOREEXCEPTIONONPRELOAD);
+        if (value != null) {
+            poolProperties.setIgnoreExceptionOnPreLoad(Boolean.parseBoolean(value));
+        }
+
         return poolProperties;
     }
 
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java
index a90be96..89dcc28 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java
@@ -448,6 +448,15 @@ public class DataSourceProxy implements PoolConfiguration {
      */
 
     @Override
+    public void setValidationQueryTimeout(int validationQueryTimeout) {
+        this.poolProperties.setValidationQueryTimeout(validationQueryTimeout);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+
+    @Override
     public void setJdbcInterceptors(String interceptors) {
         this.getPoolProperties().setJdbcInterceptors(interceptors);
     }
@@ -920,6 +929,15 @@ public class DataSourceProxy implements PoolConfiguration {
      */
 
     @Override
+    public int getValidationQueryTimeout() {
+        return getPoolProperties().getValidationQueryTimeout();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+
+    @Override
     public String getValidatorClassName() {
         return getPoolProperties().getValidatorClassName();
     }
@@ -1288,6 +1306,22 @@ public class DataSourceProxy implements PoolConfiguration {
         getPoolProperties().setPropagateInterruptState(propagateInterruptState);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isIgnoreExceptionOnPreLoad() {
+        return getPoolProperties().isIgnoreExceptionOnPreLoad();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setIgnoreExceptionOnPreLoad(boolean ignoreExceptionOnPreLoad) {
+        getPoolProperties().setIgnoreExceptionOnPreLoad(ignoreExceptionOnPreLoad);
+    }
+
     public void purge()  {
         try {
             createPool().purge();
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DisposableConnectionFacade.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DisposableConnectionFacade.java
index 5d88b93..a51dda1 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DisposableConnectionFacade.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DisposableConnectionFacade.java
@@ -61,9 +61,9 @@ public class DisposableConnectionFacade extends JdbcInterceptor {
     public Object invoke(Object proxy, Method method, Object[] args)
             throws Throwable {
         if (compare(EQUALS_VAL, method)) {
-            return this.equals(Proxy.getInvocationHandler(args[0]));
+            return Boolean.valueOf(this.equals(Proxy.getInvocationHandler(args[0])));
         } else if (compare(HASHCODE_VAL, method)) {
-            return this.hashCode();
+            return Integer.valueOf(this.hashCode());
         } else if (getNext()==null) {
             if (compare(ISCLOSED_VAL, method)) {
                 return Boolean.TRUE;
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java
index b9a1cd5..f38a43b 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java
@@ -524,6 +524,19 @@ public interface PoolConfiguration {
     public void setValidationQuery(String validationQuery);
 
     /**
+     * The timeout in seconds before a connection validation queries fail.
+     * A value less than or equal to zero will disable this feature.  Defaults to -1.
+     * @return the timeout value in seconds
+     */
+    public int getValidationQueryTimeout();
+
+    /**
+     * The timeout in seconds before a connection validation queries fail.
+     * A value less than or equal to zero will disable this feature.  Defaults to -1.
+     */
+    public void setValidationQueryTimeout(int validationQueryTimeout);
+
+    /**
      * Return the name of the optional validator class - may be null.
      *
      * @return the name of the optional validator class - may be null
@@ -864,4 +877,16 @@ public interface PoolConfiguration {
      */
     public void setPropagateInterruptState(boolean propagateInterruptState);
 
+    /**
+     * Set to true if you want to ignore error of connection creation while initializing the pool.
+     * Set to false if you want to fail the initialization of the pool by throwing exception.
+     * @param ignoreExceptionOnPreLoad set to true if you want to ignore error of connection creation while initializing the pool.
+     */
+    public void setIgnoreExceptionOnPreLoad(boolean ignoreExceptionOnPreLoad);
+
+    /**
+     * @see PoolConfiguration#setIgnoreExceptionOnPreLoad(boolean)
+     */
+    public boolean isIgnoreExceptionOnPreLoad();
+
 }
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java
index 18ebbda..e5ccda9 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java
@@ -57,6 +57,7 @@ public class PoolProperties implements PoolConfiguration, Cloneable, Serializabl
     private volatile int minIdle = initialSize;
     private volatile int maxWait = 30000;
     private volatile String validationQuery;
+    private volatile int validationQueryTimeout = -1;
     private volatile String validatorClassName;
     private volatile Validator validator;
     private volatile boolean testOnBorrow = false;
@@ -92,6 +93,7 @@ public class PoolProperties implements PoolConfiguration, Cloneable, Serializabl
     private volatile boolean useDisposableConnectionFacade = true;
     private volatile boolean logValidationErrors = false;
     private volatile boolean propagateInterruptState = false;
+    private volatile boolean ignoreExceptionOnPreLoad = false;
 
 
     /**
@@ -382,6 +384,22 @@ public class PoolProperties implements PoolConfiguration, Cloneable, Serializabl
     /**
      * {@inheritDoc}
      */
+    @Override
+    public int getValidationQueryTimeout() {
+        return validationQueryTimeout;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setValidationQueryTimeout(int validationQueryTimeout) {
+        this.validationQueryTimeout = validationQueryTimeout;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
 
     @Override
     public String getValidatorClassName() {
@@ -754,6 +772,7 @@ public class PoolProperties implements PoolConfiguration, Cloneable, Serializabl
         }
 
         try {
+            @SuppressWarnings("unchecked")
             Class<Validator> validatorClass = (Class<Validator>)Class.forName(className);
             validator = validatorClass.newInstance();
         } catch (ClassNotFoundException e) {
@@ -936,6 +955,7 @@ public class PoolProperties implements PoolConfiguration, Cloneable, Serializabl
             return properties;
         }
 
+        @SuppressWarnings("unchecked")
         public Class<? extends JdbcInterceptor> getInterceptorClass() throws ClassNotFoundException {
             if (clazz==null) {
                 if (getClassName().indexOf(".")<0) {
@@ -1254,6 +1274,22 @@ public class PoolProperties implements PoolConfiguration, Cloneable, Serializabl
         this.propagateInterruptState = propagateInterruptState;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isIgnoreExceptionOnPreLoad() {
+        return ignoreExceptionOnPreLoad;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setIgnoreExceptionOnPreLoad(boolean ignoreExceptionOnPreLoad) {
+        this.ignoreExceptionOnPreLoad = ignoreExceptionOnPreLoad;
+    }
+
     @Override
     protected Object clone() throws CloneNotSupportedException {
         // TODO Auto-generated method stub
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PooledConnection.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PooledConnection.java
index f49fca8..81b7d11 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PooledConnection.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PooledConnection.java
@@ -452,6 +452,12 @@ public class PooledConnection {
         Statement stmt = null;
         try {
             stmt = connection.createStatement();
+
+            int validationQueryTimeout = poolProperties.getValidationQueryTimeout();
+            if (validationQueryTimeout > 0) {
+                stmt.setQueryTimeout(validationQueryTimeout);
+            }
+
             stmt.execute(query);
             stmt.close();
             this.lastValidated = now;
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java
index b40f55e..747aa84 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java
@@ -250,6 +250,7 @@ public class SlowQueryReport extends AbstractQueryReport  {
             "The date and time of the last invocation"
         };
 
+        @SuppressWarnings("rawtypes")
         static final OpenType[] FIELD_TYPES = new OpenType[] {
             SimpleType.STRING,
             SimpleType.INTEGER,
@@ -284,6 +285,7 @@ public class SlowQueryReport extends AbstractQueryReport  {
             return FIELD_DESCRIPTIONS;
         }
 
+        @SuppressWarnings("rawtypes")
         public static OpenType[] getFieldTypes() {
             return FIELD_TYPES;
         }
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementCache.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementCache.java
index 9587122..c61aeab 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementCache.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementCache.java
@@ -127,6 +127,7 @@ public class StatementCache extends StatementDecoratorInterceptor {
 
     @Override
     public void disconnected(ConnectionPool parent, PooledConnection con, boolean finalizing) {
+        @SuppressWarnings("unchecked")
         ConcurrentHashMap<String,CachedStatement> statements =
             (ConcurrentHashMap<String,CachedStatement>)con.getAttributes().get(STATEMENT_CACHE_ATTR);
 
@@ -181,12 +182,14 @@ public class StatementCache extends StatementDecoratorInterceptor {
     }
 
     public CachedStatement isCached(String sql) {
+        @SuppressWarnings("unchecked")
         ConcurrentHashMap<String,CachedStatement> cache =
             (ConcurrentHashMap<String,CachedStatement>)pcon.getAttributes().get(STATEMENT_CACHE_ATTR);
         return cache.get(sql);
     }
 
     public boolean cacheStatement(CachedStatement proxy) {
+        @SuppressWarnings("unchecked")
         ConcurrentHashMap<String,CachedStatement> cache =
             (ConcurrentHashMap<String,CachedStatement>)pcon.getAttributes().get(STATEMENT_CACHE_ATTR);
         if (proxy.getSql()==null) {
@@ -206,6 +209,7 @@ public class StatementCache extends StatementDecoratorInterceptor {
     }
 
     public boolean removeStatement(CachedStatement proxy) {
+        @SuppressWarnings("unchecked")
         ConcurrentHashMap<String,CachedStatement> cache =
             (ConcurrentHashMap<String,CachedStatement>)pcon.getAttributes().get(STATEMENT_CACHE_ATTR);
         if (cache.remove(proxy.getSql()) != null) {
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java
index 17f1c65..66988cb 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java
@@ -310,6 +310,11 @@ public class ConnectionPool extends NotificationBroadcasterSupport implements Co
         return getPoolProperties().getValidationQuery();
     }
 
+    @Override
+    public int getValidationQueryTimeout() {
+        return getPoolProperties().getValidationQueryTimeout();
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -663,6 +668,11 @@ public class ConnectionPool extends NotificationBroadcasterSupport implements Co
         getPoolProperties().setValidationQuery(validationQuery);
     }
 
+    @Override
+    public void setValidationQueryTimeout(int validationQueryTimeout) {
+        getPoolProperties().setValidationQueryTimeout(validationQueryTimeout);
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -832,6 +842,22 @@ public class ConnectionPool extends NotificationBroadcasterSupport implements Co
      * {@inheritDoc}
      */
     @Override
+    public boolean isIgnoreExceptionOnPreLoad() {
+        return getPoolProperties().isIgnoreExceptionOnPreLoad();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setIgnoreExceptionOnPreLoad(boolean ignoreExceptionOnPreLoad) {
+        getPoolProperties().setIgnoreExceptionOnPreLoad(ignoreExceptionOnPreLoad);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public void purge() {
         pool.purge();
 
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml
index 8f0db5c..b8baa3a 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml
@@ -126,6 +126,11 @@
                   type="java.lang.String"
              writeable="false"/>
 
+    <attribute    name="validationQueryTimeout"
+           description="The timeout in seconds before a connection validation queries fail"
+                  type="java.lang.Integer"
+             writeable="false" />
+
     <attribute    name="testOnBorrow"
            description="True if validation happens when a connection is requested"
                   type="java.lang.Boolean"
@@ -224,6 +229,107 @@
                   type="java.lang.String"
              writeable="false"/>
 
+<attribute    name="jmxEnabled"
+           description="Register the pool with JMX or not"
+                  type="java.lang.Boolean"
+                    is="true"
+             writeable="false"/>
+
+    <attribute    name="fairQueue"
+           description="a fair queue is being used by the connection pool"
+                  type="java.lang.Boolean"
+                    is="true"
+             writeable="false"/>
+
+    <attribute    name="abandonWhenPercentageFull"
+           description="Connections that have been abandoned isn't closed unless connections in use are above this percentage"
+                  type="java.lang.Integer"
+             writeable="false"/>
+
+    <attribute    name="maxAge"
+           description="Time in milliseconds to keep this connection alive even when used"
+                  type="java.lang.Long"
+             writeable="false"/>
+
+    <attribute    name="useEquals"
+           description="Set to true if you wish the ProxyConnection class to use String.equals and set to false when you wish to use == when comparing method names"
+                  type="java.lang.Boolean"
+                    is="true"
+             writeable="false"/>
+
+    <attribute    name="useLock"
+           description="If true, use a lock when operations are performed on the connection object"
+                  type="java.lang.Boolean"
+                    is="false"
+             writeable="false"/>
+
+    <attribute    name="suspectTimeout"
+           description="Timeout in seconds for connection that suspected to have been abandoned"
+                  type="java.lang.Integer"
+             writeable="false"/>
+
+    <attribute    name="rollbackOnReturn"
+           description="If autoCommit==false then the pool can terminate the transaction by calling rollback on the connection as it is returned to the pool"
+                  type="java.lang.Boolean"
+                    is="false"
+             writeable="false"/>
+
+    <attribute    name="commitOnReturn"
+           description="If autoCommit==false then the pool can complete the transaction by calling commit on the connection as it is returned to the pool"
+                  type="java.lang.Boolean"
+                    is="false"
+             writeable="false"/>
+
+    <attribute    name="alternateUsernameAllowed"
+           description="If true, getConnection(username,password) is allowed"
+                  type="java.lang.Boolean"
+                    is="true"
+             writeable="false"/>
+
+    <attribute    name="dataSource"
+           description="Data source that is injected into the pool"
+                  type="javax.sql.DataSource"
+             writeable="false"/>
+
+    <attribute    name="dataSourceJNDI"
+           description="The JNDI name for a data source to be looked up"
+                  type="java.lang.String"
+             writeable="false"/>
+
+    <attribute    name="useDisposableConnectionFacade"
+           description="If true, connection pool is configured to use a connection facade to prevent re-use of connection after close() has been invoked"
+                  type="java.lang.Boolean"
+                    is="false"
+             writeable="false"/>
+
+    <attribute    name="logValidationErrors"
+           description="Log errors during the validation phase to the log file"
+                  type="java.lang.Boolean"
+                    is="false"
+             writeable="false"/>
+
+    <attribute    name="validatorClassName"
+           description="The name of validator class which implements org.apache.tomcat.jdbc.pool.Validator interface"
+                  type="java.lang.String"
+             writeable="false"/>
+
+    <attribute    name="waitCount"
+           description="The number of threads waiting for a connection"
+                  type="java.lang.Integer"
+             writeable="false"/>
+
+    <attribute    name="propagateInterruptState"
+           description="If true, propagate the interrupt state for a thread that has been interrupted"
+                  type="java.lang.Boolean"
+                    is="false"
+             writeable="false"/>
+
+    <attribute    name="ignoreExceptionOnPreLoad"
+           description="If true, ignore error of connection creation while initializing the pool"
+                  type="java.lang.Boolean"
+                    is="true"
+             writeable="false"/>
+
     <operation    name="checkIdle"
                   description="forces a check of idle connections"
                   impact="ACTION"
@@ -238,6 +344,17 @@
                   description="forces a validation of abandoned connections"
                   impact="ACTION"
                   returnType="void" />
+
+    <operation    name="purge"
+                  description="Purges all connections in the pool"
+                  impact="ACTION"
+                  returnType="void" />
+
+    <operation    name="purgeOnReturn"
+                  description="Purges connections when they are returned from the pool"
+                  impact="ACTION"
+                  returnType="void" />
+
   </mbean>
 
 </mbeans-descriptors>
diff --git a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/test/TestValidationQueryTimeout.java b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/test/TestValidationQueryTimeout.java
new file mode 100644
index 0000000..ffdcd81
--- /dev/null
+++ b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/test/TestValidationQueryTimeout.java
@@ -0,0 +1,266 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.jdbc.test;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.DriverPropertyInfo;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Statement;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.tomcat.jdbc.pool.interceptor.QueryTimeoutInterceptor;
+
+public class TestValidationQueryTimeout extends DefaultTestCase {
+
+    private static int TIMEOUT = 10;
+    private static boolean isTimeoutSet;
+    private static final String longQuery = "select * from test as A, test as B, test as C, test as D, test as E";
+
+    @Before
+    public void setUp() throws SQLException {
+        DriverManager.registerDriver(new MockDriver());
+
+        // use our mock driver
+        this.datasource.setDriverClassName(MockDriver.class.getName());
+        this.datasource.setUrl(MockDriver.url);
+
+        // Required to trigger validation query's execution
+        this.datasource.setInitialSize(1);
+        this.datasource.setTestOnBorrow(true);
+        this.datasource.setValidationInterval(-1);
+        this.datasource.setValidationQuery("SELECT 1");
+        this.datasource.setValidationQueryTimeout(TIMEOUT);
+
+        TIMEOUT = 10;
+        isTimeoutSet = false;
+    }
+
+    @Override
+    @After
+    public void tearDown() throws SQLException {
+        DriverManager.deregisterDriver(new MockDriver());
+    }
+
+    @Test
+    public void testValidationQueryTimeoutEnabled() throws Exception {
+        // because testOnBorrow is true, this triggers the validation query
+        this.datasource.getConnection();
+        Assert.assertTrue(isTimeoutSet);
+    }
+
+    @Test
+    public void testValidationQueryTimeoutDisabled() throws Exception {
+        this.datasource.setValidationQueryTimeout(-1);
+
+        // because testOnBorrow is true, this triggers the validation query
+        this.datasource.getConnection();
+        Assert.assertFalse(isTimeoutSet);
+    }
+
+    @Test
+    public void testValidationQueryTimeoutWithQueryTimeoutInterceptor() throws Exception {
+        int interceptorTimeout = 30;
+        this.datasource.setJdbcInterceptors(
+                            QueryTimeoutInterceptor.class.getName()+
+                            "(queryTimeout="+ interceptorTimeout +")");
+
+        // because testOnBorrow is true, this triggers the validation query
+        Connection con = this.datasource.getConnection();
+        Assert.assertTrue(isTimeoutSet);
+
+        // increase the expected timeout to 30, which is what we set for the interceptor
+        TIMEOUT = 30;
+
+        // now create a statement, make sure the query timeout is set by the interceptor
+        Statement st = con.createStatement();
+        Assert.assertEquals(interceptorTimeout, st.getQueryTimeout());
+        st.close();
+        st = con.prepareStatement("");
+        Assert.assertEquals(interceptorTimeout, st.getQueryTimeout());
+        st.close();
+        st = con.prepareCall("");
+        Assert.assertEquals(interceptorTimeout, st.getQueryTimeout());
+        st.close();
+        con.close();
+
+        // pull another connection and check it
+        TIMEOUT = 10;
+        isTimeoutSet = false;
+        this.datasource.getConnection();
+        Assert.assertTrue(isTimeoutSet);
+    }
+
+    // this test depends on the execution time of the validation query
+    //   specifically, it needs to run for longer than 1 second to pass
+    //   if this fails
+    @Test(expected=SQLException.class)
+    public void testValidationQueryTimeoutOnConnection() throws Exception {
+        // use our mock driver
+        this.datasource.setDriverClassName("org.h2.Driver");
+        this.datasource.setUrl("jdbc:h2:~/.h2/test;QUERY_TIMEOUT=0;DB_CLOSE_ON_EXIT=FALSE");
+
+        // Required to trigger validation query's execution
+        this.datasource.setTestOnConnect(true);
+        this.datasource.setValidationInterval(-1);
+        this.datasource.setValidationQuery(longQuery);
+        this.datasource.setValidationQueryTimeout(1);
+
+        this.datasource.getConnection();
+    }
+
+    @Test(expected=SQLException.class)
+    public void testValidationInvalidOnConnection() throws Exception {
+        // use our mock driver
+        this.datasource.setDriverClassName("org.h2.Driver");
+        this.datasource.setUrl("jdbc:h2:~/.h2/test;QUERY_TIMEOUT=0;DB_CLOSE_ON_EXIT=FALSE");
+
+        // Required to trigger validation query's execution
+        this.datasource.setTestOnBorrow(true);
+        this.datasource.setInitialSize(1);
+        this.datasource.setTestOnConnect(true);
+        this.datasource.setValidationInterval(-1);
+        this.datasource.setValidationQuery("SELECT");
+        this.datasource.setValidationQueryTimeout(1);
+
+        this.datasource.getConnection();
+    }
+
+    @Test
+    public void testLongValidationQueryTime() throws Exception {
+        // use our mock driver
+        this.datasource.setDriverClassName("org.h2.Driver");
+        this.datasource.setUrl("jdbc:h2:~/.h2/test;QUERY_TIMEOUT=0;DB_CLOSE_ON_EXIT=FALSE");
+        Connection con = this.datasource.getConnection();
+        Statement stmt = null;
+        long start = 0, end = 0;
+        try {
+            stmt = con.createStatement();
+            // set the query timeout to 2 sec
+            //  this keeps this test from slowing things down too much
+            stmt.setQueryTimeout(2);
+            // assert that our long query takes longer than one second to run
+            //  this is a requirement for other tests to run properly
+            start = System.currentTimeMillis();
+            stmt.execute(longQuery);
+        } catch (SQLException ex) {}
+        finally {
+            end = System.currentTimeMillis();
+
+            if (stmt != null) { stmt.close(); }
+            if (con != null) { con.close(); }
+
+            Assert.assertTrue(start != 0 && end != 0);
+            Assert.assertTrue((end - start) > 1000);
+        }
+    }
+
+    @Test
+    public void testValidationQueryTimeoutOnBorrow() throws Exception {
+        // use our mock driver
+        this.datasource.setDriverClassName("org.h2.Driver");
+        this.datasource.setUrl("jdbc:h2:~/.h2/test;QUERY_TIMEOUT=0;DB_CLOSE_ON_EXIT=FALSE");
+
+        // Required to trigger validation query's execution
+        this.datasource.setTestOnBorrow(true);
+        this.datasource.setValidationInterval(-1);
+        this.datasource.setValidationQuery(longQuery);
+        this.datasource.setValidationQueryTimeout(1);
+
+        // assert that even though the validation query times out, we still get a connection
+        Connection con = this.datasource.getConnection();
+        Assert.assertNotNull(con);
+        Statement st = con.createStatement();
+        ResultSet rs = st.executeQuery("SELECT 1");
+        rs.close();
+        st.close();
+        con.close();
+    }
+
+    /**
+     * Mock Driver, Connection and Statement implementations use to verify setQueryTimeout was called.
+     */
+    public static class MockDriver implements java.sql.Driver {
+        public static final String url = "jdbc:tomcat:mock";
+
+        public MockDriver() {
+        }
+
+        @Override
+        public boolean acceptsURL(String url) throws SQLException {
+            return url!=null && url.equals(MockDriver.url);
+        }
+
+        @Override
+        public Connection connect(String url, Properties info) throws SQLException {
+            return new MockConnection(info);
+        }
+
+        @Override
+        public int getMajorVersion() {
+            return 0;
+        }
+
+        @Override
+        public int getMinorVersion() {
+            return 0;
+        }
+
+        @Override
+        public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
+            return null;
+        }
+
+        @Override
+        public boolean jdbcCompliant() {
+            return false;
+        }
+
+        @Override
+        public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+            return null;
+        }
+    }
+
+    public static class MockConnection extends org.apache.tomcat.jdbc.test.driver.Connection {
+        public MockConnection(Properties info) {
+            super(info);
+        }
+
+        @Override
+        public Statement createStatement() throws SQLException {
+            return new MockStatement();
+        }
+    }
+
+    public static class MockStatement extends org.apache.tomcat.jdbc.test.driver.Statement {
+        @Override
+        public void setQueryTimeout(int seconds) throws SQLException {
+            super.setQueryTimeout(seconds);
+            Assert.assertEquals(TIMEOUT, seconds);
+            isTimeoutSet = true;
+        }
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/LocalStrings.properties b/res/META-INF/default/.gitignore
similarity index 65%
copy from java/org/apache/coyote/http11/upgrade/LocalStrings.properties
copy to res/META-INF/default/.gitignore
index 410cc5c..0073da0 100644
--- a/java/org/apache/coyote/http11/upgrade/LocalStrings.properties
+++ b/res/META-INF/default/.gitignore
@@ -1,3 +1,4 @@
+# -----------------------------------------------------------------------------
 # Licensed to the Apache Software Foundation (ASF) under one or more
 # contributor license agreements.  See the NOTICE file distributed with
 # this work for additional information regarding copyright ownership.
@@ -12,9 +13,12 @@
 # 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.
-
-apr.read.error=Error [{0}] reading data from the APR/native socket.
-apr.write.error=Error [{0}] writing data to the APR/native socket.
-
-nio.eof.error=Unexpected EOF read on the socket
-
+# -----------------------------------------------------------------------------
+# Git ignores empty directories and the unit tests require this directory to
+# be present for the welcome file tests to pass. The presence of this file
+# doesn't break the unit tests.
+#
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
\ No newline at end of file
diff --git a/res/META-INF/tomcat7-websocket.jar/services/javax.servlet.ServletContainerInitializer b/res/META-INF/tomcat7-websocket.jar/services/javax.servlet.ServletContainerInitializer
new file mode 100644
index 0000000..4d26d70
--- /dev/null
+++ b/res/META-INF/tomcat7-websocket.jar/services/javax.servlet.ServletContainerInitializer
@@ -0,0 +1 @@
+org.apache.tomcat.websocket.server.WsSci
diff --git a/res/META-INF/tomcat7-websocket.jar/services/javax.websocket.ContainerProvider b/res/META-INF/tomcat7-websocket.jar/services/javax.websocket.ContainerProvider
new file mode 100644
index 0000000..91c8204
--- /dev/null
+++ b/res/META-INF/tomcat7-websocket.jar/services/javax.websocket.ContainerProvider
@@ -0,0 +1 @@
+org.apache.tomcat.websocket.WsContainerProvider
\ No newline at end of file
diff --git a/res/META-INF/tomcat7-websocket.jar/services/javax.websocket.server.ServerEndpointConfig.Configurator b/res/META-INF/tomcat7-websocket.jar/services/javax.websocket.server.ServerEndpointConfig.Configurator
new file mode 100644
index 0000000..38046d2
--- /dev/null
+++ b/res/META-INF/tomcat7-websocket.jar/services/javax.websocket.server.ServerEndpointConfig.Configurator
@@ -0,0 +1 @@
+org.apache.tomcat.websocket.server.DefaultServerEndpointConfigurator
\ No newline at end of file
diff --git a/webapps/examples/index.html b/res/META-INF/tomcat7-websocket.jar/web-fragment.xml
similarity index 65%
copy from webapps/examples/index.html
copy to res/META-INF/tomcat7-websocket.jar/web-fragment.xml
index 475dc9f..d5d302f 100644
--- a/webapps/examples/index.html
+++ b/res/META-INF/tomcat7-websocket.jar/web-fragment.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
 <!--
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
@@ -14,17 +15,12 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<HTML><HEAD><TITLE>Apache Tomcat Examples</TITLE>
-<META http-equiv=Content-Type content="text/html">
-</HEAD>
-<BODY>
-<P>
-<H3>Apache Tomcat Examples</H3>
-<P></P>
-<ul>
-<li><a href="servlets">Servlets examples</a></li>
-<li><a href="jsp">JSP Examples</a></li>
-<li><a href="websocket">WebSocket Examples</a></li>
-</ul>
-</BODY></HTML>
+<web-fragment xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
+                      http://xmlns.jcp.org/xml/ns/javaee/web-fragment_3_1.xsd"
+  version="3.1"
+  metadata-complete="true">
+  <name>org_apache_tomcat_websocket</name>
+  <distributable/>
+</web-fragment>
\ No newline at end of file
diff --git a/res/META-INF/websocket-api.jar.manifest b/res/META-INF/websocket-api.jar.manifest
new file mode 100644
index 0000000..a9651a8
--- /dev/null
+++ b/res/META-INF/websocket-api.jar.manifest
@@ -0,0 +1,11 @@
+Manifest-version: 1.0
+X-Compile-Source-JDK: @source.jdk@
+X-Compile-Target-JDK: @target.jdk@
+
+Name: javax/el/
+Specification-Title: WebSocket
+Specification-Version: 1.0
+Specification-Vendor: Oracle, Inc.
+Implementation-Title: javax.net.websocket
+Implementation-Version: 1.0. at websocket.revision@
+Implementation-Vendor: Apache Software Foundation
diff --git a/res/checkstyle/javax-import-control.xml b/res/checkstyle/javax-import-control.xml
index 2150ae2..2e7201d 100644
--- a/res/checkstyle/javax-import-control.xml
+++ b/res/checkstyle/javax-import-control.xml
@@ -44,6 +44,9 @@
       <allow pkg="javax.servlet.jsp"/>
     </subpackage>
   </subpackage>
+  <subpackage name="websocket">
+    <allow pkg="javax.websocket"/>
+  </subpackage>
   <subpackage name="xml.ws">
     <allow pkg="javax.xwl.ws"/>
   </subpackage>
diff --git a/res/checkstyle/org-import-control.xml b/res/checkstyle/org-import-control.xml
index 6f7e504..5af2f6a 100644
--- a/res/checkstyle/org-import-control.xml
+++ b/res/checkstyle/org-import-control.xml
@@ -79,6 +79,7 @@
     </subpackage>
   </subpackage>
   <subpackage name="coyote">
+    <allow pkg="javax.servlet"/>
     <allow pkg="org.apache.coyote"/>
     <allow pkg="org.apache.juli"/>
     <allow pkg="org.apache.tomcat.jni"/>
@@ -127,5 +128,24 @@
         <allow pkg="org.apache.tomcat" exact-match="true"/>
       </subpackage>
     </subpackage>
+    <subpackage name="websocket">
+      <allow pkg="javax.websocket"/>
+      <!-- Require to access HTTP Upgrade API -->
+      <allow pkg="org.apache.catalina.connector"/>
+      <!-- The Servlet 3.1 API replacement classes are in this package -->
+      <allow pkg="org.apache.coyote.http11.upgrade"/>
+      <allow pkg="org.apache.juli"/>
+      <allow pkg="org.apache.tomcat.util"/>
+      <!-- Ideally want to remove this -->
+      <allow pkg="org.apache.tomcat.websocket.pojo"/>
+      <disallow pkg="javax.servlet"/>
+      <subpackage name="pojo">
+        <allow pkg="org.apache.tomcat.websocket"/>
+      </subpackage>
+      <subpackage name="server">
+        <allow pkg="javax.servlet"/>
+        <allow pkg="org.apache.tomcat.websocket"/>
+      </subpackage>
+    </subpackage>
   </subpackage>
 </import-control>
\ No newline at end of file
diff --git a/res/ide-support/eclipse/java-compiler-errors-warnings.txt b/res/ide-support/eclipse/java-compiler-errors-warnings.txt
index 6f8c2e8..20bb104 100644
--- a/res/ide-support/eclipse/java-compiler-errors-warnings.txt
+++ b/res/ide-support/eclipse/java-compiler-errors-warnings.txt
@@ -52,7 +52,7 @@ Unnecessary code
    (all additional check boxes)
 Generic types
  - All                                  - W
-   (ignore unavoidable generics warnings)   (Eclipse 3.7+)
+   [ ] Ignore unavoidable generic type problems
 Annotations
  - All                                  - W
    (all additional check boxes)
diff --git a/res/maven/mvn-pub.xml b/res/maven/mvn-pub.xml
index 3ed4186..b9ccda2 100644
--- a/res/maven/mvn-pub.xml
+++ b/res/maven/mvn-pub.xml
@@ -305,10 +305,15 @@
                   jarFileName="servlet-api.jar"
                srcJarFileName="servlet-api-src.jar"/>
 
+    <doMavenDeploy artifactId="tomcat-websocket-api"
+                  jarFileName="websocket-api.jar"
+               srcJarFileName="websocket-api-src.jar"/>
+
     <doMavenDeploy artifactId="tomcat-api"/>
     <doMavenDeploy artifactId="tomcat-util"/>
     <doMavenDeploy artifactId="tomcat-coyote"/>
     <doMavenDeploy artifactId="tomcat-dbcp"/>
+    <doMavenDeploy artifactId="tomcat7-websocket"/>
     <doMavenDeployNoSrc artifactId="tomcat-i18n-es"/>
     <doMavenDeployNoSrc artifactId="tomcat-i18n-fr"/>
     <doMavenDeployNoSrc artifactId="tomcat-i18n-ja"/>
diff --git a/res/maven/mvn.properties.default b/res/maven/mvn.properties.default
index 73b5a1c..0bc29c0 100644
--- a/res/maven/mvn.properties.default
+++ b/res/maven/mvn.properties.default
@@ -35,7 +35,7 @@ maven.asf.release.repo.url=https://repository.apache.org/service/local/staging/d
 maven.asf.release.repo.repositoryId=apache.releases
 
 # Release version info
-maven.asf.release.deploy.version=7.0.42
+maven.asf.release.deploy.version=7.0.45
 
 #Where do we load the libraries from
 tomcat.lib.path=../../output/build/lib
diff --git a/res/maven/tomcat-coyote.pom b/res/maven/tomcat-coyote.pom
index 5330bdc..25584ae 100644
--- a/res/maven/tomcat-coyote.pom
+++ b/res/maven/tomcat-coyote.pom
@@ -32,6 +32,12 @@
   <dependencies>
     <dependency>
       <groupId>org.apache.tomcat</groupId>
+      <artifactId>tomcat-servlet-api</artifactId>
+      <version>@MAVEN.DEPLOY.VERSION@</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat</groupId>
       <artifactId>tomcat-juli</artifactId>
       <version>@MAVEN.DEPLOY.VERSION@</version>
       <scope>compile</scope>
diff --git a/res/maven/tomcat-coyote.pom b/res/maven/tomcat-websocket-api.pom
similarity index 69%
copy from res/maven/tomcat-coyote.pom
copy to res/maven/tomcat-websocket-api.pom
index 5330bdc..df4a5b1 100644
--- a/res/maven/tomcat-coyote.pom
+++ b/res/maven/tomcat-websocket-api.pom
@@ -18,9 +18,9 @@
 <project>
   <modelVersion>4.0.0</modelVersion>
   <groupId>org.apache.tomcat</groupId>
-  <artifactId>tomcat-coyote</artifactId>
+  <artifactId>tomcat-websocket-api</artifactId>
   <version>@MAVEN.DEPLOY.VERSION@</version>
-  <description>Tomcat Connectors and HTTP parser</description>
+  <description>WebSocket (JSR356) API</description>
   <url>http://tomcat.apache.org/</url>
   <licenses>
     <license>
@@ -29,18 +29,4 @@
       <distribution>repo</distribution>
     </license>
   </licenses>
-  <dependencies>
-    <dependency>
-      <groupId>org.apache.tomcat</groupId>
-      <artifactId>tomcat-juli</artifactId>
-      <version>@MAVEN.DEPLOY.VERSION@</version>
-      <scope>compile</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.tomcat</groupId>
-      <artifactId>tomcat-util</artifactId>
-      <version>@MAVEN.DEPLOY.VERSION@</version>
-      <scope>compile</scope>
-    </dependency>
-  </dependencies>
 </project>
diff --git a/res/maven/tomcat-coyote.pom b/res/maven/tomcat7-websocket.pom
similarity index 69%
copy from res/maven/tomcat-coyote.pom
copy to res/maven/tomcat7-websocket.pom
index 5330bdc..21c6461 100644
--- a/res/maven/tomcat-coyote.pom
+++ b/res/maven/tomcat7-websocket.pom
@@ -18,9 +18,9 @@
 <project>
   <modelVersion>4.0.0</modelVersion>
   <groupId>org.apache.tomcat</groupId>
-  <artifactId>tomcat-coyote</artifactId>
+  <artifactId>tomcat7-websocket</artifactId>
   <version>@MAVEN.DEPLOY.VERSION@</version>
-  <description>Tomcat Connectors and HTTP parser</description>
+  <description>Tomcat WebSocket (JSR356) implementation</description>
   <url>http://tomcat.apache.org/</url>
   <licenses>
     <license>
@@ -32,6 +32,24 @@
   <dependencies>
     <dependency>
       <groupId>org.apache.tomcat</groupId>
+      <artifactId>tomcat-websocket-api</artifactId>
+      <version>@MAVEN.DEPLOY.VERSION@</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat</groupId>
+      <artifactId>tomcat-catalina</artifactId>
+      <version>@MAVEN.DEPLOY.VERSION@</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat</groupId>
+      <artifactId>tomcat-coyote</artifactId>
+      <version>@MAVEN.DEPLOY.VERSION@</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat</groupId>
       <artifactId>tomcat-juli</artifactId>
       <version>@MAVEN.DEPLOY.VERSION@</version>
       <scope>compile</scope>
diff --git a/test/deployment/context.war b/test/deployment/context.war
new file mode 100644
index 0000000..677c1a6
Binary files /dev/null and b/test/deployment/context.war differ
diff --git a/webapps/examples/index.html b/test/deployment/dirContext/META-INF/context.xml
similarity index 66%
copy from webapps/examples/index.html
copy to test/deployment/dirContext/META-INF/context.xml
index 475dc9f..3b73dc0 100644
--- a/webapps/examples/index.html
+++ b/test/deployment/dirContext/META-INF/context.xml
@@ -14,17 +14,4 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<HTML><HEAD><TITLE>Apache Tomcat Examples</TITLE>
-<META http-equiv=Content-Type content="text/html">
-</HEAD>
-<BODY>
-<P>
-<H3>Apache Tomcat Examples</H3>
-<P></P>
-<ul>
-<li><a href="servlets">Servlets examples</a></li>
-<li><a href="jsp">JSP Examples</a></li>
-<li><a href="websocket">WebSocket Examples</a></li>
-</ul>
-</BODY></HTML>
+<Context sessionCookieName="DIR_CONTEXT" />
\ No newline at end of file
diff --git a/webapps/examples/index.html b/test/deployment/dirContext/index.html
similarity index 66%
copy from webapps/examples/index.html
copy to test/deployment/dirContext/index.html
index 475dc9f..0753b0a 100644
--- a/webapps/examples/index.html
+++ b/test/deployment/dirContext/index.html
@@ -14,17 +14,9 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<HTML><HEAD><TITLE>Apache Tomcat Examples</TITLE>
-<META http-equiv=Content-Type content="text/html">
-</HEAD>
-<BODY>
-<P>
-<H3>Apache Tomcat Examples</H3>
-<P></P>
-<ul>
-<li><a href="servlets">Servlets examples</a></li>
-<li><a href="jsp">JSP Examples</a></li>
-<li><a href="websocket">WebSocket Examples</a></li>
-</ul>
-</BODY></HTML>
+<!-- dirContext -->
+<html>
+  <body>
+    <p>Directory based web application with a context.xml file.</p>
+  </body>
+</html>
\ No newline at end of file
diff --git a/webapps/examples/index.html b/test/deployment/dirNoContext/index.html
similarity index 66%
copy from webapps/examples/index.html
copy to test/deployment/dirNoContext/index.html
index 475dc9f..9b3b5a4 100644
--- a/webapps/examples/index.html
+++ b/test/deployment/dirNoContext/index.html
@@ -14,17 +14,9 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<HTML><HEAD><TITLE>Apache Tomcat Examples</TITLE>
-<META http-equiv=Content-Type content="text/html">
-</HEAD>
-<BODY>
-<P>
-<H3>Apache Tomcat Examples</H3>
-<P></P>
-<ul>
-<li><a href="servlets">Servlets examples</a></li>
-<li><a href="jsp">JSP Examples</a></li>
-<li><a href="websocket">WebSocket Examples</a></li>
-</ul>
-</BODY></HTML>
+<!-- dirNoContext -->
+<html>
+  <body>
+    <p>Directory based web application with no context.xml file.</p>
+  </body>
+</html>
\ No newline at end of file
diff --git a/test/deployment/noContext.war b/test/deployment/noContext.war
new file mode 100644
index 0000000..ef951bc
Binary files /dev/null and b/test/deployment/noContext.war differ
diff --git a/test/javax/el/TestArrayELResolver.java b/test/javax/el/TestArrayELResolver.java
new file mode 100644
index 0000000..7b4f6d3
--- /dev/null
+++ b/test/javax/el/TestArrayELResolver.java
@@ -0,0 +1,362 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.el;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.jasper.el.ELContextImpl;
+
+public class TestArrayELResolver {
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetType01() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        resolver.getType(null, new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is not resolved if base is not an array.
+     */
+    @Test
+    public void testGetType02() {
+        doNegativeTest(new Object(), new Object(), MethodUnderTest.GET_TYPE,
+                true);
+    }
+
+    /**
+     * Tests that a valid property is resolved.
+     */
+    @Test
+    public void testGetType03() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        ELContext context = new ELContextImpl();
+
+        String[] base = new String[] { "element" };
+        Class<?> result = resolver.getType(context, base, new Integer(0));
+
+        Assert.assertEquals(base.getClass().getComponentType(), result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that the key is out of bounds and exception will be thrown.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testGetType04() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        ELContext context = new ELContextImpl();
+
+        String[] base = new String[] { "element" };
+        resolver.getType(context, base, new Integer(1));
+    }
+
+    /**
+     * Tests that a result is returned even when a coercion cannot be performed.
+     */
+    @Test
+    public void testGetType05() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        ELContext context = new ELContextImpl();
+
+        String[] base = new String[] { "element" };
+        Class<?> result = resolver.getType(context, base, "index");
+
+        Assert.assertEquals(base.getClass().getComponentType(), result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetValue01() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        resolver.getValue(null, new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is not resolved if base is not an array.
+     */
+    @Test
+    public void testGetValue02() {
+        doNegativeTest(new Object(), new Object(), MethodUnderTest.GET_VALUE,
+                true);
+    }
+
+    /**
+     * Tests that a valid property is resolved.
+     */
+    @Test
+    public void testGetValue03() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        ELContext context = new ELContextImpl();
+
+        String[] base = new String[] { "element" };
+        Object result = resolver.getValue(context, base, new Integer(0));
+
+        Assert.assertEquals("element", result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests a coercion cannot be performed as the key is not integer.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetValue04() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        ELContext context = new ELContextImpl();
+
+        String[] base = new String[] { "element" };
+        resolver.getValue(context, base, "key");
+    }
+
+    /**
+     * Tests that the key is out of bounds and null will be returned.
+     */
+    @Test
+    public void testGetValue05() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        ELContext context = new ELContextImpl();
+
+        String[] base = new String[] { "element" };
+        Object result = resolver.getValue(context, base, new Integer(1));
+
+        Assert.assertNull(result);
+        Assert.assertTrue(context.isPropertyResolved());
+
+        result = resolver.getValue(context, base, new Integer(-1));
+
+        Assert.assertNull(result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testSetValue01() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        resolver.setValue(null, new Object(), new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is not set if base is not an array.
+     */
+    @Test
+    public void testSetValue02() {
+        doNegativeTest(new Object(), new Object(), MethodUnderTest.SET_VALUE,
+                false);
+    }
+
+    /**
+     * Tests that an exception is thrown when readOnly is true.
+     */
+    @Test(expected = PropertyNotWritableException.class)
+    public void testSetValue03() {
+        ArrayELResolver resolver = new ArrayELResolver(true);
+        ELContext context = new ELContextImpl();
+
+        resolver.setValue(context, new String[] {}, new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is set.
+     */
+    @Test
+    public void testSetValue04() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        ELContext context = new ELContextImpl();
+
+        String[] base = new String[] { "element" };
+        resolver.setValue(context, base, new Integer(0), "new-element");
+
+        Assert.assertEquals("new-element",
+                resolver.getValue(context, base, new Integer(0)));
+        Assert.assertTrue(context.isPropertyResolved());
+
+        resolver.setValue(context, base, new Integer(0), null);
+
+        Assert.assertEquals(null,
+                resolver.getValue(context, base, new Integer(0)));
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests a coercion cannot be performed as the key is not integer.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetValue05() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        ELContext context = new ELContextImpl();
+
+        String[] base = new String[] { "element" };
+        resolver.setValue(context, base, "key", "new-element");
+    }
+
+    /**
+     * Tests that the key is out of bounds and exception will be thrown.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testSetValue06() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        ELContext context = new ELContextImpl();
+
+        String[] base = new String[] { "element" };
+        resolver.setValue(context, base, new Integer(1), "new-element");
+    }
+
+    /**
+     * Tests that an exception will be thrown if the value is not from the
+     * corresponding type.
+     */
+    @Test(expected = ClassCastException.class)
+    public void testSetValue07() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        ELContext context = new ELContextImpl();
+
+        String[] base = new String[] { "element" };
+        resolver.setValue(context, base, new Integer(0), new Integer(1));
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testIsReadOnly01() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        resolver.isReadOnly(null, new Object(), new Object());
+    }
+
+    /**
+     * Tests that the propertyResolved is false if base is not an array.
+     */
+    @Test
+    public void testIsReadOnly02() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        ELContext context = new ELContextImpl();
+
+        boolean result = resolver.isReadOnly(context, new Object(),
+                new Object());
+
+        Assert.assertFalse(result);
+        Assert.assertFalse(context.isPropertyResolved());
+
+        resolver = new ArrayELResolver(true);
+
+        result = resolver.isReadOnly(context, new Object(), new Object());
+
+        Assert.assertTrue(result);
+        Assert.assertFalse(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that if the ArrayELResolver is constructed with readOnly the method
+     * will return always true, otherwise false.
+     */
+    @Test
+    public void testIsReadOnly03() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        ELContext context = new ELContextImpl();
+
+        String[] base = new String[] { "element" };
+        boolean result = resolver.isReadOnly(context, base, new Integer(0));
+
+        Assert.assertFalse(result);
+        Assert.assertTrue(context.isPropertyResolved());
+
+        resolver = new ArrayELResolver(true);
+
+        result = resolver.isReadOnly(context, base, new Integer(0));
+
+        Assert.assertTrue(result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that the key is out of bounds and exception will be thrown.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testIsReadOnly04() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        ELContext context = new ELContextImpl();
+
+        String[] base = new String[] { "element" };
+        resolver.isReadOnly(context, base, new Integer(1));
+    }
+
+    /**
+     * Tests that a result is returned even when a coercion cannot be performed.
+     */
+    @Test
+    public void testIsReadOnly05() {
+        ArrayELResolver resolver = new ArrayELResolver();
+        ELContext context = new ELContextImpl();
+
+        String[] base = new String[] { "element" };
+        boolean result = resolver.isReadOnly(context, base, "key");
+
+        Assert.assertFalse(result);
+        Assert.assertTrue(context.isPropertyResolved());
+
+        resolver = new ArrayELResolver(true);
+
+        result = resolver.isReadOnly(context, base, "key");
+
+        Assert.assertTrue(result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    private void doNegativeTest(Object base, Object trigger,
+            MethodUnderTest method, boolean checkResult) {
+        ArrayELResolver resolver = new ArrayELResolver();
+        ELContext context = new ELContextImpl();
+
+        Object result = null;
+        switch (method) {
+        case GET_VALUE: {
+            result = resolver.getValue(context, base, trigger);
+            break;
+        }
+        case SET_VALUE: {
+            resolver.setValue(context, base, trigger, new Object());
+            break;
+        }
+        case GET_TYPE: {
+            result = resolver.getType(context, base, trigger);
+            break;
+        }
+        default: {
+            // Should never happen
+            Assert.fail("Missing case for method");
+        }
+        }
+
+        if (checkResult) {
+            Assert.assertNull(result);
+        }
+        Assert.assertFalse(context.isPropertyResolved());
+    }
+
+    private static enum MethodUnderTest {
+        GET_VALUE, SET_VALUE, GET_TYPE
+    }
+
+}
diff --git a/test/javax/el/TestBeanELResolver.java b/test/javax/el/TestBeanELResolver.java
index aeb9d1f..bdf4184 100644
--- a/test/javax/el/TestBeanELResolver.java
+++ b/test/javax/el/TestBeanELResolver.java
@@ -16,14 +16,26 @@
  */
 package javax.el;
 
-import org.junit.Assert;
+import java.beans.FeatureDescriptor;
+import java.beans.PropertyDescriptor;
+import java.util.Iterator;
 
+import org.junit.Assert;
 import org.junit.Test;
 
 import org.apache.jasper.el.ELContextImpl;
 
 public class TestBeanELResolver {
 
+    private static final String METHOD01_NAME = "toString";
+    private static final String METHOD02_NAME = "<init>";
+    private static final String METHOD03_NAME = "nonExistingMethod";
+    private static final String BEAN_NAME = "test";
+    private static final String PROPERTY01_NAME = "valueA";
+    private static final String PROPERTY02_NAME = "valueB";
+    private static final String PROPERTY03_NAME = "name";
+    private static final String PROPERTY_VALUE = "test1";
+
     @Test
     public void testBug53421() {
         ExpressionFactory factory = ExpressionFactory.newInstance();
@@ -54,6 +66,398 @@ public class TestBeanELResolver {
                 msg.contains(type));
     }
 
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetType01() {
+        BeanELResolver resolver = new BeanELResolver();
+        resolver.getType(null, new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is not resolved if base is null.
+     */
+    @Test
+    public void testGetType02() {
+        doNegativeTest(null, new Object(), MethodUnderTest.GET_TYPE, true);
+    }
+
+    /**
+     * Tests that a valid property is resolved.
+     */
+    @Test
+    public void testGetType03() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        Class<?> result = resolver.getType(context, new Bean(), PROPERTY01_NAME);
+
+        Assert.assertEquals(String.class, result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that an exception will be thrown when the property does not exist.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testGetType04() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        resolver.getType(context, new Bean(), PROPERTY02_NAME);
+    }
+
+    /**
+     * Tests that an exception will be thrown when a coercion cannot be
+     * performed.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testGetType05() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        resolver.getType(context, new Bean(), new Object());
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetValue01() {
+        BeanELResolver resolver = new BeanELResolver();
+        resolver.getValue(null, new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is not resolved if base is null.
+     */
+    @Test
+    public void testGetValue02() {
+        doNegativeTest(null, new Object(), MethodUnderTest.GET_VALUE, true);
+    }
+
+    /**
+     * Tests that a valid property is resolved.
+     */
+    @Test
+    public void testGetValue03() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        Object result = resolver.getValue(context, new TesterBean(BEAN_NAME), PROPERTY03_NAME);
+
+        Assert.assertEquals(BEAN_NAME, result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that an exception will be thrown when the property does not exist.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testGetValue04() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        resolver.getValue(context, new Bean(), PROPERTY02_NAME);
+    }
+
+    /**
+     * Tests that an exception will be thrown when a coercion cannot be
+     * performed.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testGetValue05() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        resolver.getValue(context, new Bean(), new Object());
+    }
+
+    /**
+     * Tests that an exception will be thrown when the property is not readable.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testGetValue06() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        resolver.getValue(context, new Bean(), PROPERTY01_NAME);
+    }
+
+    /**
+     * Tests that getter method throws exception which should be propagated.
+     */
+    @Test(expected = ELException.class)
+    public void testGetValue07() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        resolver.getValue(context, new TesterBean(BEAN_NAME), PROPERTY01_NAME);
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testSetValue01() {
+        BeanELResolver resolver = new BeanELResolver();
+        resolver.setValue(null, new Object(), new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is not resolved if base is null.
+     */
+    @Test
+    public void testSetValue02() {
+        doNegativeTest(null, new Object(), MethodUnderTest.SET_VALUE, true);
+    }
+
+    /**
+     * Tests that an exception is thrown when readOnly is true.
+     */
+    @Test(expected = PropertyNotWritableException.class)
+    public void testSetValue03() {
+        BeanELResolver resolver = new BeanELResolver(true);
+        ELContext context = new ELContextImpl();
+
+        resolver.setValue(context, new Bean(), new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is resolved.
+     */
+    @Test
+    public void testSetValue04() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        TesterBean bean = new TesterBean(BEAN_NAME);
+        resolver.setValue(context, bean, PROPERTY03_NAME, PROPERTY_VALUE);
+
+        Assert.assertEquals(PROPERTY_VALUE, resolver.getValue(context, bean, PROPERTY03_NAME));
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that an exception will be thrown when a coercion cannot be
+     * performed.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testSetValue05() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        resolver.setValue(context, new Bean(), new Object(), PROPERTY_VALUE);
+    }
+
+    /**
+     * Tests that an exception will be thrown when the property does not exist.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testSetValue06() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        resolver.setValue(context, new Bean(), PROPERTY02_NAME, PROPERTY_VALUE);
+    }
+
+    /**
+     * Tests that an exception will be thrown when the property does not have
+     * setter method.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testSetValue07() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        resolver.setValue(context, new TesterBean(BEAN_NAME), PROPERTY01_NAME, PROPERTY_VALUE);
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testIsReadOnly01() {
+        BeanELResolver resolver = new BeanELResolver();
+        resolver.isReadOnly(null, new Object(), new Object());
+    }
+
+    /**
+     * Tests that the propertyResolved is false if base is null.
+     */
+    @Test
+    public void testIsReadOnly02() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        resolver.isReadOnly(context, null, new Object());
+
+        Assert.assertFalse(context.isPropertyResolved());
+
+        resolver = new BeanELResolver(true);
+
+        resolver.isReadOnly(context, null, new Object());
+
+        Assert.assertFalse(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that if the BeanELResolver is constructed with readOnly the method
+     * will return always true.
+     */
+    @Test
+    public void testIsReadOnly03() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        boolean result = resolver.isReadOnly(context, new TesterBean(BEAN_NAME), PROPERTY03_NAME);
+
+        Assert.assertFalse(result);
+        Assert.assertTrue(context.isPropertyResolved());
+
+        resolver = new BeanELResolver(true);
+
+        result = resolver.isReadOnly(context, new TesterBean(BEAN_NAME), PROPERTY03_NAME);
+
+        Assert.assertTrue(result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that an exception is thrown when a coercion cannot be performed.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testIsReadOnly04() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        resolver.isReadOnly(context, new TesterBean(BEAN_NAME), new Integer(0));
+    }
+
+    /**
+     * Tests that an exception will be thrown when the property does not exist.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testIsReadOnly05() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        resolver.isReadOnly(context, new Bean(), PROPERTY02_NAME);
+    }
+
+    /**
+     * Tests that true will be returned when the property does not have setter
+     * method.
+     */
+    @Test
+    public void testIsReadOnly06() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        boolean result = resolver.isReadOnly(context, new TesterBean(BEAN_NAME), PROPERTY01_NAME);
+
+        Assert.assertTrue(result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that a valid FeatureDescriptors are not returned if base is not
+     * Map.
+     */
+    @Test
+    public void testGetFeatureDescriptors01() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        Iterator<FeatureDescriptor> result = resolver.getFeatureDescriptors(context, null);
+
+        Assert.assertNull(result);
+    }
+
+    /**
+     * Tests that a valid FeatureDescriptors are returned.
+     */
+    @Test
+    public void testGetFeatureDescriptors02() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        Iterator<FeatureDescriptor> result = resolver.getFeatureDescriptors(context, new Bean());
+
+        while (result.hasNext()) {
+            PropertyDescriptor featureDescriptor = (PropertyDescriptor) result.next();
+            Assert.assertEquals(featureDescriptor.getPropertyType(),
+                    featureDescriptor.getValue(ELResolver.TYPE));
+            Assert.assertEquals(Boolean.TRUE,
+                    featureDescriptor.getValue(ELResolver.RESOLVABLE_AT_DESIGN_TIME));
+        }
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testInvoke01() {
+        BeanELResolver resolver = new BeanELResolver();
+        resolver.invoke(null, new Object(), new Object(), new Class<?>[0], new Object[0]);
+    }
+
+    /**
+     * Tests that a valid property is not resolved if base is null.
+     */
+    @Test
+    public void testInvoke02() {
+        doNegativeTest(null, new Object(), MethodUnderTest.INVOKE, true);
+    }
+
+    /**
+     * Tests a method invocation.
+     */
+    @Test
+    public void testInvoke03() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        Object result = resolver.invoke(context, new TesterBean(BEAN_NAME), METHOD01_NAME,
+                new Class<?>[] {}, new Object[] {});
+
+        Assert.assertEquals(BEAN_NAME, result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that the method name cannot be coerced to String.
+     */
+    @Test
+    public void testInvoke04() {
+        doNegativeTest(new Bean(), null, MethodUnderTest.INVOKE, true);
+    }
+
+    /**
+     * Tests that a call to <init> as a method name will throw an exception.
+     */
+    @Test(expected = MethodNotFoundException.class)
+    public void testInvoke05() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        resolver.invoke(context, new TesterBean(BEAN_NAME), METHOD02_NAME, new Class<?>[] {},
+                new Object[] {});
+    }
+
+    /**
+     * Tests that a call to a non existing method will throw an exception.
+     */
+    @Test(expected = MethodNotFoundException.class)
+    public void testInvoke06() {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        resolver.invoke(context, new TesterBean(BEAN_NAME), METHOD03_NAME, new Class<?>[] {},
+                new Object[] {});
+    }
+
     private static class Bean {
 
         @SuppressWarnings("unused")
@@ -61,4 +465,44 @@ public class TestBeanELResolver {
             // NOOP
         }
     }
+
+    private void doNegativeTest(Object base, Object trigger, MethodUnderTest method,
+            boolean checkResult) {
+        BeanELResolver resolver = new BeanELResolver();
+        ELContext context = new ELContextImpl();
+
+        Object result = null;
+        switch (method) {
+        case GET_VALUE: {
+            result = resolver.getValue(context, base, trigger);
+            break;
+        }
+        case SET_VALUE: {
+            resolver.setValue(context, base, trigger, new Object());
+            break;
+        }
+        case GET_TYPE: {
+            result = resolver.getType(context, base, trigger);
+            break;
+        }
+        case INVOKE: {
+            result = resolver.invoke(context, base, trigger, new Class<?>[0], new Object[0]);
+            break;
+        }
+        default: {
+            // Should never happen
+            Assert.fail("Missing case for method");
+        }
+        }
+
+        if (checkResult) {
+            Assert.assertNull(result);
+        }
+        Assert.assertFalse(context.isPropertyResolved());
+    }
+
+    private static enum MethodUnderTest {
+        GET_VALUE, SET_VALUE, GET_TYPE, INVOKE
+    }
+
 }
diff --git a/java/javax/annotation/security/DeclareRoles.java b/test/javax/el/TestELContext.java
similarity index 70%
copy from java/javax/annotation/security/DeclareRoles.java
copy to test/javax/el/TestELContext.java
index d5d214a..d59b19a 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/test/javax/el/TestELContext.java
@@ -5,27 +5,28 @@
  * The ASF licenses this file to You 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 javax.el;
 
+import org.junit.Test;
 
-package javax.annotation.security;
+public class TestELContext {
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+    /**
+     * Tests that a null key results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetContext() {
+        ELContext elContext = new TesterELContext();
+        elContext.getContext(null);
+    }
 
- at Target({ElementType.TYPE})
- at Retention(RetentionPolicy.RUNTIME)
-
-public @interface DeclareRoles {
-    public String[] value();
 }
diff --git a/test/javax/el/TestListELResolver.java b/test/javax/el/TestListELResolver.java
new file mode 100644
index 0000000..4380198
--- /dev/null
+++ b/test/javax/el/TestListELResolver.java
@@ -0,0 +1,375 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.el;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.jasper.el.ELContextImpl;
+
+public class TestListELResolver {
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetValue01() {
+        ListELResolver resolver = new ListELResolver();
+        resolver.getValue(null, new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is not resolved if base is not List.
+     */
+    @Test
+    public void testGetValue02() {
+        doNegativeTest(new Object(), new Object(), MethodUnderTest.GET_VALUE,
+                true);
+    }
+
+    /**
+     * Tests that a valid property is resolved.
+     */
+    @Test
+    public void testGetValue03() {
+        ListELResolver resolver = new ListELResolver();
+        ELContext context = new ELContextImpl();
+
+        List<String> list = new ArrayList<String>();
+        list.add("key");
+        Object result = resolver.getValue(context, list, new Integer(0));
+
+        Assert.assertEquals("key", result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests a coercion cannot be performed as the key is not integer.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetValue04() {
+        ListELResolver resolver = new ListELResolver();
+        ELContext context = new ELContextImpl();
+
+        List<String> list = new ArrayList<String>();
+        list.add("key");
+        resolver.getValue(context, list, "key");
+    }
+
+    /**
+     * Tests that the key is out of bounds and null will be returned.
+     */
+    @Test
+    public void testGetValue05() {
+        ListELResolver resolver = new ListELResolver();
+        ELContext context = new ELContextImpl();
+
+        List<String> list = new ArrayList<String>();
+        list.add("key");
+        Object result = resolver.getValue(context, list, new Integer(1));
+
+        Assert.assertNull(result);
+        Assert.assertTrue(context.isPropertyResolved());
+
+        result = resolver.getValue(context, list, new Integer(-1));
+
+        Assert.assertNull(result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetType01() {
+        ListELResolver resolver = new ListELResolver();
+        resolver.getType(null, new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is not resolved if base is not List.
+     */
+    @Test
+    public void testGetType02() {
+        doNegativeTest(new Object(), new Object(), MethodUnderTest.GET_TYPE,
+                true);
+    }
+
+    /**
+     * Tests that a valid property is resolved.
+     */
+    @Test
+    public void testGetType03() {
+        ListELResolver resolver = new ListELResolver();
+        ELContext context = new ELContextImpl();
+
+        List<String> list = new ArrayList<String>();
+        list.add("key");
+        Class<?> result = resolver.getType(context, list, new Integer(0));
+
+        Assert.assertEquals(Object.class, result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that the key is out of bounds and exception will be thrown.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testGetType04() {
+        ListELResolver resolver = new ListELResolver();
+        ELContext context = new ELContextImpl();
+
+        List<String> list = new ArrayList<String>();
+        list.add("key");
+        resolver.getType(context, list, new Integer(1));
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testSetValue01() {
+        ListELResolver resolver = new ListELResolver();
+        resolver.setValue(null, new Object(), new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is not set if base is not List.
+     */
+    @Test
+    public void testSetValue02() {
+        doNegativeTest(new Object(), new Object(), MethodUnderTest.SET_VALUE,
+                false);
+    }
+
+    /**
+     * Tests that an exception is thrown when readOnly is true.
+     */
+    @Test(expected = PropertyNotWritableException.class)
+    public void testSetValue03() {
+        ListELResolver resolver = new ListELResolver(true);
+        ELContext context = new ELContextImpl();
+
+        resolver.setValue(context, new ArrayList<Object>(), new Object(),
+                new Object());
+    }
+
+    /**
+     * Tests that a valid property is set.
+     */
+    @Test
+    public void testSetValue04() {
+        ListELResolver resolver = new ListELResolver();
+        ELContext context = new ELContextImpl();
+
+        List<String> list = new ArrayList<String>();
+        list.add("value");
+        resolver.setValue(context, list, new Integer(0), "value");
+
+        Assert.assertEquals("value",
+                resolver.getValue(context, list, new Integer(0)));
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that an exception is thrown when the list is not modifiable.
+     */
+    @Test(expected = PropertyNotWritableException.class)
+    public void testSetValue05() {
+        ListELResolver resolver = new ListELResolver();
+        ELContext context = new ELContextImpl();
+
+        List<Object> list = Collections
+                .unmodifiableList(new ArrayList<Object>());
+        resolver.setValue(context, list, new Integer(0), "value");
+    }
+
+    /**
+     * Tests a coercion cannot be performed as the key is not integer.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetValue06() {
+        ListELResolver resolver = new ListELResolver();
+        ELContext context = new ELContextImpl();
+
+        List<String> list = new ArrayList<String>();
+        list.add("key");
+        resolver.setValue(context, list, "key", "value");
+    }
+
+    /**
+     * Tests that the key is out of bounds and exception will be thrown.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testSetValue07() {
+        ListELResolver resolver = new ListELResolver();
+        ELContext context = new ELContextImpl();
+
+        List<String> list = new ArrayList<String>();
+        list.add("key");
+        resolver.setValue(context, list, new Integer(1), "value");
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testIsReadOnly01() {
+        ListELResolver resolver = new ListELResolver();
+        resolver.isReadOnly(null, new Object(), new Object());
+    }
+
+    /**
+     * Tests that the propertyResolved is false if base is not List.
+     */
+    @Test
+    public void testIsReadOnly02() {
+        ListELResolver resolver = new ListELResolver();
+        ELContext context = new ELContextImpl();
+
+        boolean result = resolver.isReadOnly(context, new Object(),
+                new Object());
+
+        Assert.assertFalse(result);
+        Assert.assertFalse(context.isPropertyResolved());
+
+        resolver = new ListELResolver(true);
+
+        result = resolver.isReadOnly(context, new Object(), new Object());
+
+        Assert.assertTrue(result);
+        Assert.assertFalse(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that if the ListELResolver is constructed with readOnly the method
+     * will return always true, otherwise false.
+     */
+    @Test
+    public void testIsReadOnly03() {
+        ListELResolver resolver = new ListELResolver();
+        ELContext context = new ELContextImpl();
+
+        List<String> list = new ArrayList<String>();
+        list.add("key");
+        boolean result = resolver.isReadOnly(context, list, new Integer(0));
+
+        Assert.assertFalse(result);
+        Assert.assertTrue(context.isPropertyResolved());
+
+        resolver = new ListELResolver(true);
+
+        result = resolver.isReadOnly(context, list, new Integer(0));
+
+        Assert.assertTrue(result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that the readOnly is true always when the map is not modifiable.
+     */
+    @Test
+    public void testIsReadOnly04() {
+        ListELResolver resolver = new ListELResolver();
+        ELContext context = new ELContextImpl();
+
+        List<String> list = new ArrayList<String>();
+        list.add("key");
+        List<String> unmodifiableList = Collections.unmodifiableList(list);
+        boolean result = resolver.isReadOnly(context, unmodifiableList,
+                new Integer(0));
+
+        Assert.assertTrue(result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that the key is out of bounds and exception will be thrown.
+     */
+    @Test(expected = PropertyNotFoundException.class)
+    public void testIsReadOnly05() {
+        ListELResolver resolver = new ListELResolver();
+        ELContext context = new ELContextImpl();
+
+        List<String> list = new ArrayList<String>();
+        list.add("key");
+        resolver.isReadOnly(context, list, new Integer(1));
+    }
+
+    /**
+     * Tests that a result is returned even when a coercion cannot be performed.
+     */
+    @Test
+    public void testIsReadOnly06() {
+        ListELResolver resolver = new ListELResolver();
+        ELContext context = new ELContextImpl();
+
+        List<String> list = new ArrayList<String>();
+        list.add("key");
+        boolean result = resolver.isReadOnly(context, list, "key");
+
+        Assert.assertFalse(result);
+        Assert.assertTrue(context.isPropertyResolved());
+
+        resolver = new ListELResolver(true);
+
+        result = resolver.isReadOnly(context, list, "key");
+
+        Assert.assertTrue(result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    private void doNegativeTest(Object base, Object trigger,
+            MethodUnderTest method, boolean checkResult) {
+        ListELResolver resolver = new ListELResolver();
+        ELContext context = new ELContextImpl();
+
+        Object result = null;
+        switch (method) {
+        case GET_VALUE: {
+            result = resolver.getValue(context, base, trigger);
+            break;
+        }
+        case SET_VALUE: {
+            resolver.setValue(context, base, trigger, new Object());
+            break;
+        }
+        case GET_TYPE: {
+            result = resolver.getType(context, base, trigger);
+            break;
+        }
+        default: {
+            // Should never happen
+            Assert.fail("Missing case for method");
+        }
+        }
+
+        if (checkResult) {
+            Assert.assertNull(result);
+        }
+        Assert.assertFalse(context.isPropertyResolved());
+    }
+
+    private static enum MethodUnderTest {
+        GET_VALUE, SET_VALUE, GET_TYPE
+    }
+
+}
diff --git a/test/javax/el/TestMapELResolver.java b/test/javax/el/TestMapELResolver.java
new file mode 100644
index 0000000..f325b14
--- /dev/null
+++ b/test/javax/el/TestMapELResolver.java
@@ -0,0 +1,311 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 javax.el;
+
+import java.beans.FeatureDescriptor;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.jasper.el.ELContextImpl;
+
+public class TestMapELResolver {
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetType01() {
+        MapELResolver mapELResolver = new MapELResolver();
+        mapELResolver.getType(null, new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is not resolved if base is not Map.
+     */
+    @Test
+    public void testGetType02() {
+        doNegativeTest(new Object(), new Object(), MethodUnderTest.GET_TYPE,
+                true);
+    }
+
+    /**
+     * Tests that a valid property is resolved.
+     */
+    @Test
+    public void testGetType03() {
+        MapELResolver mapELResolver = new MapELResolver();
+        ELContext context = new ELContextImpl();
+
+        Class<?> result = mapELResolver.getType(context,
+                new HashMap<Object, Object>(), "test");
+
+        Assert.assertEquals(Object.class, result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetValue01() {
+        MapELResolver mapELResolver = new MapELResolver();
+        mapELResolver.getValue(null, new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is not resolved if base is not Map.
+     */
+    @Test
+    public void testGetValue02() {
+        doNegativeTest(new Object(), new Object(), MethodUnderTest.GET_VALUE,
+                true);
+    }
+
+    /**
+     * Tests that a valid property is resolved.
+     */
+    @Test
+    public void testGetValue03() {
+        MapELResolver mapELResolver = new MapELResolver();
+        ELContext context = new ELContextImpl();
+
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("key", "value");
+        Object result = mapELResolver.getValue(context, map, "key");
+
+        Assert.assertEquals("value", result);
+        Assert.assertTrue(context.isPropertyResolved());
+
+        result = mapELResolver.getValue(context, map, "unknown-key");
+
+        Assert.assertNull(result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testSetValue01() {
+        MapELResolver mapELResolver = new MapELResolver();
+        mapELResolver.setValue(null, new Object(), new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is not set if base is not Map.
+     */
+    @Test
+    public void testSetValue02() {
+        doNegativeTest(new Object(), new Object(), MethodUnderTest.SET_VALUE,
+                false);
+    }
+
+    /**
+     * Tests that an exception is thrown when readOnly is true.
+     */
+    @Test(expected = PropertyNotWritableException.class)
+    public void testSetValue03() {
+        MapELResolver mapELResolver = new MapELResolver(true);
+        ELContext context = new ELContextImpl();
+
+        mapELResolver.setValue(context, new HashMap<Object, Object>(),
+                new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is set.
+     */
+    @Test
+    public void testSetValue04() {
+        MapELResolver mapELResolver = new MapELResolver();
+        ELContext context = new ELContextImpl();
+
+        Map<String, String> map = new HashMap<String, String>();
+        mapELResolver.setValue(context, map, "key", "value");
+
+        Assert.assertEquals("value",
+                mapELResolver.getValue(context, map, "key"));
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that an exception is thrown when the map is not modifiable.
+     */
+    @Test(expected = PropertyNotWritableException.class)
+    public void testSetValue05() {
+        MapELResolver mapELResolver = new MapELResolver();
+        ELContext context = new ELContextImpl();
+
+        Map<Object, Object> map = Collections
+                .unmodifiableMap(new HashMap<Object, Object>());
+        mapELResolver.setValue(context, map, "key", "value");
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testIsReadOnly01() {
+        MapELResolver mapELResolver = new MapELResolver();
+        mapELResolver.isReadOnly(null, new Object(), new Object());
+    }
+
+    /**
+     * Tests that the propertyResolved is false if base is not Map.
+     */
+    @Test
+    public void testIsReadOnly02() {
+        MapELResolver mapELResolver = new MapELResolver();
+        ELContext context = new ELContextImpl();
+
+        boolean result = mapELResolver.isReadOnly(context, new Object(),
+                new Object());
+
+        Assert.assertFalse(result);
+        Assert.assertFalse(context.isPropertyResolved());
+
+        mapELResolver = new MapELResolver(true);
+
+        result = mapELResolver.isReadOnly(context, new Object(), new Object());
+
+        Assert.assertTrue(result);
+        Assert.assertFalse(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that if the MapELResolver is constructed with readOnly the method
+     * will return always true, otherwise false.
+     */
+    @Test
+    public void testIsReadOnly03() {
+        MapELResolver mapELResolver = new MapELResolver();
+        ELContext context = new ELContextImpl();
+
+        boolean result = mapELResolver.isReadOnly(context,
+                new HashMap<Object, Object>(), new Object());
+
+        Assert.assertFalse(result);
+        Assert.assertTrue(context.isPropertyResolved());
+
+        mapELResolver = new MapELResolver(true);
+
+        result = mapELResolver.isReadOnly(context,
+                new HashMap<Object, Object>(), new Object());
+
+        Assert.assertTrue(result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that the readOnly is true always when the map is not modifiable.
+     */
+    @Test
+    public void testIsReadOnly04() {
+        MapELResolver mapELResolver = new MapELResolver();
+        ELContext context = new ELContextImpl();
+
+        Map<Object, Object> map = Collections
+                .unmodifiableMap(new HashMap<Object, Object>());
+        boolean result = mapELResolver.isReadOnly(context, map, new Object());
+
+        Assert.assertTrue(result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that a valid FeatureDescriptors are not returned if base is not
+     * Map.
+     */
+    @Test
+    public void testGetFeatureDescriptors01() {
+        MapELResolver mapELResolver = new MapELResolver();
+        ELContext context = new ELContextImpl();
+
+        Iterator<FeatureDescriptor> result = mapELResolver
+                .getFeatureDescriptors(context, new Object());
+
+        Assert.assertNull(result);
+    }
+
+    /**
+     * Tests that a valid FeatureDescriptors are returned.
+     */
+    @Test
+    public void testGetFeatureDescriptors02() {
+        MapELResolver mapELResolver = new MapELResolver();
+        ELContext context = new ELContextImpl();
+
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("key", "value");
+        Iterator<FeatureDescriptor> result = mapELResolver
+                .getFeatureDescriptors(context, map);
+
+        while (result.hasNext()) {
+            FeatureDescriptor featureDescriptor = result.next();
+            Assert.assertEquals("key", featureDescriptor.getDisplayName());
+            Assert.assertEquals("key", featureDescriptor.getName());
+            Assert.assertEquals("", featureDescriptor.getShortDescription());
+            Assert.assertFalse(featureDescriptor.isExpert());
+            Assert.assertFalse(featureDescriptor.isHidden());
+            Assert.assertTrue(featureDescriptor.isPreferred());
+            Assert.assertEquals("key".getClass(),
+                    featureDescriptor.getValue(ELResolver.TYPE));
+            Assert.assertEquals(Boolean.TRUE, featureDescriptor
+                    .getValue(ELResolver.RESOLVABLE_AT_DESIGN_TIME));
+        }
+    }
+
+    private void doNegativeTest(Object base, Object trigger,
+            MethodUnderTest method, boolean checkResult) {
+        MapELResolver resolver = new MapELResolver();
+        ELContext context = new ELContextImpl();
+
+        Object result = null;
+        switch (method) {
+        case GET_VALUE: {
+            result = resolver.getValue(context, base, trigger);
+            break;
+        }
+        case SET_VALUE: {
+            resolver.setValue(context, base, trigger, new Object());
+            break;
+        }
+        case GET_TYPE: {
+            result = resolver.getType(context, base, trigger);
+            break;
+        }
+        default: {
+            // Should never happen
+            Assert.fail("Missing case for method");
+        }
+        }
+
+        if (checkResult) {
+            Assert.assertNull(result);
+        }
+        Assert.assertFalse(context.isPropertyResolved());
+    }
+
+    private static enum MethodUnderTest {
+        GET_VALUE, SET_VALUE, GET_TYPE
+    }
+}
diff --git a/test/javax/el/TestResourceBundleELResolver.java b/test/javax/el/TestResourceBundleELResolver.java
index fb5a511..c91233a 100644
--- a/test/javax/el/TestResourceBundleELResolver.java
+++ b/test/javax/el/TestResourceBundleELResolver.java
@@ -16,7 +16,9 @@
  */
 package javax.el;
 
+import java.beans.FeatureDescriptor;
 import java.util.Enumeration;
+import java.util.Iterator;
 import java.util.ListResourceBundle;
 import java.util.ResourceBundle;
 
@@ -34,16 +36,15 @@ public class TestResourceBundleELResolver {
 
         ResourceBundle rb = new TesterResourceBundle();
 
-        ValueExpression var =
-            factory.createValueExpression(rb, ResourceBundle.class);
+        ValueExpression var = factory.createValueExpression(rb,
+                ResourceBundle.class);
         context.getVariableMapper().setVariable("rb", var);
 
+        ValueExpression ve = factory.createValueExpression(context,
+                "${rb.keys}", String.class);
 
-        ValueExpression ve = factory.createValueExpression(
-                context, "${rb.keys}", String.class);
-
-        MethodExpression me = factory.createMethodExpression(
-                context, "${rb.getKeys()}", Enumeration.class, null);
+        MethodExpression me = factory.createMethodExpression(context,
+                "${rb.getKeys()}", Enumeration.class, null);
 
         // Ensure we are specification compliant
         String result1 = (String) ve.getValue(context);
@@ -62,17 +63,251 @@ public class TestResourceBundleELResolver {
         Assert.assertFalse(e.hasMoreElements());
     }
 
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetValue01() {
+        ResourceBundleELResolver resolver = new ResourceBundleELResolver();
+        resolver.getValue(null, new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is not resolved if base is not
+     * ResourceBundle.
+     */
+    @Test
+    public void testGetValue02() {
+        doNegativeTest(new Object(), new Object(), MethodUnderTest.GET_VALUE,
+                true);
+    }
+
+    /**
+     * Tests that a valid property is resolved.
+     */
+    @Test
+    public void testGetValue03() {
+        ResourceBundleELResolver resolver = new ResourceBundleELResolver();
+        ELContext context = new ELContextImpl();
+
+        ResourceBundle resourceBundle = new TesterResourceBundle();
+        Object result = resolver.getValue(context, resourceBundle, "key1");
+
+        Assert.assertEquals("value1", result);
+        Assert.assertTrue(context.isPropertyResolved());
+
+        result = resolver.getValue(context, resourceBundle, "unknown-key");
+
+        Assert.assertEquals("???unknown-key???", result);
+        Assert.assertTrue(context.isPropertyResolved());
+
+        result = resolver.getValue(context, resourceBundle, null);
+
+        Assert.assertNull(result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetType01() {
+        ResourceBundleELResolver resolver = new ResourceBundleELResolver();
+        resolver.getType(null, new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is not resolved if base is not
+     * ResourceBundle.
+     */
+    @Test
+    public void testGetType02() {
+        doNegativeTest(new Object(), new Object(), MethodUnderTest.GET_TYPE,
+                true);
+    }
+
+    /**
+     * Tests that null will be returned when base is ResourceBundle. Checks that
+     * the propertyResolved is true.
+     */
+    @Test
+    public void testGetType03() {
+        ResourceBundleELResolver resolver = new ResourceBundleELResolver();
+        ELContext context = new ELContextImpl();
+
+        ResourceBundle resourceBundle = new TesterResourceBundle();
+        Class<?> result = resolver.getType(context, resourceBundle, "key1");
+
+        Assert.assertNull(result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testSetValue01() {
+        ResourceBundleELResolver resolver = new ResourceBundleELResolver();
+        resolver.setValue(null, new Object(), new Object(), new Object());
+    }
+
+    /**
+     * Tests that a valid property is not set if base is not ResourceBundle.
+     */
+    @Test
+    public void testSetValue02() {
+        doNegativeTest(new Object(), new Object(), MethodUnderTest.SET_VALUE,
+                false);
+    }
+
+    /**
+     * Tests that an exception is thrown because the resolver is read only.
+     */
+    @Test(expected = PropertyNotWritableException.class)
+    public void testSetValue03() {
+        ResourceBundleELResolver resolver = new ResourceBundleELResolver();
+        ELContext context = new ELContextImpl();
+
+        ResourceBundle resourceBundle = new TesterResourceBundle();
+        resolver.setValue(context, resourceBundle, new Object(), new Object());
+    }
+
+    /**
+     * Tests that a null context results in an NPE as per EL Javadoc.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testIsReadOnly01() {
+        ResourceBundleELResolver resolver = new ResourceBundleELResolver();
+        resolver.isReadOnly(null, new Object(), new Object());
+    }
+
+    /**
+     * Tests that the propertyResolved is false and readOnly is false if base is
+     * not ResourceBundle.
+     */
+    @Test
+    public void testIsReadOnly02() {
+        ResourceBundleELResolver resolver = new ResourceBundleELResolver();
+        ELContext context = new ELContextImpl();
+
+        boolean result = resolver.isReadOnly(context, new Object(),
+                new Object());
+
+        Assert.assertFalse(result);
+        Assert.assertFalse(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that the readOnly is true always when the base is ResourceBundle.
+     */
+    @Test
+    public void testIsReadOnly03() {
+        ResourceBundleELResolver resolver = new ResourceBundleELResolver();
+        ELContext context = new ELContextImpl();
+
+        ResourceBundle resourceBundle = new TesterResourceBundle();
+        boolean result = resolver.isReadOnly(context, resourceBundle,
+                new Object());
+
+        Assert.assertTrue(result);
+        Assert.assertTrue(context.isPropertyResolved());
+    }
+
+    /**
+     * Tests that a valid FeatureDescriptors are not returned if base is not
+     * ResourceBundle.
+     */
+    @Test
+    public void testGetFeatureDescriptors01() {
+        ResourceBundleELResolver resolver = new ResourceBundleELResolver();
+        ELContext context = new ELContextImpl();
+
+        @SuppressWarnings("unchecked")
+        Iterator<FeatureDescriptor> result = resolver.getFeatureDescriptors(
+                context, new Object());
+
+        Assert.assertNull(result);
+    }
+
+    /**
+     * Tests that a valid FeatureDescriptors are returned.
+     */
+    @Test
+    public void testGetFeatureDescriptors02() {
+        ResourceBundleELResolver resolver = new ResourceBundleELResolver();
+        ELContext context = new ELContextImpl();
+
+        ResourceBundle resourceBundle = new TesterResourceBundle(
+                new Object[][] { { "key", "value" } });
+        @SuppressWarnings("unchecked")
+        Iterator<FeatureDescriptor> result = resolver.getFeatureDescriptors(
+                context, resourceBundle);
+
+        while (result.hasNext()) {
+            FeatureDescriptor featureDescriptor = result.next();
+            Assert.assertEquals("key", featureDescriptor.getDisplayName());
+            Assert.assertEquals("key", featureDescriptor.getName());
+            Assert.assertEquals("", featureDescriptor.getShortDescription());
+            Assert.assertFalse(featureDescriptor.isExpert());
+            Assert.assertFalse(featureDescriptor.isHidden());
+            Assert.assertTrue(featureDescriptor.isPreferred());
+            Assert.assertEquals(String.class,
+                    featureDescriptor.getValue(ELResolver.TYPE));
+            Assert.assertEquals(Boolean.TRUE, featureDescriptor
+                    .getValue(ELResolver.RESOLVABLE_AT_DESIGN_TIME));
+        }
+    }
 
     private static class TesterResourceBundle extends ListResourceBundle {
 
+        public TesterResourceBundle() {
+            this(new Object[][] { { "key1", "value1" }, { "key2", "value2" } });
+        }
+
+        public TesterResourceBundle(Object[][] contents) {
+            this.contents = contents;
+        }
+
         @Override
         protected Object[][] getContents() {
             return contents;
         }
 
-        private static final Object[][] contents = {
-            {"key1","value1"},
-            {"key2","value2"}
-        };
+        private Object[][] contents;
+    }
+
+    private void doNegativeTest(Object base, Object trigger,
+            MethodUnderTest method, boolean checkResult) {
+        ResourceBundleELResolver resolver = new ResourceBundleELResolver();
+        ELContext context = new ELContextImpl();
+
+        Object result = null;
+        switch (method) {
+        case GET_VALUE: {
+            result = resolver.getValue(context, base, trigger);
+            break;
+        }
+        case SET_VALUE: {
+            resolver.setValue(context, base, trigger, new Object());
+            break;
+        }
+        case GET_TYPE: {
+            result = resolver.getType(context, base, trigger);
+            break;
+        }
+        default: {
+            // Should never happen
+            Assert.fail("Missing case for method");
+        }
+        }
+
+        if (checkResult) {
+            Assert.assertNull(result);
+        }
+        Assert.assertFalse(context.isPropertyResolved());
+    }
+
+    private static enum MethodUnderTest {
+        GET_VALUE, SET_VALUE, GET_TYPE
     }
 }
diff --git a/java/javax/annotation/security/DeclareRoles.java b/test/javax/el/TesterBean.java
similarity index 64%
copy from java/javax/annotation/security/DeclareRoles.java
copy to test/javax/el/TesterBean.java
index d5d214a..2169d59 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/test/javax/el/TesterBean.java
@@ -5,27 +5,39 @@
  * The ASF licenses this file to You 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 javax.el;
 
+public class TesterBean {
 
-package javax.annotation.security;
+    private String name;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+    public TesterBean(String name) {
+        this.name = name;
+    }
 
- at Target({ElementType.TYPE})
- at Retention(RetentionPolicy.RUNTIME)
+    public String getName() {
+        return name;
+    }
 
-public @interface DeclareRoles {
-    public String[] value();
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String toString() {
+        return getName();
+    }
+
+    public String getValueA() throws Exception {
+        throw new Exception();
+    }
 }
diff --git a/java/javax/annotation/security/DeclareRoles.java b/test/javax/el/TesterELContext.java
similarity index 70%
copy from java/javax/annotation/security/DeclareRoles.java
copy to test/javax/el/TesterELContext.java
index d5d214a..c0fd1a2 100644
--- a/java/javax/annotation/security/DeclareRoles.java
+++ b/test/javax/el/TesterELContext.java
@@ -5,27 +5,32 @@
  * The ASF licenses this file to You 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 javax.el;
 
+public class TesterELContext extends ELContext {
 
-package javax.annotation.security;
+    @Override
+    public ELResolver getELResolver() {
+        return null;
+    }
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+    @Override
+    public FunctionMapper getFunctionMapper() {
+        return null;
+    }
 
- at Target({ElementType.TYPE})
- at Retention(RetentionPolicy.RUNTIME)
+    @Override
+    public VariableMapper getVariableMapper() {
+        return null;
+    }
 
-public @interface DeclareRoles {
-    public String[] value();
 }
diff --git a/test/org/apache/catalina/connector/TestCoyoteAdapter.java b/test/org/apache/catalina/connector/TestCoyoteAdapter.java
index 39c6e1e..cdd6fe5 100644
--- a/test/org/apache/catalina/connector/TestCoyoteAdapter.java
+++ b/test/org/apache/catalina/connector/TestCoyoteAdapter.java
@@ -80,8 +80,13 @@ public class TestCoyoteAdapter extends TomcatBaseTest {
         // Setup Tomcat instance
         Tomcat tomcat = getTomcatInstance();
 
-        // Must have a real docBase - just use temp
-        File docBase = new File(System.getProperty("java.io.tmpdir"));
+        // Must have a real docBase. Don't use java.io.tmpdir as it may not be
+        // writable.
+        File docBase = new File(getTemporaryDirectory(), "testCoyoteAdapter");
+        addDeleteOnTearDown(docBase);
+        if (!docBase.mkdirs() && !docBase.isDirectory()) {
+            Assert.fail("Failed to create: [" + docBase.toString() + "]");
+        }
 
         // Create the folder that will trigger the redirect
         File foo = new File(docBase, "foo");
diff --git a/test/org/apache/catalina/connector/TestMaxConnections.java b/test/org/apache/catalina/connector/TestMaxConnections.java
index 1b32e63..e1d52ec 100644
--- a/test/org/apache/catalina/connector/TestMaxConnections.java
+++ b/test/org/apache/catalina/connector/TestMaxConnections.java
@@ -23,8 +23,7 @@ import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import static org.junit.Assert.assertTrue;
-
+import org.junit.Assert;
 import org.junit.Test;
 
 import org.apache.catalina.Context;
@@ -33,17 +32,14 @@ import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
 
 public class TestMaxConnections extends TomcatBaseTest {
-    public static final int soTimeout = 3000;
+    private static final int MAX_CONNECTIONS = 3;
+    public static final int soTimeout = 5000;
     public static final int connectTimeout = 1000;
 
     @Test
     public void testConnector() throws Exception {
-        log.info("This test tries to create 10 connections to connector "
-                + "that has maxConnections='4'. Expect half of them to fail.");
         init();
         ConnectThread[] t = new ConnectThread[10];
-        int passcount = 0;
-        int connectfail = 0;
         for (int i=0; i<t.length; i++) {
             t[i] = new ConnectThread();
             t[i].setName("ConnectThread["+i+"]");
@@ -54,27 +50,19 @@ public class TestMaxConnections extends TomcatBaseTest {
         }
         for (int i=0; i<t.length; i++) {
             t[i].join();
-            if (t[i].passed) passcount++;
-            if (t[i].connectfailed) connectfail++;
         }
 
-        assertTrue("The number of successful requests should have been 4-5, actual "+passcount,4==passcount || 5==passcount);
-        log.info("There were [" + passcount + "] passed requests and ["
-                + connectfail + "] connection failures");
+        Assert.assertEquals(MAX_CONNECTIONS, SimpleServlet.getMaxConnections());
     }
 
     private class ConnectThread extends Thread {
-        public boolean passed = true;
-        public boolean connectfailed = false;
         @Override
         public void run() {
             try {
                 TestClient client = new TestClient();
                 client.doHttp10Request();
-            }catch (Exception x) {
-                passed = false;
-                log.info(Thread.currentThread().getName()+" Error:"+x.getMessage());
-                connectfailed = "connect timed out".equals(x.getMessage()) || "Connection refused: connect".equals(x.getMessage());
+            } catch (Exception x) {
+                // NO-OP. Some connections are expected to fail.
             }
         }
     }
@@ -89,7 +77,8 @@ public class TestMaxConnections extends TomcatBaseTest {
         tomcat.getConnector().setProperty("maxThreads", "10");
         tomcat.getConnector().setProperty("soTimeout", "20000");
         tomcat.getConnector().setProperty("keepAliveTimeout", "50000");
-        tomcat.getConnector().setProperty("maxConnections", "4");
+        tomcat.getConnector().setProperty(
+                "maxConnections", Integer.toString(MAX_CONNECTIONS));
         tomcat.getConnector().setProperty("acceptCount", "1");
         tomcat.start();
     }
@@ -116,14 +105,13 @@ public class TestMaxConnections extends TomcatBaseTest {
             // Close the connection
             disconnect();
             reset();
-            assertTrue(passed);
+            Assert.assertTrue(passed);
         }
 
         @Override
         public boolean isResponseBodyOK() {
             return true;
         }
-
     }
 
 
@@ -131,8 +119,15 @@ public class TestMaxConnections extends TomcatBaseTest {
 
         private static final long serialVersionUID = 1L;
 
+        private static int currentConnections = 0;
+        private static int maxConnections = 0;
+
         @Override
-        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        protected void service(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException {
+
+            increment();
+
             try {
                 Thread.sleep(TestMaxConnections.soTimeout*4/5);
             }catch (InterruptedException x) {
@@ -140,8 +135,24 @@ public class TestMaxConnections extends TomcatBaseTest {
             }
             resp.setContentLength(0);
             resp.flushBuffer();
+
+            decrement();
         }
 
-    }
+        private static synchronized void increment() {
+            currentConnections++;
+            if (currentConnections > maxConnections) {
+                maxConnections = currentConnections;
+            }
+        }
+
+        private static synchronized void decrement() {
+            currentConnections--;
+        }
 
+
+        public static synchronized int getMaxConnections() {
+            return maxConnections;
+        }
+    }
 }
diff --git a/test/org/apache/catalina/core/TestAsyncContextImpl.java b/test/org/apache/catalina/core/TestAsyncContextImpl.java
index b8bf718..c89373a 100644
--- a/test/org/apache/catalina/core/TestAsyncContextImpl.java
+++ b/test/org/apache/catalina/core/TestAsyncContextImpl.java
@@ -46,8 +46,6 @@ import javax.servlet.http.HttpServletResponse;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -398,15 +396,28 @@ public class TestAsyncContextImpl extends TomcatBaseTest {
     }
 
     @Test
-    public void testTimeoutListenerCompleteDispatch() throws Exception {
+    public void testTimeoutListenerCompleteNonAsyncDispatch() throws Exception {
         // Should trigger an error - can't do both
-        doTestTimeout(Boolean.TRUE, "/nonasync");
+        doTestTimeout(Boolean.TRUE, Boolean.FALSE);
     }
 
     @Test
-    public void testTimeoutListenerNoCompleteDispatch() throws Exception {
+    public void testTimeoutListenerNoCompleteNonAsyncDispatch()
+            throws Exception {
         // Should work
-        doTestTimeout(Boolean.FALSE, "/nonasync");
+        doTestTimeout(Boolean.FALSE, Boolean.FALSE);
+    }
+
+    @Test
+    public void testTimeoutListenerCompleteAsyncDispatch() throws Exception {
+        // Should trigger an error - can't do both
+        doTestTimeout(Boolean.TRUE, Boolean.TRUE);
+    }
+
+    @Test
+    public void testTimeoutListenerNoCompleteAsyncDispatch() throws Exception {
+        // Should work
+        doTestTimeout(Boolean.FALSE, Boolean.TRUE);
     }
 
     @Test
@@ -415,21 +426,24 @@ public class TestAsyncContextImpl extends TomcatBaseTest {
         doTestTimeout(null, null);
     }
 
-    private void doTestTimeout(Boolean completeOnTimeout, String dispatchUrl)
-    throws Exception {
+    private void doTestTimeout(Boolean completeOnTimeout, Boolean asyncDispatch)
+            throws Exception {
+
+        String dispatchUrl = null;
+        if (asyncDispatch != null) {
+            if (asyncDispatch.booleanValue()) {
+                dispatchUrl = "/async";
+            } else {
+                dispatchUrl = "/nonasync";
+            }
+        }
+
         // Setup Tomcat instance
         Tomcat tomcat = getTomcatInstance();
 
         // Must have a real docBase - just use temp
         File docBase = new File(System.getProperty("java.io.tmpdir"));
 
-        // Create the folder that will trigger the redirect
-        File foo = new File(docBase, "async");
-        addDeleteOnTearDown(foo);
-        if (!foo.mkdirs() && !foo.isDirectory()) {
-            fail("Unable to create async directory in docBase");
-        }
-
         Context ctx = tomcat.addContext("", docBase.getAbsolutePath());
 
         TimeoutServlet timeout =
@@ -437,13 +451,22 @@ public class TestAsyncContextImpl extends TomcatBaseTest {
 
         Wrapper wrapper = Tomcat.addServlet(ctx, "time", timeout);
         wrapper.setAsyncSupported(true);
-        ctx.addServletMapping("/async", "time");
-
-        if (dispatchUrl != null) {
-            NonAsyncServlet nonAsync = new NonAsyncServlet();
-            Tomcat.addServlet(ctx, "nonasync", nonAsync);
-            ctx.addServletMapping(dispatchUrl, "nonasync");
-        }
+        ctx.addServletMapping("/start", "time");
+
+        if (asyncDispatch != null) {
+            if (asyncDispatch.booleanValue()) {
+                AsyncStartRunnable asyncStartRunnable =
+                        new AsyncStartRunnable();
+                Wrapper async =
+                        Tomcat.addServlet(ctx, "async", asyncStartRunnable);
+                async.setAsyncSupported(true);
+                ctx.addServletMapping(dispatchUrl, "async");
+            } else {
+                NonAsyncServlet nonAsync = new NonAsyncServlet();
+                Tomcat.addServlet(ctx, "nonasync", nonAsync);
+                ctx.addServletMapping(dispatchUrl, "nonasync");
+            }
+         }
 
         ctx.addApplicationListener(new ApplicationListener(
                 TrackingRequestListener.class.getName(), false));
@@ -456,7 +479,7 @@ public class TestAsyncContextImpl extends TomcatBaseTest {
         tomcat.start();
         ByteChunk res = new ByteChunk();
         try {
-            getUrl("http://localhost:" + getPort() + "/async", res, null);
+            getUrl("http://localhost:" + getPort() + "/start", res, null);
         } catch (IOException ioe) {
             // Ignore - expected for some error conditions
         }
@@ -470,8 +493,12 @@ public class TestAsyncContextImpl extends TomcatBaseTest {
             expected.append("requestDestroyed");
         } else {
             expected.append("onTimeout-");
-            if (dispatchUrl != null) {
-                expected.append("NonAsyncServletGet-");
+            if (asyncDispatch != null) {
+                if (asyncDispatch.booleanValue()) {
+                    expected.append("onStartAsync-Runnable-");
+                } else {
+                    expected.append("NonAsyncServletGet-");
+                }
             }
             expected.append("onComplete-");
             expected.append("requestDestroyed");
@@ -487,12 +514,16 @@ public class TestAsyncContextImpl extends TomcatBaseTest {
                     TimeoutServlet.ASYNC_TIMEOUT + TIMEOUT_MARGIN +
                     REQUEST_TIME);
         } else {
-            alvGlobal.validateAccessLog(1, 200, TimeoutServlet.ASYNC_TIMEOUT,
-                    TimeoutServlet.ASYNC_TIMEOUT + TIMEOUT_MARGIN +
-                    REQUEST_TIME);
-            alv.validateAccessLog(1, 200, TimeoutServlet.ASYNC_TIMEOUT,
-                    TimeoutServlet.ASYNC_TIMEOUT + TIMEOUT_MARGIN +
-                    REQUEST_TIME);
+            long timeoutDelay = TimeoutServlet.ASYNC_TIMEOUT;
+            if (asyncDispatch != null && asyncDispatch.booleanValue() &&
+                    !completeOnTimeout.booleanValue()) {
+                // Extra timeout in this case
+                timeoutDelay += TimeoutServlet.ASYNC_TIMEOUT;
+            }
+            alvGlobal.validateAccessLog(1, 200, timeoutDelay,
+                    timeoutDelay + TIMEOUT_MARGIN + REQUEST_TIME);
+            alv.validateAccessLog(1, 200, timeoutDelay,
+                    timeoutDelay + TIMEOUT_MARGIN + REQUEST_TIME);
         }
     }
 
diff --git a/test/org/apache/catalina/core/TestStandardContextResources.java b/test/org/apache/catalina/core/TestStandardContextResources.java
index db06d8e..6067845 100644
--- a/test/org/apache/catalina/core/TestStandardContextResources.java
+++ b/test/org/apache/catalina/core/TestStandardContextResources.java
@@ -25,6 +25,7 @@ import java.net.URL;
 import java.util.Arrays;
 import java.util.List;
 
+import javax.naming.directory.DirContext;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -34,6 +35,7 @@ import javax.servlet.http.HttpServletResponse;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import org.junit.Assert;
 import org.junit.Test;
 
 import org.apache.catalina.Context;
@@ -45,6 +47,7 @@ import org.apache.catalina.startup.Constants;
 import org.apache.catalina.startup.ContextConfig;
 import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.naming.resources.ProxyDirContext;
 import org.apache.tomcat.util.buf.ByteChunk;
 
 public class TestStandardContextResources extends TomcatBaseTest {
@@ -88,6 +91,7 @@ public class TestStandardContextResources extends TomcatBaseTest {
 
         // For BZ 54391. Relative ordering is specified in resources2.jar.
         // It is not absolute-ordering, so there may be other jars in the list
+        @SuppressWarnings("unchecked")
         List<String> orderedLibs = (List<String>) ctx.getServletContext()
                 .getAttribute(ServletContext.ORDERED_LIBS);
         if (orderedLibs.size() > 2) {
@@ -225,6 +229,36 @@ public class TestStandardContextResources extends TomcatBaseTest {
                 "<p>resourceE.jsp in the web application</p>");
     }
 
+
+    @Test
+    public void testResourceCaching() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+
+        File appDir = new File("test/webapp-3.0-fragments");
+        // app dir is relative to server home
+        StandardContext ctx = (StandardContext) tomcat.addWebapp(
+                null, "/test", appDir.getAbsolutePath());
+        ctx.setCachingAllowed(false);
+
+        tomcat.start();
+
+        DirContext resources = ctx.getResources();
+
+        Assert.assertTrue(resources instanceof ProxyDirContext);
+
+        ProxyDirContext proxyResources = (ProxyDirContext) resources;
+
+        // Caching should be disabled
+        Assert.assertNull(proxyResources.getCache());
+
+        ctx.stop();
+        ctx.start();
+
+        // Caching should still be disabled
+        Assert.assertNull(proxyResources.getCache());
+    }
+
+
     /**
      * A servlet that prints the requested resource. The path to the requested
      * resource is passed as a parameter, <code>path</code>.
diff --git a/test/org/apache/catalina/core/TestSwallowAbortedUploads.java b/test/org/apache/catalina/core/TestSwallowAbortedUploads.java
index de83c83..ab1b814 100644
--- a/test/org/apache/catalina/core/TestSwallowAbortedUploads.java
+++ b/test/org/apache/catalina/core/TestSwallowAbortedUploads.java
@@ -224,7 +224,7 @@ public class TestSwallowAbortedUploads extends TomcatBaseTest {
         private static final String URI = "/uploadAborted";
         private static final String servletName = "uploadAborted";
         private static final int limitSize = 100;
-        private static final int hugeSize = 400000;
+        private static final int hugeSize = 2000000;
 
         private boolean init;
         private Context context;
@@ -345,7 +345,7 @@ public class TestSwallowAbortedUploads extends TomcatBaseTest {
 
         private static final String URI = "/uploadAborted";
         private static final String servletName = "uploadAborted";
-        private static final int hugeSize = 400000;
+        private static final int hugeSize = 2000000;
 
         private boolean init;
         private Context context;
diff --git a/test/org/apache/catalina/core/TesterContext.java b/test/org/apache/catalina/core/TesterContext.java
index 38d4bad..8f722cd 100644
--- a/test/org/apache/catalina/core/TesterContext.java
+++ b/test/org/apache/catalina/core/TesterContext.java
@@ -58,6 +58,7 @@ import org.apache.catalina.deploy.NamingResources;
 import org.apache.catalina.deploy.SecurityConstraint;
 import org.apache.catalina.util.CharsetMapper;
 import org.apache.juli.logging.Log;
+import org.apache.tomcat.InstanceManager;
 import org.apache.tomcat.JarScanner;
 import org.apache.tomcat.util.http.mapper.Mapper;
 
@@ -1182,4 +1183,14 @@ public class TesterContext implements Context {
             ServletSecurityElement servletSecurityElement) {
         return null;
     }
+
+    @Override
+    public InstanceManager getInstanceManager() {
+        return null;
+    }
+
+    @Override
+    public void setInstanceManager(InstanceManager instanceManager) {
+        // NO-OP
+    }
 }
diff --git a/test/org/apache/catalina/filters/TestCsrfPreventionFilter.java b/test/org/apache/catalina/filters/TestCsrfPreventionFilter.java
index 41d2007..6949ec0 100644
--- a/test/org/apache/catalina/filters/TestCsrfPreventionFilter.java
+++ b/test/org/apache/catalina/filters/TestCsrfPreventionFilter.java
@@ -83,6 +83,7 @@ public class TestCsrfPreventionFilter extends TomcatBaseTest {
         ByteArrayInputStream bais =
             new ByteArrayInputStream(baos.toByteArray());
         ObjectInputStream ois = new ObjectInputStream(bais);
+        @SuppressWarnings("unchecked")
         LruCache<String> cache2 = (LruCache<String>) ois.readObject();
 
         cache2.add("key7");
diff --git a/test/org/apache/catalina/filters/TesterServletContext.java b/test/org/apache/catalina/filters/TesterServletContext.java
index fec4670..a66c4e2 100644
--- a/test/org/apache/catalina/filters/TesterServletContext.java
+++ b/test/org/apache/catalina/filters/TesterServletContext.java
@@ -36,6 +36,10 @@ import javax.servlet.SessionCookieConfig;
 import javax.servlet.SessionTrackingMode;
 import javax.servlet.descriptor.JspConfigDescriptor;
 
+import org.apache.catalina.core.ApplicationFilterRegistration;
+import org.apache.catalina.core.TesterContext;
+import org.apache.catalina.deploy.FilterDef;
+
 public class TesterServletContext implements ServletContext {
 
     @Override
@@ -137,8 +141,7 @@ public class TesterServletContext implements ServletContext {
 
     @Override
     public String getInitParameter(String name) {
-
-        throw new RuntimeException("Not implemented");
+        return null;
     }
 
     @Override
@@ -230,7 +233,8 @@ public class TesterServletContext implements ServletContext {
     @Override
     public javax.servlet.FilterRegistration.Dynamic addFilter(
             String filterName, Filter filter) {
-        throw new RuntimeException("Not implemented");
+        return new ApplicationFilterRegistration(
+                new FilterDef(), new TesterContext());
     }
 
     @Override
diff --git a/test/org/apache/catalina/startup/TestContextConfig.java b/test/org/apache/catalina/startup/TestContextConfig.java
index 91f4d71..df0963d 100644
--- a/test/org/apache/catalina/startup/TestContextConfig.java
+++ b/test/org/apache/catalina/startup/TestContextConfig.java
@@ -146,6 +146,19 @@ public class TestContextConfig extends TomcatBaseTest {
                 "envEntry1: 1 envEntry2: 2 envEntry3: 33 envEntry4: 4");
     }
 
+    @Test
+    public void testBug55210() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+
+        File appDir = new File("test/webapp-3.0-fragments");
+        tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
+
+        tomcat.start();
+
+        assertPageContains("/test/TesterServlet1", "OK");
+        assertPageContains("/test/TesterServlet2", "OK");
+    }
+
     private static class CustomDefaultServletSCI
             implements ServletContainerInitializer {
 
diff --git a/test/org/apache/catalina/startup/TestTomcat.java b/test/org/apache/catalina/startup/TestTomcat.java
index 4c11f1f..f28e954 100644
--- a/test/org/apache/catalina/startup/TestTomcat.java
+++ b/test/org/apache/catalina/startup/TestTomcat.java
@@ -40,11 +40,13 @@ import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import org.junit.Test;
 
+import org.apache.catalina.core.StandardContext;
 import org.apache.catalina.deploy.ContextEnvironment;
 import org.apache.catalina.deploy.ContextResourceLink;
 import org.apache.catalina.realm.GenericPrincipal;
@@ -447,4 +449,41 @@ public class TestTomcat extends TomcatBaseTest {
 
         assertEquals(1, initCount.getCallCount());
     }
+
+    @Test
+    public void testGetWebappConfigFileFromDirectory() {
+        Tomcat tomcat = new Tomcat();
+        assertNotNull(tomcat.getWebappConfigFile("test/deployment/dirContext", ""));
+    }
+
+    @Test
+    public void testGetWebappConfigFileFromDirectoryNegative() {
+        Tomcat tomcat = new Tomcat();
+        assertNull(tomcat.getWebappConfigFile("test/deployment/dirNoContext", ""));
+    }
+
+    @Test
+    public void testGetWebappConfigFileFromJar() {
+        Tomcat tomcat = new Tomcat();
+        assertNotNull(tomcat.getWebappConfigFile("test/deployment/context.war", ""));
+    }
+
+    @Test
+    public void testGetWebappConfigFileFromJarNegative() {
+        Tomcat tomcat = new Tomcat();
+        assertNull(tomcat.getWebappConfigFile("test/deployment/noContext.war", ""));
+    }
+
+    @Test
+    public void testBug51526() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+
+        File appFile = new File("test/deployment/context.war");
+        StandardContext context = (StandardContext) tomcat.addWebapp(null, "/test",
+                appFile.getAbsolutePath());
+
+        tomcat.start();
+
+        assertEquals("WAR_CONTEXT", context.getSessionCookieName());
+    }
 }
diff --git a/java/javax/annotation/Generated.java b/test/org/apache/catalina/startup/TesterServletContainerInitializer1.java
similarity index 55%
copy from java/javax/annotation/Generated.java
copy to test/org/apache/catalina/startup/TesterServletContainerInitializer1.java
index d4721db..d5f883b 100644
--- a/java/javax/annotation/Generated.java
+++ b/test/org/apache/catalina/startup/TesterServletContainerInitializer1.java
@@ -5,31 +5,34 @@
  * The ASF licenses this file to You 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.apache.catalina.startup;
 
+import java.util.Set;
 
-package javax.annotation;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+public class TesterServletContainerInitializer1 implements
+        ServletContainerInitializer {
 
- at Target({ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, 
-    ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, 
-    ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE})
- at Retention(RetentionPolicy.SOURCE)
+    @Override
+    public void onStartup(Set<Class<?>> c, ServletContext ctx)
+            throws ServletException {
+        Servlet s = new TesterServlet();
+        ServletRegistration.Dynamic r = ctx.addServlet("TesterServlet1", s);
+        r.addMapping("/TesterServlet1");
+    }
 
-public @interface Generated {
-    public String[] value();
-    public String date() default "";
-    public String comment() default "";
 }
diff --git a/java/javax/annotation/Generated.java b/test/org/apache/catalina/startup/TesterServletContainerInitializer2.java
similarity index 55%
copy from java/javax/annotation/Generated.java
copy to test/org/apache/catalina/startup/TesterServletContainerInitializer2.java
index d4721db..da430df 100644
--- a/java/javax/annotation/Generated.java
+++ b/test/org/apache/catalina/startup/TesterServletContainerInitializer2.java
@@ -5,31 +5,34 @@
  * The ASF licenses this file to You 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.apache.catalina.startup;
 
+import java.util.Set;
 
-package javax.annotation;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+public class TesterServletContainerInitializer2 implements
+        ServletContainerInitializer {
 
- at Target({ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, 
-    ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, 
-    ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE})
- at Retention(RetentionPolicy.SOURCE)
+    @Override
+    public void onStartup(Set<Class<?>> c, ServletContext ctx)
+            throws ServletException {
+        Servlet s = new TesterServlet();
+        ServletRegistration.Dynamic r = ctx.addServlet("TesterServlet2", s);
+        r.addMapping("/TesterServlet2");
+    }
 
-public @interface Generated {
-    public String[] value();
-    public String date() default "";
-    public String comment() default "";
 }
diff --git a/test/org/apache/catalina/websocket/TestWebSocket.java b/test/org/apache/catalina/websocket/TestWebSocket.java
index b24b69c..917ae73 100644
--- a/test/org/apache/catalina/websocket/TestWebSocket.java
+++ b/test/org/apache/catalina/websocket/TestWebSocket.java
@@ -17,7 +17,6 @@
 package org.apache.catalina.websocket;
 
 import java.io.BufferedReader;
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -45,7 +44,9 @@ import static org.junit.Assert.assertTrue;
 import org.junit.Test;
 
 import org.apache.catalina.Context;
+import org.apache.catalina.deploy.ApplicationListener;
 import org.apache.catalina.deploy.ContextEnvironment;
+import org.apache.catalina.servlets.DefaultServlet;
 import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
 import org.apache.catalina.util.Base64;
@@ -53,7 +54,12 @@ import org.apache.tomcat.util.buf.B2CConverter;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.C2BConverter;
 import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.websocket.TesterEchoServer;
 
+/**
+ * @deprecated  Will be removed in Tomcat 8.0.x.
+ */
+ at Deprecated
 public class TestWebSocket extends TomcatBaseTest {
 
     private static final String CRLF = "\r\n";
@@ -64,16 +70,21 @@ public class TestWebSocket extends TomcatBaseTest {
     @Test
     public void testSimple() throws Exception {
         Tomcat tomcat = getTomcatInstance();
-        File appDir = new File(getBuildDirectory(), "webapps/examples");
-        tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath());
+        // Must have a real docBase - just use temp
+        Context ctx = tomcat.addContext("",
+                System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
 
-        tomcat.start();
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
 
-        WebSocketClient client= new WebSocketClient(getPort());
+        tomcat.start();
 
+        WebSocketClient client = new WebSocketClient(getPort());
 
         // Send the WebSocket handshake
-        client.writer.write("GET /examples/websocket/echoStream HTTP/1.1" + CRLF);
+        client.writer.write("GET " + TesterEchoServer.Config.PATH_BASIC + " HTTP/1.1" + CRLF);
         client.writer.write("Host: foo" + CRLF);
         client.writer.write("Upgrade: websocket" + CRLF);
         client.writer.write("Connection: keep-alive, upgrade" + CRLF);
@@ -105,14 +116,21 @@ public class TestWebSocket extends TomcatBaseTest {
     @Test
     public void testDetectWrongVersion() throws Exception {
         Tomcat tomcat = getTomcatInstance();
-        File appDir = new File(getBuildDirectory(), "webapps/examples");
-        tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath());
+        // Must have a real docBase - just use temp
+        Context ctx = tomcat.addContext("",
+                System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
+
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
 
         tomcat.start();
+
         WebSocketClient client= new WebSocketClient(getPort());
 
         // Send the WebSocket handshake
-        client.writer.write("GET /examples/websocket/echoStream HTTP/1.1" + CRLF);
+        client.writer.write("GET " + TesterEchoServer.Config.PATH_BASIC + " HTTP/1.1" + CRLF);
         client.writer.write("Host: foo" + CRLF);
         client.writer.write("Upgrade: websocket" + CRLF);
         client.writer.write("Connection: upgrade" + CRLF);
@@ -141,15 +159,21 @@ public class TestWebSocket extends TomcatBaseTest {
     @Test
     public void testNoConnection() throws Exception {
         Tomcat tomcat = getTomcatInstance();
-        File appDir = new File(getBuildDirectory(), "webapps/examples");
-        tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath());
+        // Must have a real docBase - just use temp
+        Context ctx = tomcat.addContext("",
+                System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
+
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
 
         tomcat.start();
-        WebSocketClient client= new WebSocketClient(getPort());
 
+        WebSocketClient client= new WebSocketClient(getPort());
 
         // Send the WebSocket handshake
-        client.writer.write("GET /examples/websocket/echoStream HTTP/1.1" + CRLF);
+        client.writer.write("GET " + TesterEchoServer.Config.PATH_BASIC + " HTTP/1.1" + CRLF);
         client.writer.write("Host: foo" + CRLF);
         client.writer.write("Upgrade: websocket" + CRLF);
         client.writer.write("Sec-WebSocket-Version: 13" + CRLF);
@@ -157,7 +181,7 @@ public class TestWebSocket extends TomcatBaseTest {
         client.writer.write(CRLF);
         client.writer.flush();
 
-        // Make sure we got an upgrade response
+        // Make sure we got an error response
         String responseLine = client.reader.readLine();
         assertTrue(responseLine.startsWith("HTTP/1.1 400"));
 
@@ -169,14 +193,21 @@ public class TestWebSocket extends TomcatBaseTest {
     @Test
     public void testNoUpgrade() throws Exception {
         Tomcat tomcat = getTomcatInstance();
-        File appDir = new File(getBuildDirectory(), "webapps/examples");
-        tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath());
+        // Must have a real docBase - just use temp
+        Context ctx = tomcat.addContext("",
+                System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
+
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
 
         tomcat.start();
+
         WebSocketClient client= new WebSocketClient(getPort());
 
         // Send the WebSocket handshake
-        client.writer.write("GET /examples/websocket/echoStream HTTP/1.1" + CRLF);
+        client.writer.write("GET " + TesterEchoServer.Config.PATH_BASIC + " HTTP/1.1" + CRLF);
         client.writer.write("Host: foo" + CRLF);
         client.writer.write("Connection: upgrade" + CRLF);
         client.writer.write("Sec-WebSocket-Version: 13" + CRLF);
@@ -184,9 +215,11 @@ public class TestWebSocket extends TomcatBaseTest {
         client.writer.write(CRLF);
         client.writer.flush();
 
-        // Make sure we got an upgrade response
+        // Make sure we got an error response
+        // No upgrade means it is not treated an as upgrade request so a 404 is
+        // generated when the request reaches the Default Servlet.s
         String responseLine = client.reader.readLine();
-        assertTrue(responseLine.startsWith("HTTP/1.1 400"));
+        assertTrue(responseLine.startsWith("HTTP/1.1 404"));
 
         // Finished with the socket
         client.close();
@@ -195,14 +228,21 @@ public class TestWebSocket extends TomcatBaseTest {
     @Test
     public void testKey() throws Exception {
         Tomcat tomcat = getTomcatInstance();
-        File appDir = new File(getBuildDirectory(), "webapps/examples");
-        tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath());
+        // Must have a real docBase - just use temp
+        Context ctx = tomcat.addContext("",
+                System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
+
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
 
         tomcat.start();
+
         WebSocketClient client= new WebSocketClient(getPort());
 
         // Send the WebSocket handshake
-        client.writer.write("GET /examples/websocket/echoStream HTTP/1.1" + CRLF);
+        client.writer.write("GET " + TesterEchoServer.Config.PATH_BASIC + " HTTP/1.1" + CRLF);
         client.writer.write("Host: foo" + CRLF);
         client.writer.write("Upgrade: websocket" + CRLF);
         client.writer.write("Connection: upgrade" + CRLF);
diff --git a/test/org/apache/coyote/ajp/SimpleAjpClient.java b/test/org/apache/coyote/ajp/SimpleAjpClient.java
index 1e8adc5..99227cd 100644
--- a/test/org/apache/coyote/ajp/SimpleAjpClient.java
+++ b/test/org/apache/coyote/ajp/SimpleAjpClient.java
@@ -67,6 +67,11 @@ public class SimpleAjpClient {
      * Create a message to request the given URL.
      */
     public TesterAjpMessage createForwardMessage(String url) {
+        return createForwardMessage(url, 2);
+    }
+
+    public TesterAjpMessage createForwardMessage(String url, int method) {
+
         TesterAjpMessage message = new TesterAjpMessage(AJP_PACKET_SIZE);
         message.reset();
 
@@ -78,7 +83,7 @@ public class SimpleAjpClient {
         message.appendByte(Constants.JK_AJP13_FORWARD_REQUEST);
 
         // HTTP method, GET = 2
-        message.appendByte(0x02);
+        message.appendByte(method);
 
         // Protocol
         message.appendString("http");
@@ -101,26 +106,44 @@ public class SimpleAjpClient {
         // Is ssl
         message.appendByte(0x00);
 
-        // No other headers or attributes
-        message.appendInt(0);
+        return message;
+    }
 
-        // Terminator
-        message.appendByte(0xFF);
 
-        // End the message and set the length
+    public TesterAjpMessage createBodyMessage(byte[] data) {
+
+        TesterAjpMessage message = new TesterAjpMessage(AJP_PACKET_SIZE);
+        message.reset();
+
+        // Set the header bytes
+        message.getBuffer()[0] = 0x12;
+        message.getBuffer()[1] = 0x34;
+
+        message.appendBytes(data, 0, data.length);
         message.end();
 
         return message;
     }
 
+
     /**
      * Sends an TesterAjpMessage to the server and returns the response message.
      */
-    public TesterAjpMessage sendMessage(TesterAjpMessage message)
+    public TesterAjpMessage sendMessage(TesterAjpMessage headers)
             throws IOException {
-        // Send the message
+        return sendMessage(headers, null);
+    }
+
+    public TesterAjpMessage sendMessage(TesterAjpMessage headers,
+            TesterAjpMessage body) throws IOException {
+        // Send the headers
         socket.getOutputStream().write(
-                message.getBuffer(), 0, message.getLen());
+                headers.getBuffer(), 0, headers.getLen());
+        if (body != null) {
+            // Send the body of present
+            socket.getOutputStream().write(
+                    body.getBuffer(), 0, body.getLen());
+        }
         // Read the response
         return readMessage();
     }
diff --git a/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java b/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java
index 5c4e8b2..ad4acdb 100644
--- a/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java
+++ b/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java
@@ -16,11 +16,18 @@
  */
 package org.apache.coyote.ajp;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import java.io.File;
+import java.io.IOException;
 
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Assert;
 import org.junit.Test;
 
+import org.apache.catalina.Context;
 import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
 
@@ -56,8 +63,7 @@ public class TestAbstractAjpProcessor extends TomcatBaseTest {
         tomcat.start();
 
         // Must have a real docBase - just use temp
-        org.apache.catalina.Context ctx =
-            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        Context ctx = tomcat.addContext("", System.getProperty("java.io.tmpdir"));
         Tomcat.addServlet(ctx, "helloWorld", new HelloWorldServlet());
         ctx.addServletMapping("/", "helloWorld");
 
@@ -70,6 +76,8 @@ public class TestAbstractAjpProcessor extends TomcatBaseTest {
         validateCpong(ajpClient.cping());
 
         TesterAjpMessage forwardMessage = ajpClient.createForwardMessage("/");
+        // Complete the message - no extra headers required.
+        forwardMessage.end();
 
         // Two requests
         for (int i = 0; i < 2; i++) {
@@ -90,6 +98,107 @@ public class TestAbstractAjpProcessor extends TomcatBaseTest {
         ajpClient.disconnect();
     }
 
+    @Test
+    public void testPost() throws Exception {
+        doTestPost(false, HttpServletResponse.SC_OK);
+    }
+
+
+    @Test
+    public void testPostMultipleContentLength() throws Exception {
+        // Multiple content lengths
+        doTestPost(true, HttpServletResponse.SC_BAD_REQUEST);
+    }
+
+
+    public void doTestPost(boolean multipleCL, int expectedStatus) throws Exception {
+
+        Tomcat tomcat = getTomcatInstance();
+
+        // Use the normal Tomcat ROOT context
+        File root = new File("test/webapp-3.0");
+        tomcat.addWebapp("", root.getAbsolutePath());
+
+        tomcat.start();
+
+        SimpleAjpClient ajpClient = new SimpleAjpClient();
+        ajpClient.setPort(getPort());
+        ajpClient.connect();
+
+        validateCpong(ajpClient.cping());
+
+        TesterAjpMessage forwardMessage =
+                ajpClient.createForwardMessage("/echo-params.jsp", 4);
+        forwardMessage.addHeader(0xA008, "9");
+        if (multipleCL) {
+            forwardMessage.addHeader(0xA008, "99");
+        }
+        forwardMessage.addHeader(0xA007, "application/x-www-form-urlencoded");
+        forwardMessage.end();
+
+        TesterAjpMessage bodyMessage =
+                ajpClient.createBodyMessage("test=data".getBytes());
+
+        TesterAjpMessage responseHeaders =
+                ajpClient.sendMessage(forwardMessage, bodyMessage);
+
+        validateResponseHeaders(responseHeaders, expectedStatus);
+        if (expectedStatus == HttpServletResponse.SC_OK) {
+            // Expect 3 messages: headers, body, end for a valid request
+            TesterAjpMessage responseBody = ajpClient.readMessage();
+            validateResponseBody(responseBody, "test - data");
+            validateResponseEnd(ajpClient.readMessage(), true);
+
+            // Double check the connection is still open
+            validateCpong(ajpClient.cping());
+        } else {
+            // Expect 2 messages: headers, end for an invalid request
+            validateResponseEnd(ajpClient.readMessage(), false);
+        }
+
+
+        ajpClient.disconnect();
+    }
+
+
+    /*
+     * Bug 55453
+     */
+    @Test
+    public void test304WithBody() throws Exception {
+
+        Tomcat tomcat = getTomcatInstance();
+
+        // Must have a real docBase - just use temp
+        Context ctx = tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        Tomcat.addServlet(ctx, "bug55453", new Tester304WithBodyServlet());
+        ctx.addServletMapping("/", "bug55453");
+
+        tomcat.start();
+
+        SimpleAjpClient ajpClient = new SimpleAjpClient();
+        ajpClient.setPort(getPort());
+        ajpClient.connect();
+
+        validateCpong(ajpClient.cping());
+
+        TesterAjpMessage forwardMessage = ajpClient.createForwardMessage("/");
+        forwardMessage.end();
+
+        TesterAjpMessage responseHeaders =
+                ajpClient.sendMessage(forwardMessage, null);
+
+        // Expect 2 messages: headers, end
+        validateResponseHeaders(responseHeaders, 304);
+        validateResponseEnd(ajpClient.readMessage(), true);
+
+        // Double check the connection is still open
+        validateCpong(ajpClient.cping());
+
+        ajpClient.disconnect();
+    }
+
+
     /**
      * Process response header packet and checks the status. Any other data is
      * ignored.
@@ -97,20 +206,20 @@ public class TestAbstractAjpProcessor extends TomcatBaseTest {
     private void validateResponseHeaders(TesterAjpMessage message,
             int expectedStatus) throws Exception {
         // First two bytes should always be AB
-        assertEquals((byte) 'A', message.buf[0]);
-        assertEquals((byte) 'B', message.buf[1]);
+        Assert.assertEquals((byte) 'A', message.buf[0]);
+        Assert.assertEquals((byte) 'B', message.buf[1]);
 
         // Set the start position and read the length
         message.processHeader(false);
 
         // Check the length
-        assertTrue(message.len > 0);
+        Assert.assertTrue(message.len > 0);
 
         // Should be a header message
-        assertEquals(0x04, message.readByte());
+        Assert.assertEquals(0x04, message.readByte());
 
         // Check status
-        assertEquals(expectedStatus, message.readInt());
+        Assert.assertEquals(expectedStatus, message.readInt());
 
         // Read the status message
         message.readString();
@@ -132,51 +241,66 @@ public class TestAbstractAjpProcessor extends TomcatBaseTest {
      */
     private void validateResponseBody(TesterAjpMessage message,
             String expectedBody) throws Exception {
-        assertEquals((byte) 'A', message.buf[0]);
-        assertEquals((byte) 'B', message.buf[1]);
+
+        Assert.assertEquals((byte) 'A', message.buf[0]);
+        Assert.assertEquals((byte) 'B', message.buf[1]);
 
         // Set the start position and read the length
         message.processHeader(false);
 
         // Should be a body chunk message
-        assertEquals(0x03, message.readByte());
+        Assert.assertEquals(0x03, message.readByte());
 
         int len = message.readInt();
-        assertTrue(len > 0);
+        Assert.assertTrue(len > 0);
         String body = message.readString(len);
 
-        assertEquals(expectedBody, body);
+        Assert.assertTrue(body.contains(expectedBody));
     }
 
     private void validateResponseEnd(TesterAjpMessage message,
             boolean expectedReuse) {
-        assertEquals((byte) 'A', message.buf[0]);
-        assertEquals((byte) 'B', message.buf[1]);
+        Assert.assertEquals((byte) 'A', message.buf[0]);
+        Assert.assertEquals((byte) 'B', message.buf[1]);
 
         message.processHeader(false);
 
         // Should be an end body message
-        assertEquals(0x05, message.readByte());
+        Assert.assertEquals(0x05, message.readByte());
 
         // Check the length
-        assertEquals(2, message.getLen());
+        Assert.assertEquals(2, message.getLen());
 
         boolean reuse = false;
         if (message.readByte() > 0) {
             reuse = true;
         }
 
-        assertEquals(Boolean.valueOf(expectedReuse), Boolean.valueOf(reuse));
+        Assert.assertEquals(Boolean.valueOf(expectedReuse), Boolean.valueOf(reuse));
     }
 
     private void validateCpong(TesterAjpMessage message) throws Exception {
         // First two bytes should always be AB
-        assertEquals((byte) 'A', message.buf[0]);
-        assertEquals((byte) 'B', message.buf[1]);
+        Assert.assertEquals((byte) 'A', message.buf[0]);
+        Assert.assertEquals((byte) 'B', message.buf[1]);
         // CPONG should have a message length of 1
         // This effectively checks the next two bytes
-        assertEquals(1, message.getLen());
+        Assert.assertEquals(1, message.getLen());
         // Data should be the value 9
-        assertEquals(9, message.buf[4]);
+        Assert.assertEquals(9, message.buf[4]);
+    }
+
+
+    private static class Tester304WithBodyServlet extends HttpServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException {
+
+            resp.setStatus(304);
+            resp.getWriter().print("Body not permitted for 304 response");
+        }
     }
 }
diff --git a/test/org/apache/coyote/ajp/TesterAjpMessage.java b/test/org/apache/coyote/ajp/TesterAjpMessage.java
index dd25813..5c696ab 100644
--- a/test/org/apache/coyote/ajp/TesterAjpMessage.java
+++ b/test/org/apache/coyote/ajp/TesterAjpMessage.java
@@ -16,6 +16,9 @@
  */
 package org.apache.coyote.ajp;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Extends {@link AjpMessage} to provide additional methods for reading from the
  * message.
@@ -24,6 +27,9 @@ package org.apache.coyote.ajp;
  */
 public class TesterAjpMessage extends AjpMessage {
 
+    private final List<Header> headers = new ArrayList<Header>();
+
+
     public TesterAjpMessage(int packetSize) {
         super(packetSize);
     }
@@ -68,8 +74,29 @@ public class TesterAjpMessage extends AjpMessage {
         }
     }
 
+
+    public void addHeader(int code, String value) {
+        headers.add(new Header(code, value));
+    }
+
+
+    public void addHeader(String name, String value) {
+        headers.add(new Header(name, value));
+    }
+
+
     @Override
     public void end() {
+        // Add the header count
+        appendInt(headers.size());
+
+        for (Header header : headers) {
+            header.append(this);
+        }
+
+        // Terminator
+        appendByte(0xFF);
+
         len = pos;
         int dLen = len - 4;
 
@@ -80,4 +107,39 @@ public class TesterAjpMessage extends AjpMessage {
     }
 
 
+    @Override
+    public void reset() {
+        super.reset();
+        headers.clear();
+    }
+
+
+
+
+    private static class Header {
+        private final int code;
+        private final String name;
+        private final String value;
+
+        public Header(int code, String value) {
+            this.code = code;
+            this.name = null;
+            this.value = value;
+        }
+
+        public Header(String name, String value) {
+            this.code = 0;
+            this.name = name;
+            this.value = value;
+        }
+
+        public void append(TesterAjpMessage message) {
+            if (code == 0) {
+                message.appendString(name);
+            } else {
+                message.appendInt(code);
+            }
+            message.appendString(value);
+        }
+    }
 }
diff --git a/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java b/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java
index ffa6e7c..a650c11 100644
--- a/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java
+++ b/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java
@@ -101,6 +101,54 @@ public class TestAbstractHttp11Processor extends TomcatBaseTest {
 
 
     @Test
+    public void testWithTEChunked() throws Exception {
+        doTestWithTEChunked(false);
+    }
+
+
+    @Test
+    public void testWithTEChunkedWithCL() throws Exception {
+        // Should be ignored
+        doTestWithTEChunked(true);
+    }
+
+
+    private void doTestWithTEChunked(boolean withCL)
+            throws Exception {
+
+        Tomcat tomcat = getTomcatInstance();
+
+        // Use the normal Tomcat ROOT context
+        File root = new File("test/webapp-3.0");
+        tomcat.addWebapp("", root.getAbsolutePath());
+
+        tomcat.start();
+
+        String request =
+            "POST /echo-params.jsp HTTP/1.1" + SimpleHttpClient.CRLF +
+            "Host: any" + SimpleHttpClient.CRLF +
+            (withCL ? "Content-length: 1" + SimpleHttpClient.CRLF : "") +
+            "Transfer-encoding: chunked" + SimpleHttpClient.CRLF +
+            "Content-Type: application/x-www-form-urlencoded" +
+                    SimpleHttpClient.CRLF +
+            "Connection: close" + SimpleHttpClient.CRLF +
+            SimpleHttpClient.CRLF +
+            "9" + SimpleHttpClient.CRLF +
+            "test=data" + SimpleHttpClient.CRLF +
+            "0" + SimpleHttpClient.CRLF +
+            SimpleHttpClient.CRLF;
+
+        Client client = new Client(tomcat.getConnector().getLocalPort());
+        client.setRequest(new String[] {request});
+
+        client.connect();
+        client.processRequest();
+        assertTrue(client.isResponse200());
+        assertTrue(client.getResponseBody().contains("test - data"));
+    }
+
+
+    @Test
     public void testWithTEIdentity() throws Exception {
         Tomcat tomcat = getTomcatInstance();
 
diff --git a/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java b/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
index a98700d..c827127 100644
--- a/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
+++ b/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
@@ -41,6 +41,7 @@ import org.apache.catalina.startup.TomcatBaseTest;
 public class TestChunkedInputFilter extends TomcatBaseTest {
 
     private static final String LF = "\n";
+    private static final int EXT_SIZE_LIMIT = 10;
 
     @Test
     public void testChunkHeaderCRLF() throws Exception {
@@ -198,10 +199,80 @@ public class TestChunkedInputFilter extends TomcatBaseTest {
         client.connect();
         client.processRequest();
         // Expected to fail because the trailers are longer
-        // than the default limit of 8Kb
+        // than the set limit of 10 bytes
         assertTrue(client.isResponse500());
     }
 
+
+    @Test
+    public void testExtensionSizeLimitOneBelow() throws Exception {
+        doTestExtensionSizeLimit(EXT_SIZE_LIMIT - 1, true);
+    }
+
+
+    @Test
+    public void testExtensionSizeLimitExact() throws Exception {
+        doTestExtensionSizeLimit(EXT_SIZE_LIMIT, true);
+    }
+
+
+    @Test
+    public void testExtensionSizeLimitOneOver() throws Exception {
+        doTestExtensionSizeLimit(EXT_SIZE_LIMIT + 1, false);
+    }
+
+
+    private void doTestExtensionSizeLimit(int len, boolean ok) throws Exception {
+        // Setup Tomcat instance
+        Tomcat tomcat = getTomcatInstance();
+
+        tomcat.getConnector().setProperty(
+                "maxExtensionSize", Integer.toString(EXT_SIZE_LIMIT));
+
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+
+        Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet());
+        ctx.addServletMapping("/", "servlet");
+
+        tomcat.start();
+
+        String extName = ";foo=";
+        StringBuilder extValue = new StringBuilder(len);
+        for (int i = 0; i < (len - extName.length()); i++) {
+            extValue.append("x");
+        }
+
+        String[] request = new String[]{
+            "POST /echo-params.jsp HTTP/1.1" + SimpleHttpClient.CRLF +
+            "Host: any" + SimpleHttpClient.CRLF +
+            "Transfer-encoding: chunked" + SimpleHttpClient.CRLF +
+            "Content-Type: application/x-www-form-urlencoded" +
+                    SimpleHttpClient.CRLF +
+            "Connection: close" + SimpleHttpClient.CRLF +
+            SimpleHttpClient.CRLF +
+            "3" + extName + extValue.toString() + SimpleHttpClient.CRLF +
+            "a=0" + SimpleHttpClient.CRLF +
+            "4" + SimpleHttpClient.CRLF +
+            "&b=1" + SimpleHttpClient.CRLF +
+            "0" + SimpleHttpClient.CRLF +
+            SimpleHttpClient.CRLF };
+
+        TrailerClient client =
+                new TrailerClient(tomcat.getConnector().getLocalPort());
+        client.setRequest(request);
+
+        client.connect();
+        client.processRequest();
+
+        if (ok) {
+            assertTrue(client.isResponse200());
+        } else {
+            assertTrue(client.isResponse500());
+        }
+    }
+
     @Test
     public void testNoTrailingHeaders() throws Exception {
         // Setup Tomcat instance
diff --git a/test/org/apache/jasper/compiler/TestParser.java b/test/org/apache/jasper/compiler/TestParser.java
index a0bc82f..553e47e 100644
--- a/test/org/apache/jasper/compiler/TestParser.java
+++ b/test/org/apache/jasper/compiler/TestParser.java
@@ -24,6 +24,7 @@ import java.util.List;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import org.junit.Assert;
 import org.junit.Test;
 
 import org.apache.catalina.startup.Tomcat;
@@ -312,6 +313,27 @@ public class TestParser extends TomcatBaseTest {
         assertEcho(result, "02 - <p>Foo</p><%");
     }
 
+    @Test
+    public void testBug55198() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+
+        File appDir = new File("test/webapp-3.0");
+        // app dir is relative to server home
+        tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
+
+        tomcat.start();
+
+        ByteChunk res = getUrl("http://localhost:" + getPort() +
+                "/test/bug5nnnn/bug55198.jsp");
+
+        String result = res.toString();
+
+        Assert.assertTrue(result.contains(""bar"") ||
+                result.contains(""bar""));
+        Assert.assertTrue(result.contains(""foo"") ||
+                result.contains(""foo""));
+    }
+
     /** Assertion for text printed by tags:echo */
     private static void assertEcho(String result, String expected) {
         assertTrue(result.indexOf("<p>" + expected + "</p>") > 0);
diff --git a/test/org/apache/tomcat/util/http/TestParameters.java b/test/org/apache/tomcat/util/http/TestParameters.java
index cdfff9f..7bd95e1 100644
--- a/test/org/apache/tomcat/util/http/TestParameters.java
+++ b/test/org/apache/tomcat/util/http/TestParameters.java
@@ -207,8 +207,8 @@ public class TestParameters {
 
         names = p.getParameterNames();
         assertTrue(names.hasMoreElements());
-        assertEquals("foo2", names.nextElement());
         assertEquals("foo1", names.nextElement());
+        assertEquals("foo2", names.nextElement());
         assertFalse(names.hasMoreElements());
 
         values = p.getParameterValues("foo1");
@@ -231,8 +231,8 @@ public class TestParameters {
         // Check current parameters remain unaffected
         names = p.getParameterNames();
         assertTrue(names.hasMoreElements());
-        assertEquals("foo2", names.nextElement());
         assertEquals("foo1", names.nextElement());
+        assertEquals("foo2", names.nextElement());
         assertFalse(names.hasMoreElements());
 
         values = p.getParameterValues("foo1");
diff --git a/test/org/apache/tomcat/util/http/parser/TestMediaType.java b/test/org/apache/tomcat/util/http/parser/TestMediaType.java
index cc271a5..b564ed8 100644
--- a/test/org/apache/tomcat/util/http/parser/TestMediaType.java
+++ b/test/org/apache/tomcat/util/http/parser/TestMediaType.java
@@ -212,6 +212,25 @@ public class TestMediaType {
     }
 
 
+    @Test
+    public void testBug55454() throws IOException {
+        String input = "text/html;;charset=UTF-8";
+
+        StringReader sr = new StringReader(input);
+        MediaType m = HttpParser.parseMediaType(sr);
+
+        assertEquals("text", m.getType());
+        assertEquals("html", m.getSubtype());
+
+        assertTrue(m.getParameterCount() == 1);
+
+        assertEquals("UTF-8", m.getParameterValue("charset"));
+        assertEquals("UTF-8", m.getCharset());
+
+        assertEquals("text/html; charset=UTF-8", m.toString());
+    }
+
+
     private void doTest(Parameter... parameters) throws IOException {
         for (String lws : LWS_VALUES) {
             doTest(lws, parameters);
diff --git a/test/org/apache/tomcat/util/net/TesterSupport.java b/test/org/apache/tomcat/util/net/TesterSupport.java
index 7ff1272..5a90768 100644
--- a/test/org/apache/tomcat/util/net/TesterSupport.java
+++ b/test/org/apache/tomcat/util/net/TesterSupport.java
@@ -22,6 +22,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.InetAddress;
 import java.net.Socket;
+import java.net.URISyntaxException;
 import java.net.UnknownHostException;
 import java.security.KeyManagementException;
 import java.security.KeyStore;
@@ -77,25 +78,32 @@ public final class TesterSupport {
         RFC_5746_SUPPORTED = result;
     }
 
-    protected static void initSsl(Tomcat tomcat) {
+    public static void initSsl(Tomcat tomcat) {
         initSsl(tomcat, "localhost.jks", null, null);
     }
 
     protected static void initSsl(Tomcat tomcat, String keystore,
             String keystorePass, String keyPass) {
 
+        ClassLoader cl = TesterSupport.class.getClassLoader();
+
         String protocol = tomcat.getConnector().getProtocolHandlerClassName();
         if (protocol.indexOf("Apr") == -1) {
             Connector connector = tomcat.getConnector();
             connector.setProperty("sslProtocol", "tls");
-            File keystoreFile =
-                new File("test/org/apache/tomcat/util/net/" + keystore);
+            
+            java.net.URL keyStoreUrl =
+                    cl.getResource("org/apache/tomcat/util/net/" + keystore);
+            File keystoreFile = toFile(keyStoreUrl);
             connector.setAttribute("keystoreFile",
                     keystoreFile.getAbsolutePath());
-            File truststoreFile = new File(
-                    "test/org/apache/tomcat/util/net/ca.jks");
+            
+            java.net.URL truststoreUrl =
+                    cl.getResource("org/apache/tomcat/util/net/ca.jks");
+            File truststoreFile = toFile(truststoreUrl);
             connector.setAttribute("truststoreFile",
                     truststoreFile.getAbsolutePath());
+            
             if (keystorePass != null) {
                 connector.setAttribute("keystorePass", keystorePass);
             }
@@ -103,23 +111,34 @@ public final class TesterSupport {
                 connector.setAttribute("keyPass", keyPass);
             }
         } else {
-            File keystoreFile = new File(
-                    "test/org/apache/tomcat/util/net/localhost-cert.pem");
+            java.net.URL keyStoreUrl =
+                    cl.getResource("org/apache/tomcat/util/net/localhost-cert.pem");
+            File keystoreFile = toFile(keyStoreUrl);
             tomcat.getConnector().setAttribute("SSLCertificateFile",
                     keystoreFile.getAbsolutePath());
-            keystoreFile = new File(
-                    "test/org/apache/tomcat/util/net/localhost-key.pem");
+            
+            java.net.URL sslCertificateKeyUrl =
+                    cl.getResource("org/apache/tomcat/util/net/localhost-key.pem");
+            File sslCertificateKeyFile = toFile(sslCertificateKeyUrl);
             tomcat.getConnector().setAttribute("SSLCertificateKeyFile",
-                    keystoreFile.getAbsolutePath());
+                    sslCertificateKeyFile.getAbsolutePath());
         }
         tomcat.getConnector().setSecure(true);
         tomcat.getConnector().setProperty("SSLEnabled", "true");
     }
 
+    private static File toFile(java.net.URL url) {
+        try {
+            return new File(url.toURI());
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+    
     protected static KeyManager[] getUser1KeyManagers() throws Exception {
         KeyManagerFactory kmf = KeyManagerFactory.getInstance(
                 KeyManagerFactory.getDefaultAlgorithm());
-        kmf.init(getKeyStore("test/org/apache/tomcat/util/net/user1.jks"),
+        kmf.init(getKeyStore("org/apache/tomcat/util/net/user1.jks"),
                 "changeit".toCharArray());
         return kmf.getKeyManagers();
     }
@@ -127,7 +146,7 @@ public final class TesterSupport {
     protected static TrustManager[] getTrustManagers() throws Exception {
         TrustManagerFactory tmf = TrustManagerFactory.getInstance(
                 TrustManagerFactory.getDefaultAlgorithm());
-        tmf.init(getKeyStore("test/org/apache/tomcat/util/net/ca.jks"));
+        tmf.init(getKeyStore("org/apache/tomcat/util/net/ca.jks"));
         return tmf.getTrustManagers();
     }
 
@@ -146,7 +165,9 @@ public final class TesterSupport {
     }
 
     private static KeyStore getKeyStore(String keystore) throws Exception {
-        File keystoreFile = new File(keystore);
+        ClassLoader cl = TesterSupport.class.getClassLoader();
+        java.net.URL keystoreUrl = cl.getResource(keystore);
+        File keystoreFile = toFile(keystoreUrl);
         KeyStore ks = KeyStore.getInstance("JKS");
         InputStream is = null;
         try {
diff --git a/test/org/apache/tomcat/websocket/TestUtil.java b/test/org/apache/tomcat/websocket/TestUtil.java
new file mode 100644
index 0000000..d2dfb2a
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/TestUtil.java
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestUtil {
+
+    // Used to init SecureRandom prior to running tests
+    public static void generateMask() {
+        Util.generateMask();
+    }
+    
+    @Test
+    public void testGetMessageTypeSimple() {
+        Assert.assertEquals(
+                String.class, Util.getMessageType(new SimpleMessageHandler()));
+    }
+
+
+    @Test
+    public void testGetMessageTypeSubclass() {
+        Assert.assertEquals(String.class,
+                Util.getMessageType(new SubSimpleMessageHandler()));
+    }
+
+
+    @Test
+    public void testGetMessageTypeGenericSubclass() {
+        Assert.assertEquals(String.class,
+                Util.getMessageType(new GenericSubMessageHandler()));
+    }
+
+
+    @Test
+    public void testGetMessageTypeGenericMultipleSubclass() {
+        Assert.assertEquals(String.class,
+                Util.getMessageType(new GenericMultipleSubSubMessageHandler()));
+    }
+
+
+    @Test
+    public void testGetMessageTypeGenericMultipleSubclassSwap() {
+        Assert.assertEquals(String.class,
+                Util.getMessageType(new GenericMultipleSubSubSwapMessageHandler()));
+    }
+
+
+    @Test
+    public void testGetEncoderTypeSimple() {
+        Assert.assertEquals(
+                String.class, Util.getEncoderType(SimpleEncoder.class));
+    }
+
+
+    @Test
+    public void testGetEncoderTypeSubclass() {
+        Assert.assertEquals(String.class,
+                Util.getEncoderType(SubSimpleEncoder.class));
+    }
+
+
+    @Test
+    public void testGetEncoderTypeGenericSubclass() {
+        Assert.assertEquals(String.class,
+                Util.getEncoderType(GenericSubEncoder.class));
+    }
+
+
+    @Test
+    public void testGetEncoderTypeGenericMultipleSubclass() {
+        Assert.assertEquals(String.class,
+                Util.getEncoderType(GenericMultipleSubSubEncoder.class));
+    }
+
+
+    @Test
+    public void testGetEncoderTypeGenericMultipleSubclassSwap() {
+        Assert.assertEquals(String.class,
+                Util.getEncoderType(GenericMultipleSubSubSwapEncoder.class));
+    }
+
+
+    private static class SimpleMessageHandler
+            implements MessageHandler.Whole<String> {
+        @Override
+        public void onMessage(String message) {
+            // NO-OP
+        }
+    }
+
+
+    private static class SubSimpleMessageHandler extends SimpleMessageHandler {
+    }
+
+
+    private abstract static class GenericMessageHandler<T>
+            implements MessageHandler.Whole<T> {
+    }
+
+
+    private static class GenericSubMessageHandler
+            extends GenericMessageHandler<String>{
+
+        @Override
+        public void onMessage(String message) {
+            // NO-OP
+        }
+    }
+
+
+    private static interface Foo<T> {
+        void doSomething(T thing);
+    }
+
+
+    private abstract static class GenericMultipleMessageHandler<A,B>
+            implements MessageHandler.Whole<A>, Foo<B> {
+    }
+
+
+    private abstract static class GenericMultipleSubMessageHandler<X,Y>
+            extends GenericMultipleMessageHandler<X,Y> {
+    }
+
+
+    private static class GenericMultipleSubSubMessageHandler
+            extends GenericMultipleSubMessageHandler<String,Boolean> {
+
+        @Override
+        public void onMessage(String message) {
+            // NO-OP
+        }
+
+        @Override
+        public void doSomething(Boolean thing) {
+            // NO-OP
+        }
+    }
+
+
+    private abstract static class GenericMultipleSubSwapMessageHandler<Y,X>
+            extends GenericMultipleMessageHandler<X,Y> {
+    }
+
+
+    private static class GenericMultipleSubSubSwapMessageHandler
+            extends GenericMultipleSubSwapMessageHandler<Boolean,String> {
+
+        @Override
+        public void onMessage(String message) {
+            // NO-OP
+        }
+
+        @Override
+        public void doSomething(Boolean thing) {
+            // NO-OP
+        }
+    }
+
+
+    private static class SimpleEncoder implements Encoder.Text<String> {
+
+        @Override
+        public void init(EndpointConfig endpointConfig) {
+            // NO-OP
+        }
+
+        @Override
+        public void destroy() {
+            // NO-OP
+        }
+
+        @Override
+        public String encode(String object) throws EncodeException {
+            return null;
+        }
+    }
+
+
+    private static class SubSimpleEncoder extends SimpleEncoder {
+    }
+
+
+    private abstract static class GenericEncoder<T> implements Encoder.Text<T> {
+
+        @Override
+        public void init(EndpointConfig endpointConfig) {
+            // NO-OP
+        }
+
+        @Override
+        public void destroy() {
+            // NO-OP
+        }
+    }
+
+
+    private static class GenericSubEncoder
+            extends GenericEncoder<String>{
+
+        @Override
+        public String encode(String object) throws EncodeException {
+            return null;
+        }
+
+    }
+
+
+    private abstract static class GenericMultipleEncoder<A,B>
+            implements Encoder.Text<A>, Foo<B> {
+
+        @Override
+        public void init(EndpointConfig endpointConfig) {
+            // NO-OP
+        }
+
+        @Override
+        public void destroy() {
+            // NO-OP
+        }
+    }
+
+
+    private abstract static class GenericMultipleSubEncoder<X,Y>
+            extends GenericMultipleEncoder<X,Y> {
+    }
+
+
+    private static class GenericMultipleSubSubEncoder
+            extends GenericMultipleSubEncoder<String,Boolean> {
+
+        @Override
+        public String encode(String object) throws EncodeException {
+            return null;
+        }
+
+        @Override
+        public void doSomething(Boolean thing) {
+            // NO-OP
+        }
+
+    }
+
+
+    private abstract static class GenericMultipleSubSwapEncoder<Y,X>
+            extends GenericMultipleEncoder<X,Y> {
+    }
+
+
+    private static class GenericMultipleSubSubSwapEncoder
+            extends GenericMultipleSubSwapEncoder<Boolean,String> {
+
+        @Override
+        public String encode(String object) throws EncodeException {
+            return null;
+        }
+
+        @Override
+        public void doSomething(Boolean thing) {
+            // NO-OP
+        }
+    }
+}
diff --git a/test/org/apache/tomcat/websocket/TestWebSocketFrameClient.java b/test/org/apache/tomcat/websocket/TestWebSocketFrameClient.java
new file mode 100644
index 0000000..bc345da
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/TestWebSocketFrameClient.java
@@ -0,0 +1,95 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URL;
+import java.util.Queue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.ContainerProvider;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.deploy.ApplicationListener;
+import org.apache.catalina.servlets.DefaultServlet;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.util.net.TesterSupport;
+import org.apache.tomcat.websocket.TesterMessageCountClient.BasicText;
+import org.apache.tomcat.websocket.TesterMessageCountClient.TesterProgrammaticEndpoint;
+
+public class TestWebSocketFrameClient extends TomcatBaseTest {
+
+    @Test
+    public void testConnectToServerEndpointSSL() throws Exception {
+
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterFirehoseServer.Config.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+
+        TesterSupport.initSsl(tomcat);
+
+        tomcat.start();
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+        ClientEndpointConfig clientEndpointConfig =
+                ClientEndpointConfig.Builder.create().build();
+        URL truststoreUrl = this.getClass().getClassLoader().getResource(
+                "org/apache/tomcat/util/net/ca.jks");
+        File truststoreFile = new File(truststoreUrl.toURI());
+        clientEndpointConfig.getUserProperties().put(
+                WsWebSocketContainer.SSL_TRUSTSTORE_PROPERTY,
+                truststoreFile.getAbsolutePath());
+        Session wsSession = wsContainer.connectToServer(
+                TesterProgrammaticEndpoint.class,
+                clientEndpointConfig,
+                new URI("wss://localhost:" + getPort() +
+                        TesterFirehoseServer.Config.PATH));
+        CountDownLatch latch =
+                new CountDownLatch(TesterFirehoseServer.MESSAGE_COUNT);
+        BasicText handler = new BasicText(latch);
+        wsSession.addMessageHandler(handler);
+        wsSession.getBasicRemote().sendText("Hello");
+
+        // Ignore the latch result as the message count test below will tell us
+        // if the right number of messages arrived
+        handler.getLatch().await(TesterFirehoseServer.WAIT_TIME_MILLIS,
+                TimeUnit.MILLISECONDS);
+
+        Queue<String> messages = handler.getMessages();
+        Assert.assertEquals(
+                TesterFirehoseServer.MESSAGE_COUNT, messages.size());
+        for (String message : messages) {
+            Assert.assertEquals(TesterFirehoseServer.MESSAGE, message);
+        }
+    }
+}
diff --git a/test/org/apache/tomcat/websocket/TestWsFrame.java b/test/org/apache/tomcat/websocket/TestWsFrame.java
new file mode 100644
index 0000000..0b423ad
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/TestWsFrame.java
@@ -0,0 +1,49 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.IOException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestWsFrame {
+
+    @Test
+    public void testByteArrayToLong() throws IOException {
+        Assert.assertEquals(0L, WsFrameBase.byteArrayToLong(new byte[] { 0 }, 0, 1));
+        Assert.assertEquals(1L, WsFrameBase.byteArrayToLong(new byte[] { 1 }, 0, 1));
+        Assert.assertEquals(0xFF, WsFrameBase.byteArrayToLong(new byte[] { -1 }, 0, 1));
+        Assert.assertEquals(0xFFFF,
+                WsFrameBase.byteArrayToLong(new byte[] { -1, -1 }, 0, 2));
+        Assert.assertEquals(0xFFFFFF,
+                WsFrameBase.byteArrayToLong(new byte[] { -1, -1, -1 }, 0, 3));
+    }
+
+
+    @Test
+    public void testByteArrayToLongOffset() throws IOException {
+        Assert.assertEquals(0L, WsFrameBase.byteArrayToLong(new byte[] { 20, 0 }, 1, 1));
+        Assert.assertEquals(1L, WsFrameBase.byteArrayToLong(new byte[] { 20, 1 }, 1, 1));
+        Assert.assertEquals(0xFF, WsFrameBase.byteArrayToLong(new byte[] { 20, -1 }, 1, 1));
+        Assert.assertEquals(0xFFFF,
+                WsFrameBase.byteArrayToLong(new byte[] { 20, -1, -1 }, 1, 2));
+        Assert.assertEquals(0xFFFFFF,
+                WsFrameBase.byteArrayToLong(new byte[] { 20, -1, -1, -1 }, 1, 3));
+    }
+
+}
diff --git a/test/org/apache/tomcat/websocket/TestWsPingPongMessages.java b/test/org/apache/tomcat/websocket/TestWsPingPongMessages.java
new file mode 100644
index 0000000..f5a1db2
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/TestWsPingPongMessages.java
@@ -0,0 +1,100 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.ContainerProvider;
+import javax.websocket.PongMessage;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.deploy.ApplicationListener;
+import org.apache.catalina.servlets.DefaultServlet;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.websocket.TesterMessageCountClient.TesterEndpoint;
+import org.apache.tomcat.websocket.TesterMessageCountClient.TesterProgrammaticEndpoint;
+
+
+public class TestWsPingPongMessages extends TomcatBaseTest {
+
+    ByteBuffer applicationData = ByteBuffer.wrap(new String("mydata")
+            .getBytes());
+
+    @Test
+    public void testPingPongMessages() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx = tomcat.addContext("",
+                System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
+
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        tomcat.start();
+
+        WebSocketContainer wsContainer = ContainerProvider
+                .getWebSocketContainer();
+
+        tomcat.start();
+
+        Session wsSession = wsContainer.connectToServer(
+                TesterProgrammaticEndpoint.class, ClientEndpointConfig.Builder
+                        .create().build(), new URI("ws://localhost:"
+                        + getPort() + TesterEchoServer.Config.PATH_ASYNC));
+
+        CountDownLatch latch = new CountDownLatch(1);
+        TesterEndpoint tep = (TesterEndpoint) wsSession.getUserProperties()
+                .get("endpoint");
+        tep.setLatch(latch);
+
+        PongMessageHandler handler = new PongMessageHandler(latch);
+        wsSession.addMessageHandler(handler);
+        wsSession.getBasicRemote().sendPing(applicationData);
+
+        boolean latchResult = handler.getLatch().await(10, TimeUnit.SECONDS);
+        Assert.assertTrue(latchResult);
+        Assert.assertArrayEquals(applicationData.array(),
+                (handler.getMessages().peek()).getApplicationData().array());
+    }
+
+    public static class PongMessageHandler extends
+            TesterMessageCountClient.BasicHandler<PongMessage> {
+        public PongMessageHandler(CountDownLatch latch) {
+            super(latch);
+        }
+
+        @Override
+        public void onMessage(PongMessage message) {
+            getMessages().add(message);
+            if (getLatch() != null) {
+                getLatch().countDown();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/org/apache/tomcat/websocket/TestWsRemoteEndpoint.java b/test/org/apache/tomcat/websocket/TestWsRemoteEndpoint.java
new file mode 100644
index 0000000..cd9df6a
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/TestWsRemoteEndpoint.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.Writer;
+import java.net.URI;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ClientEndpointConfig.Builder;
+import javax.websocket.ContainerProvider;
+import javax.websocket.Endpoint;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.deploy.ApplicationListener;
+import org.apache.catalina.servlets.DefaultServlet;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.websocket.TesterMessageCountClient.AsyncHandler;
+import org.apache.tomcat.websocket.TesterMessageCountClient.AsyncText;
+import org.apache.tomcat.websocket.TesterMessageCountClient.TesterAnnotatedEndpoint;
+import org.apache.tomcat.websocket.TesterMessageCountClient.TesterEndpoint;
+import org.apache.tomcat.websocket.TesterMessageCountClient.TesterProgrammaticEndpoint;
+
+public class TestWsRemoteEndpoint extends TomcatBaseTest {
+
+    private static final String SEQUENCE = "ABCDE";
+    private static final int S_LEN = SEQUENCE.length();
+    private static final String TEST_MESSAGE_5K;
+
+    static {
+        StringBuilder sb = new StringBuilder(S_LEN * 1024);
+        for (int i = 0; i < 1024; i++) {
+            sb.append(SEQUENCE);
+        }
+        TEST_MESSAGE_5K = sb.toString();
+    }
+
+    @Test
+    public void testWriterAnnotation() throws Exception {
+        doTestWriter(TesterAnnotatedEndpoint.class);
+    }
+
+    @Test
+    public void testWriterProgrammatic() throws Exception {
+        doTestWriter(TesterProgrammaticEndpoint.class);
+    }
+
+    private void doTestWriter(Class<?> clazz) throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+
+        tomcat.start();
+
+        Session wsSession;
+        URI uri = new URI("ws://localhost:" + getPort() +
+                TesterEchoServer.Config.PATH_ASYNC);
+        if (Endpoint.class.isAssignableFrom(clazz)) {
+            @SuppressWarnings("unchecked")
+            Class<? extends Endpoint> endpointClazz =
+                    (Class<? extends Endpoint>) clazz;
+            wsSession = wsContainer.connectToServer(endpointClazz,
+                    Builder.create().build(), uri);
+        } else {
+            wsSession = wsContainer.connectToServer(clazz, uri);
+        }
+
+        CountDownLatch latch = new CountDownLatch(1);
+        TesterEndpoint tep =
+                (TesterEndpoint) wsSession.getUserProperties().get("endpoint");
+        tep.setLatch(latch);
+        AsyncHandler<?> handler = new AsyncText(latch);
+
+        wsSession.addMessageHandler(handler);
+
+        Writer w = wsSession.getBasicRemote().getSendWriter();
+
+        for (int i = 0; i < 8; i++) {
+            w.write(TEST_MESSAGE_5K);
+        }
+
+        w.close();
+
+        boolean latchResult = handler.getLatch().await(10, TimeUnit.SECONDS);
+
+        Assert.assertTrue(latchResult);
+
+        @SuppressWarnings("unchecked")
+        List<String> messages = (List<String>) handler.getMessages();
+
+        int offset = 0;
+        int i = 0;
+        for (String message : messages) {
+            // First may be a fragment
+            Assert.assertEquals(SEQUENCE.substring(offset, S_LEN),
+                    message.substring(0, S_LEN - offset));
+            i = S_LEN - offset;
+            while (i + S_LEN < message.length()) {
+                if (!SEQUENCE.equals(message.substring(i, i + S_LEN))) {
+                    Assert.fail();
+                }
+                i += S_LEN;
+            }
+            offset = message.length() - i;
+            if (!SEQUENCE.substring(0, offset).equals(message.substring(i))) {
+                Assert.fail();
+            }
+        }
+    }
+}
diff --git a/test/org/apache/tomcat/websocket/TestWsSession.java b/test/org/apache/tomcat/websocket/TestWsSession.java
new file mode 100644
index 0000000..4b08fd7
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/TestWsSession.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestWsSession {
+
+    @Test
+    public void testAppendCloseReasonWithTruncation01() {
+        doTestAppendCloseReasonWithTruncation(100);
+    }
+
+
+    @Test
+    public void testAppendCloseReasonWithTruncation02() {
+        doTestAppendCloseReasonWithTruncation(119);
+    }
+
+
+    @Test
+    public void testAppendCloseReasonWithTruncation03() {
+        doTestAppendCloseReasonWithTruncation(120);
+    }
+
+
+    @Test
+    public void testAppendCloseReasonWithTruncation04() {
+        doTestAppendCloseReasonWithTruncation(121);
+    }
+
+
+    @Test
+    public void testAppendCloseReasonWithTruncation05() {
+        doTestAppendCloseReasonWithTruncation(122);
+    }
+
+
+    @Test
+    public void testAppendCloseReasonWithTruncation06() {
+        doTestAppendCloseReasonWithTruncation(123);
+    }
+
+
+    @Test
+    public void testAppendCloseReasonWithTruncation07() {
+        doTestAppendCloseReasonWithTruncation(124);
+    }
+
+
+    @Test
+    public void testAppendCloseReasonWithTruncation08() {
+        doTestAppendCloseReasonWithTruncation(125);
+    }
+
+
+    @Test
+    public void testAppendCloseReasonWithTruncation09() {
+        doTestAppendCloseReasonWithTruncation(150);
+    }
+
+
+    private void doTestAppendCloseReasonWithTruncation(int reasonLength) {
+        StringBuilder reason = new StringBuilder(reasonLength);
+        for (int i = 0; i < reasonLength; i++) {
+            reason.append('a');
+        }
+
+        ByteBuffer buf = ByteBuffer.allocate(256);
+
+        WsSession.appendCloseReasonWithTruncation(buf, reason.toString());
+
+        // Check the position and contents
+        if (reasonLength <= 123) {
+            Assert.assertEquals(reasonLength, buf.position());
+            for (int i = 0; i < reasonLength; i++) {
+                Assert.assertEquals('a', buf.get(i));
+            }
+        } else {
+            // Must have been truncated
+            Assert.assertEquals(123, buf.position());
+            for (int i = 0; i < 120; i++) {
+                Assert.assertEquals('a', buf.get(i));
+            }
+            Assert.assertEquals(0xE2, buf.get(120) & 0xFF);
+            Assert.assertEquals(0x80, buf.get(121) & 0xFF);
+            Assert.assertEquals(0xA6, buf.get(122) & 0xFF);
+        }
+    }
+}
diff --git a/test/org/apache/tomcat/websocket/TestWsSubprotocols.java b/test/org/apache/tomcat/websocket/TestWsSubprotocols.java
new file mode 100644
index 0000000..dff517a
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/TestWsSubprotocols.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.servlet.ServletContextEvent;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.ContainerProvider;
+import javax.websocket.DeploymentException;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+
+import org.apache.catalina.Context;
+import org.apache.catalina.deploy.ApplicationListener;
+import org.apache.catalina.servlets.DefaultServlet;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.websocket.TesterMessageCountClient.TesterProgrammaticEndpoint;
+import org.apache.tomcat.websocket.server.Constants;
+import org.apache.tomcat.websocket.server.WsContextListener;
+
+public class TestWsSubprotocols extends TomcatBaseTest {
+
+    @Test
+    public void testWsSubprotocols() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx = tomcat.addContext("",
+                System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(Config.class
+                .getName(), false));
+
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        tomcat.start();
+
+        WebSocketContainer wsContainer = ContainerProvider
+                .getWebSocketContainer();
+
+        tomcat.start();
+
+        Session wsSession = wsContainer.connectToServer(
+                TesterProgrammaticEndpoint.class, ClientEndpointConfig.Builder
+                        .create().preferredSubprotocols(Arrays.asList("sp3"))
+                        .build(), new URI("ws://localhost:" + getPort()
+                        + SubProtocolsEndpoint.PATH_BASIC));
+
+        Assert.assertTrue(wsSession.isOpen());
+        if (wsSession.getNegotiatedSubprotocol() != null) {
+            Assert.assertTrue(wsSession.getNegotiatedSubprotocol().isEmpty());
+        }
+        wsSession.close();
+
+        wsSession = wsContainer.connectToServer(
+                TesterProgrammaticEndpoint.class, ClientEndpointConfig.Builder
+                        .create().preferredSubprotocols(Arrays.asList("sp2"))
+                        .build(), new URI("ws://localhost:" + getPort()
+                        + SubProtocolsEndpoint.PATH_BASIC));
+
+        Assert.assertTrue(wsSession.isOpen());
+        Assert.assertEquals("sp2", wsSession.getNegotiatedSubprotocol());
+        Assert.assertArrayEquals(new String[]{"sp1","sp2"},
+                SubProtocolsEndpoint.subprotocols.toArray(new String[2]));
+        wsSession.close();
+    }
+
+    @ServerEndpoint(value = "/echo", subprotocols = {"sp1","sp2"})
+    public static class SubProtocolsEndpoint {
+        public static String PATH_BASIC = "/echo";
+        public static List<String> subprotocols;
+
+        @OnOpen
+        public void processOpen(@SuppressWarnings("unused") Session session,
+                EndpointConfig  epc) {
+            subprotocols = ((ServerEndpointConfig)epc).getSubprotocols();
+        }
+
+    }
+
+    public static class Config extends WsContextListener {
+        @Override
+        public void contextInitialized(ServletContextEvent sce) {
+            super.contextInitialized(sce);
+            ServerContainer sc = (ServerContainer) sce.getServletContext()
+                    .getAttribute(Constants.
+                            SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
+            try {
+                sc.addEndpoint(SubProtocolsEndpoint.class);
+            } catch (DeploymentException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java b/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java
new file mode 100644
index 0000000..4f74222
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java
@@ -0,0 +1,880 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.File;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletContextEvent;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.ContainerProvider;
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.deploy.ApplicationListener;
+import org.apache.catalina.servlets.DefaultServlet;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.coyote.http11.Http11Protocol;
+import org.apache.tomcat.util.net.TesterSupport;
+import org.apache.tomcat.websocket.TesterMessageCountClient.BasicBinary;
+import org.apache.tomcat.websocket.TesterMessageCountClient.BasicHandler;
+import org.apache.tomcat.websocket.TesterMessageCountClient.BasicText;
+import org.apache.tomcat.websocket.TesterMessageCountClient.TesterEndpoint;
+import org.apache.tomcat.websocket.TesterMessageCountClient.TesterProgrammaticEndpoint;
+import org.apache.tomcat.websocket.server.Constants;
+import org.apache.tomcat.websocket.server.WsContextListener;
+
+public class TestWsWebSocketContainer extends TomcatBaseTest {
+
+    private static final String MESSAGE_STRING_1 = "qwerty";
+    private static final String MESSAGE_TEXT_4K;
+    private static final byte[] MESSAGE_BINARY_4K = new byte[4096];
+
+    private static final long TIMEOUT_MS = 5 * 1000;
+    private static final long MARGIN = 500;
+
+    static {
+        StringBuilder sb = new StringBuilder(4096);
+        for (int i = 0; i < 4096; i++) {
+            sb.append('*');
+        }
+        MESSAGE_TEXT_4K = sb.toString();
+    }
+
+
+    @Test
+    public void testConnectToServerEndpoint() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        tomcat.start();
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+        Session wsSession = wsContainer.connectToServer(
+                TesterProgrammaticEndpoint.class,
+                ClientEndpointConfig.Builder.create().build(),
+                new URI("ws://localhost:" + getPort() +
+                        TesterEchoServer.Config.PATH_ASYNC));
+        CountDownLatch latch = new CountDownLatch(1);
+        BasicText handler = new BasicText(latch);
+        wsSession.addMessageHandler(handler);
+        wsSession.getBasicRemote().sendText(MESSAGE_STRING_1);
+
+        boolean latchResult = handler.getLatch().await(10, TimeUnit.SECONDS);
+
+        Assert.assertTrue(latchResult);
+
+        Queue<String> messages = handler.getMessages();
+        Assert.assertEquals(1, messages.size());
+        Assert.assertEquals(MESSAGE_STRING_1, messages.peek());
+    }
+
+
+    @Test(expected=javax.websocket.DeploymentException.class)
+    public void testConnectToServerEndpointInvalidScheme() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
+
+        tomcat.start();
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+        wsContainer.connectToServer(TesterProgrammaticEndpoint.class,
+                ClientEndpointConfig.Builder.create().build(),
+                new URI("ftp://localhost:" + getPort() +
+                        TesterEchoServer.Config.PATH_ASYNC));
+    }
+
+
+    @Test(expected=javax.websocket.DeploymentException.class)
+    public void testConnectToServerEndpointNoHost() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
+
+        tomcat.start();
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+        wsContainer.connectToServer(TesterProgrammaticEndpoint.class,
+                ClientEndpointConfig.Builder.create().build(),
+                new URI("ws://" + TesterEchoServer.Config.PATH_ASYNC));
+    }
+
+
+    @Test
+    public void testSmallTextBufferClientTextMessage() throws Exception {
+        doBufferTest(true, false, true, false);
+    }
+
+
+    @Test
+    public void testSmallTextBufferClientBinaryMessage() throws Exception {
+        doBufferTest(true, false, false, true);
+    }
+
+
+    @Test
+    public void testSmallTextBufferServerTextMessage() throws Exception {
+        doBufferTest(true, true, true, false);
+    }
+
+
+    @Test
+    public void testSmallTextBufferServerBinaryMessage() throws Exception {
+        doBufferTest(true, true, false, true);
+    }
+
+
+    @Test
+    public void testSmallBinaryBufferClientTextMessage() throws Exception {
+        doBufferTest(false, false, true, true);
+    }
+
+
+    @Test
+    public void testSmallBinaryBufferClientBinaryMessage() throws Exception {
+        doBufferTest(false, false, false, false);
+    }
+
+
+    @Test
+    public void testSmallBinaryBufferServerTextMessage() throws Exception {
+        doBufferTest(false, true, true, true);
+    }
+
+
+    @Test
+    public void testSmallBinaryBufferServerBinaryMessage() throws Exception {
+        doBufferTest(false, true, false, false);
+    }
+
+
+    private void doBufferTest(boolean isTextBuffer, boolean isServerBuffer,
+            boolean isTextMessage, boolean pass) throws Exception {
+
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+
+        if (isServerBuffer) {
+            if (isTextBuffer) {
+                ctx.addParameter(
+                        org.apache.tomcat.websocket.server.Constants.
+                                TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM,
+                        "1024");
+            } else {
+                ctx.addParameter(
+                        org.apache.tomcat.websocket.server.Constants.
+                                BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM,
+                        "1024");
+            }
+        } else {
+            if (isTextBuffer) {
+                wsContainer.setDefaultMaxTextMessageBufferSize(1024);
+            } else {
+                wsContainer.setDefaultMaxBinaryMessageBufferSize(1024);
+            }
+        }
+
+        tomcat.start();
+
+        Session wsSession = wsContainer.connectToServer(
+                TesterProgrammaticEndpoint.class,
+                ClientEndpointConfig.Builder.create().build(),
+                        new URI("ws://localhost:" + getPort() +
+                                TesterEchoServer.Config.PATH_BASIC));
+        BasicHandler<?> handler;
+        CountDownLatch latch = new CountDownLatch(1);
+        TesterEndpoint tep =
+                (TesterEndpoint) wsSession.getUserProperties().get("endpoint");
+        tep.setLatch(latch);
+        if (isTextMessage) {
+            handler = new BasicText(latch);
+        } else {
+            handler = new BasicBinary(latch);
+        }
+
+        wsSession.addMessageHandler(handler);
+        if (isTextMessage) {
+            wsSession.getBasicRemote().sendText(MESSAGE_TEXT_4K);
+        } else {
+            wsSession.getBasicRemote().sendBinary(
+                    ByteBuffer.wrap(MESSAGE_BINARY_4K));
+        }
+
+        boolean latchResult = handler.getLatch().await(10, TimeUnit.SECONDS);
+
+        Assert.assertTrue(latchResult);
+
+        Queue<?> messages = handler.getMessages();
+        if (pass) {
+            Assert.assertEquals(1, messages.size());
+            if (isTextMessage) {
+                Assert.assertEquals(MESSAGE_TEXT_4K, messages.peek());
+            } else {
+                Assert.assertEquals(ByteBuffer.wrap(MESSAGE_BINARY_4K),
+                        messages.peek());
+            }
+        } else {
+            // When the message exceeds the buffer size, the WebSocket is
+            // closed. The endpoint ensures that the latch is cleared when the
+            // WebSocket closes. However, the session isn't marked as closed
+            // until after the onClose() method completes so there is a small
+            // window where this test could fail. Therefore, wait briefly to
+            // give the session a chance to complete the close process.
+            for (int i = 0; i < 500; i++) {
+                if (!wsSession.isOpen()) {
+                    break;
+                }
+                Thread.sleep(10);
+            }
+            Assert.assertFalse(wsSession.isOpen());
+        }
+    }
+
+
+    @Test
+    public void testWriteTimeoutClientContainer() throws Exception {
+        doTestWriteTimeoutClient(true);
+    }
+
+
+    @Test
+    public void testWriteTimeoutClientEndpoint() throws Exception {
+        doTestWriteTimeoutClient(false);
+    }
+
+
+    private void doTestWriteTimeoutClient(boolean setTimeoutOnContainer)
+            throws Exception {
+
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                BlockingConfig.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+
+        // Set the async timeout
+        if (setTimeoutOnContainer) {
+            wsContainer.setAsyncSendTimeout(TIMEOUT_MS);
+        }
+
+        tomcat.start();
+
+        Session wsSession = wsContainer.connectToServer(
+                TesterProgrammaticEndpoint.class,
+                ClientEndpointConfig.Builder.create().build(),
+                new URI("ws://localhost:" + getPort() + BlockingConfig.PATH));
+
+        if (!setTimeoutOnContainer) {
+            wsSession.getAsyncRemote().setSendTimeout(TIMEOUT_MS);
+        }
+
+        long lastSend = 0;
+
+        // Should send quickly until the network buffers fill up and then block
+        // until the timeout kicks in
+        Exception exception = null;
+        try {
+            while (true) {
+                Future<Void> f = wsSession.getAsyncRemote().sendBinary(
+                        ByteBuffer.wrap(MESSAGE_BINARY_4K));
+                lastSend = System.currentTimeMillis();
+                f.get();
+            }
+        } catch (Exception e) {
+            exception = e;
+        }
+
+        long timeout = System.currentTimeMillis() - lastSend;
+
+
+        String msg = "Time out was [" + timeout + "] ms";
+
+        // Check correct time passed
+        Assert.assertTrue(msg, timeout >= TIMEOUT_MS - MARGIN );
+
+        // Check the timeout wasn't too long
+        Assert.assertTrue(msg, timeout < TIMEOUT_MS * 2);
+
+        Assert.assertNotNull(exception);
+    }
+
+
+    @Test
+    public void testWriteTimeoutServerContainer() throws Exception {
+        doTestWriteTimeoutServer(true);
+    }
+
+
+    @Test
+    public void testWriteTimeoutServerEndpoint() throws Exception {
+        doTestWriteTimeoutServer(false);
+    }
+
+
+    private static volatile boolean timoutOnContainer = false;
+
+    private void doTestWriteTimeoutServer(boolean setTimeoutOnContainer)
+            throws Exception {
+
+        /*
+         * Note: There are all sorts of horrible uses of statics in this test
+         *       because the API uses classes and the tests really need access
+         *       to the instances which simply isn't possible.
+         */
+        timoutOnContainer = setTimeoutOnContainer;
+
+        Tomcat tomcat = getTomcatInstance();
+
+        if (getProtocol().equals(Http11Protocol.class.getName())) {
+            // This will never work for BIO
+            return;
+        }
+
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                ConstantTxConfig.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+
+        tomcat.start();
+
+        Session wsSession = wsContainer.connectToServer(
+                TesterProgrammaticEndpoint.class,
+                ClientEndpointConfig.Builder.create().build(),
+                new URI("ws://localhost:" + getPort() +
+                        ConstantTxConfig.PATH));
+
+        wsSession.addMessageHandler(new BlockingBinaryHandler());
+
+        int loops = 0;
+        while (loops < 15) {
+            Thread.sleep(1000);
+            if (!ConstantTxEndpoint.getRunning()) {
+                break;
+            }
+            loops++;
+        }
+
+        // Check the right exception was thrown
+        Assert.assertNotNull(ConstantTxEndpoint.getException());
+        Assert.assertEquals(ExecutionException.class,
+                ConstantTxEndpoint.getException().getClass());
+        Assert.assertNotNull(ConstantTxEndpoint.getException().getCause());
+        Assert.assertEquals(SocketTimeoutException.class,
+                ConstantTxEndpoint.getException().getCause().getClass());
+
+        // Check correct time passed
+        Assert.assertTrue(ConstantTxEndpoint.getTimeout() >= TIMEOUT_MS);
+
+        // Check the timeout wasn't too long
+        Assert.assertTrue(ConstantTxEndpoint.getTimeout() < TIMEOUT_MS*2);
+    }
+
+
+    public static class BlockingConfig extends WsContextListener {
+
+        public static final String PATH = "/block";
+
+        @Override
+        public void contextInitialized(ServletContextEvent sce) {
+            super.contextInitialized(sce);
+            ServerContainer sc =
+                    (ServerContainer) sce.getServletContext().getAttribute(
+                            Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
+            try {
+                sc.addEndpoint(BlockingPojo.class);
+            } catch (DeploymentException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+
+
+    @ServerEndpoint("/block")
+    public static class BlockingPojo {
+        @SuppressWarnings("unused")
+        @OnMessage
+        public void echoTextMessage(Session session, String msg, boolean last) {
+            try {
+                Thread.sleep(60000);
+            } catch (InterruptedException e) {
+                // Ignore
+            }
+        }
+
+
+        @SuppressWarnings("unused")
+        @OnMessage
+        public void echoBinaryMessage(Session session, ByteBuffer msg,
+                boolean last) {
+            try {
+                Thread.sleep(TIMEOUT_MS * 10);
+            } catch (InterruptedException e) {
+                // Ignore
+            }
+        }
+    }
+
+
+    public static class BlockingBinaryHandler
+            implements MessageHandler.Partial<ByteBuffer> {
+
+        @Override
+        public void onMessage(ByteBuffer messagePart, boolean last) {
+            try {
+                Thread.sleep(TIMEOUT_MS * 10);
+            } catch (InterruptedException e) {
+                // Ignore
+            }
+        }
+    }
+
+
+    public static class ConstantTxEndpoint extends Endpoint {
+
+        // Have to be static to be able to retrieve results from test case
+        private static volatile long timeout = -1;
+        private static volatile Exception exception = null;
+        private static volatile boolean running = true;
+
+
+        @Override
+        public void onOpen(Session session, EndpointConfig config) {
+
+            // Reset everything
+            timeout = -1;
+            exception = null;
+            running = true;
+
+            if (!TestWsWebSocketContainer.timoutOnContainer) {
+                session.getAsyncRemote().setSendTimeout(TIMEOUT_MS);
+            }
+
+            long lastSend = 0;
+
+            // Should send quickly until the network buffers fill up and then
+            // block until the timeout kicks in
+            try {
+                while (true) {
+                    lastSend = System.currentTimeMillis();
+                    Future<Void> f = session.getAsyncRemote().sendBinary(
+                            ByteBuffer.wrap(MESSAGE_BINARY_4K));
+                    f.get();
+                }
+            } catch (ExecutionException e) {
+                exception = e;
+            } catch (InterruptedException e) {
+                exception = e;
+            }
+            timeout = System.currentTimeMillis() - lastSend;
+            running = false;
+        }
+
+        public static long getTimeout() {
+            return timeout;
+        }
+
+        public static Exception getException() {
+            return exception;
+        }
+
+        public static boolean getRunning() {
+            return running;
+        }
+    }
+
+
+    public static class ConstantTxConfig extends WsContextListener {
+
+        private static final String PATH = "/test";
+
+        @Override
+        public void contextInitialized(ServletContextEvent sce) {
+            super.contextInitialized(sce);
+            ServerContainer sc =
+                    (ServerContainer) sce.getServletContext().getAttribute(
+                            Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
+            try {
+                sc.addEndpoint(ServerEndpointConfig.Builder.create(
+                        ConstantTxEndpoint.class, PATH).build());
+                if (TestWsWebSocketContainer.timoutOnContainer) {
+                    sc.setAsyncSendTimeout(TIMEOUT_MS);
+                }
+            } catch (DeploymentException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+
+
+    @Test
+    public void testGetOpenSessions() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        tomcat.start();
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+
+        Session s1a = connectToEchoServer(wsContainer, EndpointA.class,
+                TesterEchoServer.Config.PATH_BASIC);
+        Session s2a = connectToEchoServer(wsContainer, EndpointA.class,
+                TesterEchoServer.Config.PATH_BASIC);
+        Session s3a = connectToEchoServer(wsContainer, EndpointA.class,
+                TesterEchoServer.Config.PATH_BASIC);
+
+        Session s1b = connectToEchoServer(wsContainer, EndpointB.class,
+                TesterEchoServer.Config.PATH_BASIC);
+        Session s2b = connectToEchoServer(wsContainer, EndpointB.class,
+                TesterEchoServer.Config.PATH_BASIC);
+
+        Set<Session> setA = s3a.getOpenSessions();
+        Assert.assertEquals(3, setA.size());
+        Assert.assertTrue(setA.remove(s1a));
+        Assert.assertTrue(setA.remove(s2a));
+        Assert.assertTrue(setA.remove(s3a));
+
+        s1a.close();
+
+        setA = s3a.getOpenSessions();
+        Assert.assertEquals(2, setA.size());
+        Assert.assertFalse(setA.remove(s1a));
+        Assert.assertTrue(setA.remove(s2a));
+        Assert.assertTrue(setA.remove(s3a));
+
+        Set<Session> setB = s1b.getOpenSessions();
+        Assert.assertEquals(2, setB.size());
+        Assert.assertTrue(setB.remove(s1b));
+        Assert.assertTrue(setB.remove(s2b));
+    }
+
+
+    @Test
+    public void testSessionExpiryContainer() throws Exception {
+
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        tomcat.start();
+
+        // Need access to implementation methods for configuring unit tests
+        WsWebSocketContainer wsContainer = (WsWebSocketContainer)
+                ContainerProvider.getWebSocketContainer();
+
+        // 5 second timeout
+        wsContainer.setDefaultMaxSessionIdleTimeout(5000);
+        wsContainer.setProcessPeriod(1);
+
+        connectToEchoServer(wsContainer, EndpointA.class,
+                TesterEchoServer.Config.PATH_BASIC);
+        connectToEchoServer(wsContainer, EndpointA.class,
+                TesterEchoServer.Config.PATH_BASIC);
+        Session s3a = connectToEchoServer(wsContainer, EndpointA.class,
+                TesterEchoServer.Config.PATH_BASIC);
+
+        // Check all three sessions are open
+        Set<Session> setA = s3a.getOpenSessions();
+        Assert.assertEquals(3, setA.size());
+
+        int count = 0;
+        boolean isOpen = true;
+        while (isOpen && count < 8) {
+            count ++;
+            Thread.sleep(1000);
+            isOpen = false;
+            for (Session session : setA) {
+                if (session.isOpen()) {
+                    isOpen = true;
+                    break;
+                }
+            }
+        }
+
+        if (isOpen) {
+            for (Session session : setA) {
+                if (session.isOpen()) {
+                    System.err.println("Session with ID [" + session.getId() +
+                            "] is open");
+                }
+            }
+            Assert.fail("There were open sessions");
+        }
+    }
+
+
+    @Test
+    public void testSessionExpirySession() throws Exception {
+
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        tomcat.start();
+
+        // Need access to implementation methods for configuring unit tests
+        WsWebSocketContainer wsContainer = (WsWebSocketContainer)
+                ContainerProvider.getWebSocketContainer();
+
+        // 5 second timeout
+        wsContainer.setDefaultMaxSessionIdleTimeout(5000);
+        wsContainer.setProcessPeriod(1);
+
+        Session s1a = connectToEchoServer(wsContainer, EndpointA.class,
+                TesterEchoServer.Config.PATH_BASIC);
+        s1a.setMaxIdleTimeout(3000);
+        Session s2a = connectToEchoServer(wsContainer, EndpointA.class,
+                TesterEchoServer.Config.PATH_BASIC);
+        s2a.setMaxIdleTimeout(6000);
+        Session s3a = connectToEchoServer(wsContainer, EndpointA.class,
+                TesterEchoServer.Config.PATH_BASIC);
+        s3a.setMaxIdleTimeout(9000);
+
+        // Check all three sessions are open
+        Set<Session> setA = s3a.getOpenSessions();
+
+        int expected = 3;
+        while (expected > 0) {
+            Assert.assertEquals(expected, getOpenCount(setA));
+
+            int count = 0;
+            while (getOpenCount(setA) == expected && count < 5) {
+                count ++;
+                Thread.sleep(1000);
+            }
+
+            expected--;
+        }
+
+        Assert.assertEquals(0, getOpenCount(setA));
+    }
+
+
+    private int getOpenCount(Set<Session> sessions) {
+        int result = 0;
+        for (Session session : sessions) {
+            if (session.isOpen()) {
+                result++;
+            }
+        }
+        return result;
+    }
+
+    private Session connectToEchoServer(WebSocketContainer wsContainer,
+            Class<? extends Endpoint> clazz, String path) throws Exception {
+        return wsContainer.connectToServer(clazz,
+                ClientEndpointConfig.Builder.create().build(),
+                new URI("ws://localhost:" + getPort() + path));
+    }
+
+    public static final class EndpointA extends Endpoint {
+
+        @Override
+        public void onOpen(Session session, EndpointConfig config) {
+            // NO-OP
+        }
+    }
+
+
+    public static final class EndpointB extends Endpoint {
+
+        @Override
+        public void onOpen(Session session, EndpointConfig config) {
+            // NO-OP
+        }
+    }
+
+
+    @Test
+    public void testConnectToServerEndpointSSL() throws Exception {
+
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        TesterSupport.initSsl(tomcat);
+
+        tomcat.start();
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+        ClientEndpointConfig clientEndpointConfig =
+                ClientEndpointConfig.Builder.create().build();
+        URL truststoreUrl = this.getClass().getClassLoader().getResource(
+                "org/apache/tomcat/util/net/ca.jks");
+        File truststoreFile = new File(truststoreUrl.toURI());
+        clientEndpointConfig.getUserProperties().put(
+                WsWebSocketContainer.SSL_TRUSTSTORE_PROPERTY,
+                truststoreFile.getAbsolutePath());
+        Session wsSession = wsContainer.connectToServer(
+                TesterProgrammaticEndpoint.class,
+                clientEndpointConfig,
+                new URI("wss://localhost:" + getPort() +
+                        TesterEchoServer.Config.PATH_ASYNC));
+        CountDownLatch latch = new CountDownLatch(1);
+        BasicText handler = new BasicText(latch);
+        wsSession.addMessageHandler(handler);
+        wsSession.getBasicRemote().sendText(MESSAGE_STRING_1);
+
+        boolean latchResult = handler.getLatch().await(10, TimeUnit.SECONDS);
+
+        Assert.assertTrue(latchResult);
+
+        Queue<String> messages = handler.getMessages();
+        Assert.assertEquals(1, messages.size());
+        Assert.assertEquals(MESSAGE_STRING_1, messages.peek());
+    }
+
+
+    @Test
+    public void testMaxMessageSize01() throws Exception {
+        doMaxMessageSize(TesterEchoServer.BasicLimit.MAX_SIZE - 1, true);
+    }
+
+
+    @Test
+    public void testMaxMessageSize02() throws Exception {
+        doMaxMessageSize(TesterEchoServer.BasicLimit.MAX_SIZE, true);
+    }
+
+
+    @Test
+    public void testMaxMessageSize03() throws Exception {
+        doMaxMessageSize(TesterEchoServer.BasicLimit.MAX_SIZE + 1, false);
+    }
+
+
+    private void doMaxMessageSize(long size, boolean expectOpen)
+            throws Exception {
+
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                TesterEchoServer.Config.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        tomcat.start();
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+
+        Session s = connectToEchoServer(wsContainer, EndpointA.class,
+                TesterEchoServer.Config.PATH_BASIC_LIMIT);
+
+        StringBuilder msg = new StringBuilder();
+        for (long i = 0; i < size; i++) {
+            msg.append('x');
+        }
+
+        s.getBasicRemote().sendText(msg.toString());
+
+        // Wait for up to 5 seconds for session to close
+        boolean open = s.isOpen();
+        int count = 0;
+        while (open != expectOpen && count < 50) {
+            Thread.sleep(100);
+            count++;
+            open = s.isOpen();
+        }
+
+        Assert.assertEquals(Boolean.valueOf(expectOpen),
+                Boolean.valueOf(s.isOpen()));
+    }
+}
diff --git a/test/org/apache/tomcat/websocket/TesterEchoServer.java b/test/org/apache/tomcat/websocket/TesterEchoServer.java
new file mode 100644
index 0000000..36fde23
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/TesterEchoServer.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import javax.servlet.ServletContextEvent;
+import javax.websocket.DeploymentException;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpoint;
+
+import org.apache.tomcat.websocket.server.Constants;
+import org.apache.tomcat.websocket.server.WsContextListener;
+
+public class TesterEchoServer {
+
+    public static class Config extends WsContextListener {
+
+        public static final String PATH_ASYNC = "/echoAsync";
+        public static final String PATH_BASIC = "/echoBasic";
+        public static final String PATH_BASIC_LIMIT = "/echoBasicLimit";
+
+        @Override
+        public void contextInitialized(ServletContextEvent sce) {
+            super.contextInitialized(sce);
+            ServerContainer sc =
+                    (ServerContainer) sce.getServletContext().getAttribute(
+                            Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
+            try {
+                sc.addEndpoint(Async.class);
+                sc.addEndpoint(Basic.class);
+                sc.addEndpoint(BasicLimit.class);
+            } catch (DeploymentException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+
+    @ServerEndpoint("/echoAsync")
+    public static class Async {
+
+        @OnMessage
+        public void echoTextMessage(Session session, String msg, boolean last) {
+            try {
+                session.getBasicRemote().sendText(msg, last);
+            } catch (IOException e) {
+                try {
+                    session.close();
+                } catch (IOException e1) {
+                    // Ignore
+                }
+            }
+        }
+
+
+        @OnMessage
+        public void echoBinaryMessage(Session session, ByteBuffer msg,
+                boolean last) {
+            try {
+                session.getBasicRemote().sendBinary(msg, last);
+            } catch (IOException e) {
+                try {
+                    session.close();
+                } catch (IOException e1) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+
+    @ServerEndpoint("/echoBasic")
+    public static class Basic {
+        @OnMessage
+        public void echoTextMessage(Session session, String msg) {
+            try {
+                session.getBasicRemote().sendText(msg);
+            } catch (IOException e) {
+                try {
+                    session.close();
+                } catch (IOException e1) {
+                    // Ignore
+                }
+            }
+        }
+
+
+        @OnMessage
+        public void echoBinaryMessage(Session session, ByteBuffer msg) {
+            try {
+                session.getBasicRemote().sendBinary(msg);
+            } catch (IOException e) {
+                try {
+                    session.close();
+                } catch (IOException e1) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+
+    @ServerEndpoint("/echoBasicLimit")
+    public static class BasicLimit {
+
+        public static final long MAX_SIZE = 10;
+
+        @OnMessage(maxMessageSize = MAX_SIZE)
+        public void echoTextMessage(Session session, String msg) {
+            try {
+                session.getBasicRemote().sendText(msg);
+            } catch (IOException e) {
+                try {
+                    session.close();
+                } catch (IOException e1) {
+                    // Ignore
+                }
+            }
+        }
+
+
+        @OnMessage(maxMessageSize = MAX_SIZE)
+        public void echoBinaryMessage(Session session, ByteBuffer msg) {
+            try {
+                session.getBasicRemote().sendBinary(msg);
+            } catch (IOException e) {
+                try {
+                    session.close();
+                } catch (IOException e1) {
+                    // Ignore
+                }
+            }
+        }
+    }
+}
diff --git a/test/org/apache/tomcat/websocket/TesterFirehoseServer.java b/test/org/apache/tomcat/websocket/TesterFirehoseServer.java
new file mode 100644
index 0000000..4047ff9
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/TesterFirehoseServer.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.IOException;
+
+import javax.servlet.ServletContextEvent;
+import javax.websocket.DeploymentException;
+import javax.websocket.OnMessage;
+import javax.websocket.RemoteEndpoint.Basic;
+import javax.websocket.Session;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpoint;
+
+import org.apache.tomcat.websocket.server.Constants;
+import org.apache.tomcat.websocket.server.WsContextListener;
+
+/**
+ * Sends {@link #MESSAGE_COUNT} messages of size {@link #MESSAGE_SIZE} bytes as
+ * quickly as possible after the client sends its first message.
+ */
+public class TesterFirehoseServer {
+
+    public static final int MESSAGE_COUNT = 100000;
+    public static final String MESSAGE;
+    public static final int MESSAGE_SIZE = 1024;
+    public static final int WAIT_TIME_MILLIS = 60000;
+
+    static {
+        StringBuilder sb = new StringBuilder(MESSAGE_SIZE);
+        for (int i = 0; i < MESSAGE_SIZE; i++) {
+            sb.append('x');
+        }
+        MESSAGE = sb.toString();
+    }
+
+
+    public static class Config extends WsContextListener {
+
+        public static final String PATH = "/firehose";
+
+        @Override
+        public void contextInitialized(ServletContextEvent sce) {
+            super.contextInitialized(sce);
+            ServerContainer sc =
+                    (ServerContainer) sce.getServletContext().getAttribute(
+                            Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
+            try {
+                sc.addEndpoint(Endpoint.class);
+            } catch (DeploymentException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+
+
+    @ServerEndpoint(Config.PATH)
+    public static class Endpoint {
+
+        private volatile boolean started = false;
+
+        @OnMessage
+        public void onMessage(Session session,
+                @SuppressWarnings("unused") String msg) throws IOException {
+
+            if (started) {
+                return;
+            }
+            synchronized (this) {
+                if (started) {
+                    return;
+                } else {
+                    started = true;
+                }
+            }
+
+            session.getUserProperties().put(
+                    "org.apache.tomcat.websocket.BLOCKING_SEND_TIMEOUT",
+                    Long.valueOf(WAIT_TIME_MILLIS));
+
+            Basic remote = session.getBasicRemote();
+            remote.setBatchingAllowed(true);
+
+            for (int i = 0; i < MESSAGE_COUNT; i++) {
+                remote.sendText(MESSAGE);
+            }
+
+            // Ensure remaining messages are flushed
+            remote.setBatchingAllowed(false);
+        }
+    }
+}
diff --git a/test/org/apache/tomcat/websocket/TesterMessageCountClient.java b/test/org/apache/tomcat/websocket/TesterMessageCountClient.java
new file mode 100644
index 0000000..e2319b7
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/TesterMessageCountClient.java
@@ -0,0 +1,226 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+
+public class TesterMessageCountClient {
+
+    public interface TesterEndpoint {
+        void setLatch(CountDownLatch latch);
+    }
+
+    public static class TesterProgrammaticEndpoint
+            extends Endpoint implements TesterEndpoint {
+
+        private CountDownLatch latch = null;
+
+        @Override
+        public void setLatch(CountDownLatch latch) {
+            this.latch = latch;
+        }
+
+        @Override
+        public void onClose(Session session, CloseReason closeReason) {
+            clearLatch();
+        }
+
+        @Override
+        public void onError(Session session, Throwable throwable) {
+            clearLatch();
+        }
+
+        private void clearLatch() {
+            if (latch != null) {
+                while (latch.getCount() > 0) {
+                    latch.countDown();
+                }
+            }
+        }
+
+        @Override
+        public void onOpen(Session session, EndpointConfig config) {
+            session.getUserProperties().put("endpoint", this);
+        }
+    }
+
+    @ClientEndpoint
+    public static class TesterAnnotatedEndpoint implements TesterEndpoint {
+
+        private CountDownLatch latch = null;
+
+        @Override
+        public void setLatch(CountDownLatch latch) {
+            this.latch = latch;
+        }
+
+        @OnClose
+        public void onClose() {
+            clearLatch();
+        }
+
+        @OnError
+        public void onError(@SuppressWarnings("unused") Throwable throwable) {
+            clearLatch();
+        }
+
+        private void clearLatch() {
+            if (latch != null) {
+                while (latch.getCount() > 0) {
+                    latch.countDown();
+                }
+            }
+        }
+
+        @OnOpen
+        public void onOpen(Session session) {
+            session.getUserProperties().put("endpoint", this);
+        }
+    }
+
+
+    public abstract static class BasicHandler<T>
+            implements MessageHandler.Whole<T> {
+
+        private final CountDownLatch latch;
+
+        private final Queue<T> messages = new LinkedBlockingQueue<T>();
+
+        public BasicHandler(CountDownLatch latch) {
+            this.latch = latch;
+        }
+
+        public CountDownLatch getLatch() {
+            return latch;
+        }
+
+        public Queue<T> getMessages() {
+            return messages;
+        }
+    }
+
+    public static class BasicBinary extends BasicHandler<ByteBuffer> {
+
+        public BasicBinary(CountDownLatch latch) {
+            super(latch);
+        }
+
+        @Override
+        public void onMessage(ByteBuffer message) {
+            getMessages().add(message);
+            if (getLatch() != null) {
+                getLatch().countDown();
+            }
+        }
+    }
+
+    public static class BasicText extends BasicHandler<String> {
+
+        private final String expected;
+
+        public BasicText(CountDownLatch latch) {
+            this(latch, null);
+        }
+
+        public BasicText(CountDownLatch latch, String expected) {
+            super(latch);
+            this.expected = expected;
+        }
+
+        @Override
+        public void onMessage(String message) {
+            if (expected == null) {
+                getMessages().add(message);
+            } else {
+                if (!expected.equals(message)) {
+                    throw new IllegalStateException(
+                            "Expected: [" + expected + "]\r\n" +
+                            "Was:      [" + message + "]");
+                }
+            }
+            if (getLatch() != null) {
+                getLatch().countDown();
+            }
+        }
+    }
+
+    public abstract static class AsyncHandler<T>
+            implements MessageHandler.Partial<T> {
+
+        private final CountDownLatch latch;
+
+        private final List<T> messages = new CopyOnWriteArrayList<T>();
+
+        public AsyncHandler(CountDownLatch latch) {
+            this.latch = latch;
+        }
+
+        public CountDownLatch getLatch() {
+            return latch;
+        }
+
+        public List<T> getMessages() {
+            return messages;
+        }
+    }
+
+    public static class AsyncBinary extends AsyncHandler<ByteBuffer> {
+
+        public AsyncBinary(CountDownLatch latch) {
+            super(latch);
+        }
+
+        @Override
+        public void onMessage(ByteBuffer message, boolean last) {
+            getMessages().add(message);
+            if (last && getLatch() != null) {
+                getLatch().countDown();
+            }
+        }
+    }
+
+    public static class AsyncText extends AsyncHandler<String> {
+
+
+        public AsyncText(CountDownLatch latch) {
+            super(latch);
+        }
+
+        @Override
+        public void onMessage(String message, boolean last) {
+            getMessages().add(message);
+            if (last && getLatch() != null) {
+                getLatch().countDown();
+            }
+        }
+    }
+}
diff --git a/test/org/apache/tomcat/websocket/TesterWsClientAutobahn.java b/test/org/apache/tomcat/websocket/TesterWsClientAutobahn.java
new file mode 100644
index 0000000..518ff33
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/TesterWsClientAutobahn.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.websocket;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ContainerProvider;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.apache.tomcat.util.ExceptionUtils;
+
+/**
+ * Runs the Autobahn test suite in client mode for testing the WebSocket client
+ * implementation.
+ */
+public class TesterWsClientAutobahn {
+
+    private static final String HOST = "localhost";
+    private static final int PORT = 9001;
+    private static final String USER_AGENT = "ApacheTomcat8WebSocketClient";
+
+
+    public static void main(String[] args) throws Exception {
+
+        WebSocketContainer wsc = ContainerProvider.getWebSocketContainer();
+
+        int testCaseCount = getTestCaseCount(wsc);
+        System.out.println("There are " + testCaseCount + " test cases");
+        for (int testCase = 1; testCase <= testCaseCount; testCase++) {
+            if (testCase % 50 == 0) {
+                System.out.println(testCase);
+            } else {
+                System.out.print('.');
+            }
+            try {
+                executeTestCase(wsc, testCase);
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                t.printStackTrace();
+            }
+
+        }
+        System.out.println("Testing complete");
+        updateReports(wsc);
+    }
+
+
+    private static int getTestCaseCount(WebSocketContainer wsc)
+            throws Exception {
+
+        URI uri = new URI("ws://" + HOST + ":" + PORT + "/getCaseCount");
+        CaseCountClient caseCountClient = new CaseCountClient();
+        wsc.connectToServer(caseCountClient, uri);
+        return caseCountClient.getCaseCount();
+    }
+
+
+    private static void executeTestCase(WebSocketContainer wsc, int testCase)
+            throws Exception {
+        URI uri = new URI("ws://" + HOST + ":" + PORT + "/runCase?case=" +
+                testCase + "&agent=" + USER_AGENT);
+        TestCaseClient testCaseClient = new TestCaseClient();
+        wsc.connectToServer(testCaseClient, uri);
+        testCaseClient.waitForClose();
+    }
+
+
+    private static void updateReports(WebSocketContainer wsc)
+            throws Exception {
+
+        URI uri = new URI("ws://" + HOST + ":" + PORT +
+                "/updateReports?agent=" + USER_AGENT);
+        UpdateReportsClient updateReportsClient = new UpdateReportsClient();
+        wsc.connectToServer(updateReportsClient, uri);
+    }
+
+
+    @ClientEndpoint
+    public static class CaseCountClient {
+
+        private final CountDownLatch latch = new CountDownLatch(1);
+        private volatile int caseCount = 0;
+
+        // Need to wait for message
+        public int getCaseCount() throws InterruptedException {
+            latch.await();
+            return caseCount;
+        }
+
+        @OnMessage
+        public void onMessage(String msg) {
+            latch.countDown();
+            caseCount = Integer.valueOf(msg).intValue();
+        }
+
+
+        @OnError
+        public void onError(Throwable t) {
+            latch.countDown();
+            t.printStackTrace();
+        }
+    }
+
+
+    @ClientEndpoint
+    public static class TestCaseClient {
+
+        private final CountDownLatch latch = new CountDownLatch(1);
+
+        public void waitForClose() throws InterruptedException {
+            latch.await();
+        }
+
+        @OnMessage
+        public void echoTextMessage(Session session, String msg, boolean last) {
+            try {
+                if (session.isOpen()) {
+                    session.getBasicRemote().sendText(msg, last);
+                }
+            } catch (IOException e) {
+                try {
+                    session.close();
+                } catch (IOException e1) {
+                    // Ignore
+                }
+            }
+        }
+
+        @OnMessage
+        public void echoBinaryMessage(Session session, ByteBuffer bb,
+                boolean last) {
+            try {
+                if (session.isOpen()) {
+                    session.getBasicRemote().sendBinary(bb, last);
+                }
+            } catch (IOException e) {
+                try {
+                    session.close();
+                } catch (IOException e1) {
+                    // Ignore
+                }
+            }
+        }
+
+        @OnClose
+        public void releaseLatch() {
+            latch.countDown();
+        }
+    }
+
+
+    @ClientEndpoint
+    public static class UpdateReportsClient {
+
+        private final CountDownLatch latch = new CountDownLatch(1);
+
+        public void waitForClose() throws InterruptedException {
+            latch.await();
+        }
+
+        @OnClose
+        public void onClose() {
+            latch.countDown();
+        }
+    }
+}
diff --git a/test/org/apache/tomcat/websocket/pojo/TestEncodingDecoding.java b/test/org/apache/tomcat/websocket/pojo/TestEncodingDecoding.java
new file mode 100644
index 0000000..616a7a5
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/pojo/TestEncodingDecoding.java
@@ -0,0 +1,477 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.pojo;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.servlet.ServletContextEvent;
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ContainerProvider;
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.DeploymentException;
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Extension;
+import javax.websocket.MessageHandler;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.deploy.ApplicationListener;
+import org.apache.catalina.servlets.DefaultServlet;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.websocket.pojo.TesterUtil.ServerConfigListener;
+import org.apache.tomcat.websocket.pojo.TesterUtil.SingletonConfigurator;
+import org.apache.tomcat.websocket.server.WsContextListener;
+
+public class TestEncodingDecoding extends TomcatBaseTest {
+
+    private static final String MESSAGE_ONE = "message-one";
+    private static final String PATH_PROGRAMMATIC_EP = "/echoProgrammaticEP";
+    private static final String PATH_ANNOTATED_EP = "/echoAnnotatedEP";
+
+
+    @Test
+    public void testProgrammaticEndPoints() throws Exception{
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                ProgramaticServerEndpointConfig.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+
+        tomcat.start();
+
+        Client client = new Client();
+        URI uri = new URI("ws://localhost:" + getPort() + PATH_PROGRAMMATIC_EP);
+        Session session = wsContainer.connectToServer(client, uri);
+
+        MsgString msg1 = new MsgString();
+        msg1.setData(MESSAGE_ONE);
+        session.getBasicRemote().sendObject(msg1);
+        // Should not take very long
+        int i = 0;
+        while (i < 20) {
+            if (MsgStringMessageHandler.received.size() > 0 &&
+                    client.received.size() > 0) {
+                break;
+            }
+            Thread.sleep(100);
+            i++;
+        }
+
+        // Check messages were received
+        Assert.assertEquals(1, MsgStringMessageHandler.received.size());
+        Assert.assertEquals(1, client.received.size());
+
+        // Check correct messages were received
+        Assert.assertEquals(MESSAGE_ONE,
+                ((MsgString) MsgStringMessageHandler.received.peek()).getData());
+        Assert.assertEquals(MESSAGE_ONE,
+                new String(((MsgByte) client.received.peek()).getData()));
+        session.close();
+    }
+
+
+    @Test
+    public void testAnnotatedEndPoints() throws Exception {
+        // Set up utility classes
+        Server server = new Server();
+        SingletonConfigurator.setInstance(server);
+        ServerConfigListener.setPojoClazz(Server.class);
+
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                ServerConfigListener.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+
+        tomcat.start();
+
+        Client client = new Client();
+        URI uri = new URI("ws://localhost:" + getPort() + PATH_ANNOTATED_EP);
+        Session session = wsContainer.connectToServer(client, uri);
+
+        MsgString msg1 = new MsgString();
+        msg1.setData(MESSAGE_ONE);
+        session.getBasicRemote().sendObject(msg1);
+
+        // Should not take very long
+        int i = 0;
+        while (i < 20) {
+            if (server.received.size() > 0 && client.received.size() > 0) {
+                break;
+            }
+            Thread.sleep(100);
+        }
+
+        // Check messages were received
+        Assert.assertEquals(1, server.received.size());
+        Assert.assertEquals(1, client.received.size());
+
+        // Check correct messages were received
+        Assert.assertEquals(MESSAGE_ONE,
+                ((MsgString) server.received.peek()).getData());
+        Assert.assertEquals(MESSAGE_ONE,
+                ((MsgString) client.received.peek()).getData());
+        session.close();
+
+        // Should not take very long but some failures have been seen
+        i = testEvent(MsgStringEncoder.class.getName()+":init", 0);
+        i = testEvent(MsgStringDecoder.class.getName()+":init", i);
+        i = testEvent(MsgByteEncoder.class.getName()+":init", i);
+        i = testEvent(MsgByteDecoder.class.getName()+":init", i);
+        i = testEvent(MsgStringEncoder.class.getName()+":destroy", i);
+        i = testEvent(MsgStringDecoder.class.getName()+":destroy", i);
+        i = testEvent(MsgByteEncoder.class.getName()+":destroy", i);
+        i = testEvent(MsgByteDecoder.class.getName()+":destroy", i);
+    }
+
+    
+    private int testEvent(String name, int count) throws InterruptedException {
+        int i = count;
+        while (i < 50) {
+            if (Server.isLifeCycleEventCalled(name)) {
+                break;
+            }
+            i++;
+            Thread.sleep(100);
+        }
+        Assert.assertTrue(Server.isLifeCycleEventCalled(name));
+        return i;
+    }
+
+
+    @ClientEndpoint(decoders={MsgStringDecoder.class, MsgByteDecoder.class},
+            encoders={MsgStringEncoder.class, MsgByteEncoder.class})
+    public static class Client {
+
+        private Queue<Object> received = new ConcurrentLinkedQueue<Object>();
+
+        @OnMessage
+        public void rx(MsgString in) {
+            received.add(in);
+        }
+
+        @OnMessage
+        public void  rx(MsgByte in) {
+            received.add(in);
+        }
+    }
+
+
+    @ServerEndpoint(value=PATH_ANNOTATED_EP,
+            decoders={MsgStringDecoder.class, MsgByteDecoder.class},
+            encoders={MsgStringEncoder.class, MsgByteEncoder.class},
+            configurator=SingletonConfigurator.class)
+    public static class Server {
+
+        private Queue<Object> received = new ConcurrentLinkedQueue<Object>();
+        static HashMap<String, Boolean> lifeCyclesCalled = new HashMap<String, Boolean>(8);
+
+        @OnMessage
+        public MsgString rx(MsgString in) {
+            received.add(in);
+            // Echo the message back
+            return in;
+        }
+
+        @OnMessage
+        public MsgByte rx(MsgByte in) {
+            received.add(in);
+            // Echo the message back
+            return in;
+        }
+
+        public static void addLifeCycleEvent(String event){
+            lifeCyclesCalled.put(event, Boolean.TRUE);
+        }
+
+        public static boolean isLifeCycleEventCalled(String event){
+            Boolean called = lifeCyclesCalled.get(event);
+            return called == null ? false : called.booleanValue();
+        }
+    }
+
+
+    public static class MsgByteMessageHandler implements
+            MessageHandler.Whole<MsgByte> {
+
+        public static Queue<Object> received = new ConcurrentLinkedQueue<Object>();
+        private final Session session;
+
+        public MsgByteMessageHandler(Session session) {
+            this.session = session;
+        }
+
+        @Override
+        public void onMessage(MsgByte in) {
+            System.out.println(getClass() + " received");
+            received.add(in);
+            try {
+                MsgByte msg = new MsgByte();
+                msg.setData("got it".getBytes());
+                session.getBasicRemote().sendObject(msg);
+            } catch (IOException e) {
+                throw new IllegalStateException(e);
+            } catch (EncodeException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+
+
+    public static class MsgStringMessageHandler
+            implements MessageHandler.Whole<MsgString>{
+
+        public static Queue<Object> received = new ConcurrentLinkedQueue<Object>();
+        private final Session session;
+
+        public MsgStringMessageHandler(Session session) {
+            this.session = session;
+        }
+
+        @Override
+        public void onMessage(MsgString in) {
+            received.add(in);
+            try {
+                MsgByte msg = new MsgByte();
+                msg.setData(MESSAGE_ONE.getBytes());
+                session.getBasicRemote().sendObject(msg);
+            } catch (IOException e) {
+                e.printStackTrace();
+            } catch (EncodeException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+
+    public static class ProgrammaticEndpoint extends Endpoint {
+       @Override
+       public void onOpen(Session session, EndpointConfig config) {
+           session.addMessageHandler(new MsgStringMessageHandler(session));
+       }
+    }
+
+
+    public static class MsgString {
+        private String data;
+        public String getData() { return data; }
+        public void setData(String data) { this.data = data; }
+    }
+
+
+    public static class MsgStringEncoder implements Encoder.Text<MsgString> {
+
+        @Override
+        public void init(EndpointConfig endpointConfig) {
+            Server.addLifeCycleEvent(getClass().getName() + ":init");
+        }
+
+        @Override
+        public void destroy() {
+            Server.addLifeCycleEvent(getClass().getName() + ":destroy");
+        }
+
+        @Override
+        public String encode(MsgString msg) throws EncodeException {
+            return "MsgString:" + msg.getData();
+        }
+    }
+
+
+    public static class MsgStringDecoder implements Decoder.Text<MsgString> {
+
+        @Override
+        public void init(EndpointConfig endpointConfig) {
+            Server.addLifeCycleEvent(getClass().getName() + ":init");
+        }
+
+        @Override
+        public void destroy() {
+            Server.addLifeCycleEvent(getClass().getName() + ":destroy");
+        }
+
+        @Override
+        public MsgString decode(String s) throws DecodeException {
+            MsgString result = new MsgString();
+            result.setData(s.substring(10));
+            return result;
+        }
+
+        @Override
+        public boolean willDecode(String s) {
+            return s.startsWith("MsgString:");
+        }
+    }
+
+
+    public static class MsgByte {
+        private byte[] data;
+        public byte[] getData() { return data; }
+        public void setData(byte[] data) { this.data = data; }
+    }
+
+
+    public static class MsgByteEncoder implements Encoder.Binary<MsgByte> {
+
+        @Override
+        public void init(EndpointConfig endpointConfig) {
+            Server.addLifeCycleEvent(getClass().getName() + ":init");
+        }
+
+        @Override
+        public void destroy() {
+            Server.addLifeCycleEvent(getClass().getName() + ":destroy");
+        }
+
+        @Override
+        public ByteBuffer encode(MsgByte msg) throws EncodeException {
+            byte[] data = msg.getData();
+            ByteBuffer reply = ByteBuffer.allocate(2 + data.length);
+            reply.put((byte) 0x12);
+            reply.put((byte) 0x34);
+            reply.put(data);
+            reply.flip();
+            return reply;
+        }
+    }
+
+
+    public static class MsgByteDecoder implements Decoder.Binary<MsgByte> {
+
+        @Override
+        public void init(EndpointConfig endpointConfig) {
+             Server.addLifeCycleEvent(getClass().getName() + ":init");
+        }
+
+        @Override
+        public void destroy() {
+            Server.addLifeCycleEvent(getClass().getName() + ":destroy");
+        }
+
+        @Override
+        public MsgByte decode(ByteBuffer bb) throws DecodeException {
+            MsgByte result = new MsgByte();
+            byte[] data = new byte[bb.limit() - bb.position()];
+            bb.get(data);
+            result.setData(data);
+            return result;
+        }
+
+        @Override
+        public boolean willDecode(ByteBuffer bb) {
+            bb.mark();
+            if (bb.get() == 0x12 && bb.get() == 0x34) {
+                return true;
+            }
+            bb.reset();
+            return false;
+        }
+    }
+
+
+    public static class ProgramaticServerEndpointConfig extends WsContextListener {
+
+        @Override
+        public void contextInitialized(ServletContextEvent sce) {
+            super.contextInitialized(sce);
+            ServerContainer sc =
+                    (ServerContainer) sce.getServletContext().getAttribute(
+                            org.apache.tomcat.websocket.server.Constants.
+                            SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
+            try {
+                sc.addEndpoint(new ServerEndpointConfig() {
+                    @Override
+                    public Map<String, Object> getUserProperties() {
+                        return Collections.emptyMap();
+                    }
+                    @Override
+                    public List<Class<? extends Encoder>> getEncoders() {
+                        List<Class<? extends Encoder>> encoders = new ArrayList<Class<? extends Encoder>>(2);
+                        encoders.add(MsgStringEncoder.class);
+                        encoders.add(MsgByteEncoder.class);
+                        return encoders;
+                    }
+                    @Override
+                    public List<Class<? extends Decoder>> getDecoders() {
+                        List<Class<? extends Decoder>> decoders = new ArrayList<Class<? extends Decoder>>(2);
+                        decoders.add(MsgStringDecoder.class);
+                        decoders.add(MsgByteDecoder.class);
+                        return decoders;
+                    }
+                    @Override
+                    public List<String> getSubprotocols() {
+                        return Collections.emptyList();
+                    }
+                    @Override
+                    public String getPath() {
+                        return PATH_PROGRAMMATIC_EP;
+                    }
+                    @Override
+                    public List<Extension> getExtensions() {
+                        return Collections.emptyList();
+                    }
+                    @Override
+                    public Class<?> getEndpointClass() {
+                        return ProgrammaticEndpoint.class;
+                    }
+                    @Override
+                    public Configurator getConfigurator() {
+                        return new ServerEndpointConfig.Configurator() {
+                        };
+                    }
+                });
+            } catch (DeploymentException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+}
diff --git a/test/org/apache/tomcat/websocket/pojo/TestPojoEndpointBase.java b/test/org/apache/tomcat/websocket/pojo/TestPojoEndpointBase.java
new file mode 100644
index 0000000..62c9577
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/pojo/TestPojoEndpointBase.java
@@ -0,0 +1,155 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.pojo;
+
+import java.net.URI;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ContainerProvider;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import javax.websocket.server.ServerEndpoint;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.deploy.ApplicationListener;
+import org.apache.catalina.servlets.DefaultServlet;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.websocket.TestUtil;
+import org.apache.tomcat.websocket.pojo.TesterUtil.ServerConfigListener;
+import org.apache.tomcat.websocket.pojo.TesterUtil.SingletonConfigurator;
+
+public class TestPojoEndpointBase extends TomcatBaseTest {
+
+    @Test
+    public void testBug54716() throws Exception {
+        TestUtil.generateMask();
+        // Set up utility classes
+        Bug54716 server = new Bug54716();
+        SingletonConfigurator.setInstance(server);
+        ServerConfigListener.setPojoClazz(Bug54716.class);
+
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                ServerConfigListener.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+
+
+        tomcat.start();
+
+        Client client = new Client();
+        URI uri = new URI("ws://localhost:" + getPort() + "/");
+
+        wsContainer.connectToServer(client, uri);
+
+        // Server should close the connection after the exception on open.
+        boolean closed = client.waitForClose(5);
+        Assert.assertTrue("Server failed to close connection", closed);
+    }
+
+
+    @Test
+    public void testOnOpenPojoMethod() throws Exception {
+        // Set up utility classes
+        OnOpenServerEndpoint server = new OnOpenServerEndpoint();
+        SingletonConfigurator.setInstance(server);
+        ServerConfigListener.setPojoClazz(OnOpenServerEndpoint.class);
+
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                ServerConfigListener.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+
+
+        tomcat.start();
+
+        Client client = new Client();
+        URI uri = new URI("ws://localhost:" + getPort() + "/");
+
+        Session session = wsContainer.connectToServer(client, uri);
+
+        client.waitForClose(5);
+        Assert.assertTrue(session.isOpen());
+    }
+
+
+    @ServerEndpoint("/")
+    public static class OnOpenServerEndpoint {
+
+        @OnOpen
+        public void onOpen(@SuppressWarnings("unused") Session session,
+                EndpointConfig config) {
+            if (config == null) {
+                throw new RuntimeException();
+            }
+        }
+
+        @OnError
+        public void onError(@SuppressWarnings("unused") Throwable t){
+            throw new RuntimeException();
+        }
+    }
+
+
+    @ServerEndpoint("/")
+    public static class Bug54716 {
+
+        @OnOpen
+        public void onOpen() {
+            throw new RuntimeException();
+        }
+    }
+
+
+    @ClientEndpoint
+    public static final class Client {
+
+        private final CountDownLatch closeLatch = new CountDownLatch(1);
+
+        @OnClose
+        public void onClose() {
+            closeLatch.countDown();
+        }
+
+        public boolean waitForClose(int seconds) throws InterruptedException {
+            return closeLatch.await(seconds, TimeUnit.SECONDS);
+        }
+    }
+}
diff --git a/test/org/apache/tomcat/websocket/pojo/TestPojoMethodMapping.java b/test/org/apache/tomcat/websocket/pojo/TestPojoMethodMapping.java
new file mode 100644
index 0000000..ec5a9c0
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/pojo/TestPojoMethodMapping.java
@@ -0,0 +1,152 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.pojo;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.ContainerProvider;
+import javax.websocket.OnClose;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.deploy.ApplicationListener;
+import org.apache.catalina.servlets.DefaultServlet;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.websocket.pojo.TesterUtil.ServerConfigListener;
+import org.apache.tomcat.websocket.pojo.TesterUtil.SimpleClient;
+import org.apache.tomcat.websocket.pojo.TesterUtil.SingletonConfigurator;
+
+public class TestPojoMethodMapping extends TomcatBaseTest {
+
+    private static final String PARAM_ONE = "abcde";
+    private static final String PARAM_TWO = "12345";
+    private static final String PARAM_THREE = "true";
+
+    @Test
+    public void test() throws Exception {
+
+        // Set up utility classes
+        Server server = new Server();
+        SingletonConfigurator.setInstance(server);
+        ServerConfigListener.setPojoClazz(Server.class);
+
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                ServerConfigListener.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+
+
+        tomcat.start();
+
+        SimpleClient client = new SimpleClient();
+        URI uri = new URI("ws://localhost:" + getPort() + "/" + PARAM_ONE +
+                "/" + PARAM_TWO + "/" + PARAM_THREE);
+
+        Session session = wsContainer.connectToServer(client, uri);
+        session.getBasicRemote().sendText("NO-OP");
+        session.close();
+
+        // Give server 5s to close
+        int count = 0;
+        while (count < 50) {
+            if (server.isClosed()) {
+                break;
+            }
+            count++;
+            Thread.sleep(100);
+        }
+        if (count == 50) {
+            Assert.fail("Server did not process an onClose event within 5 " +
+                    "seconds of the client sending a close message");
+        }
+
+        // Check no errors
+        List<String> errors = server.getErrors();
+        for (String error : errors) {
+            System.err.println(error);
+        }
+        Assert.assertEquals("Found errors", 0, errors.size());
+    }
+
+
+    @ServerEndpoint(value="/{one}/{two}/{three}",
+            configurator=SingletonConfigurator.class)
+    public static final class Server {
+
+        private final List<String> errors = new ArrayList<String>();
+        private volatile boolean closed;
+
+        @OnOpen
+        public void onOpen(@PathParam("one") String p1, @PathParam("two")int p2,
+                @PathParam("three")boolean p3) {
+            checkParams("onOpen", p1, p2, p3);
+        }
+
+        @OnMessage
+        public void onMessage(@SuppressWarnings("unused") String msg,
+                @PathParam("one") String p1, @PathParam("two")int p2,
+                @PathParam("three")boolean p3) {
+            checkParams("onMessage", p1, p2, p3);
+        }
+
+        @OnClose
+        public void onClose(@PathParam("one") String p1,
+                @PathParam("two")int p2, @PathParam("three")boolean p3) {
+            checkParams("onClose", p1, p2, p3);
+            closed = true;
+        }
+
+        public List<String> getErrors() {
+            return errors;
+        }
+
+        public boolean isClosed() {
+            return closed;
+        }
+
+        private void checkParams(String method, String p1, int p2, boolean p3) {
+            checkParam(method, PARAM_ONE, p1);
+            checkParam(method, PARAM_TWO, Integer.toString(p2));
+            checkParam(method, PARAM_THREE, Boolean.toString(p3));
+        }
+
+        private void checkParam(String method, String expected, String actual) {
+            if (!expected.equals(actual)) {
+                errors.add("Method [" + method + "]. Expected [" + expected +
+                        "] was + [" + actual + "]");
+            }
+        }
+    }
+}
diff --git a/test/org/apache/tomcat/websocket/pojo/TesterUtil.java b/test/org/apache/tomcat/websocket/pojo/TesterUtil.java
new file mode 100644
index 0000000..21c11bb
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/pojo/TesterUtil.java
@@ -0,0 +1,74 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.pojo;
+
+import javax.servlet.ServletContextEvent;
+import javax.websocket.ClientEndpoint;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig.Configurator;
+
+import org.apache.tomcat.websocket.server.Constants;
+import org.apache.tomcat.websocket.server.WsContextListener;
+
+public class TesterUtil {
+
+    public static class ServerConfigListener extends WsContextListener {
+
+        private static Class<?> pojoClazz;
+
+        public static void setPojoClazz(Class<?> pojoClazz) {
+            ServerConfigListener.pojoClazz = pojoClazz;
+        }
+
+        @Override
+        public void contextInitialized(ServletContextEvent sce) {
+            super.contextInitialized(sce);
+            ServerContainer sc =
+                    (ServerContainer) sce.getServletContext().getAttribute(
+                            Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
+            try {
+                sc.addEndpoint(pojoClazz);
+            } catch (DeploymentException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+
+
+    public static class SingletonConfigurator extends Configurator {
+
+        private static Object instance;
+
+        public static void setInstance(Object instance) {
+            SingletonConfigurator.instance = instance;
+        }
+
+        @Override
+        public <T> T getEndpointInstance(Class<T> clazz)
+                throws InstantiationException {
+            @SuppressWarnings("unchecked")
+            T result = (T) instance;
+            return result;
+        }
+    }
+
+
+    @ClientEndpoint
+    public static final class SimpleClient {
+    }
+}
diff --git a/test/org/apache/tomcat/websocket/server/TestUriTemplate.java b/test/org/apache/tomcat/websocket/server/TestUriTemplate.java
new file mode 100644
index 0000000..f0b1c4e
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/server/TestUriTemplate.java
@@ -0,0 +1,213 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.server;
+
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestUriTemplate {
+
+    @Test
+    public void testBasic() throws Exception {
+        UriTemplate t = new UriTemplate("/{a}/{b}");
+        Map<String,String> result = t.match(new UriTemplate("/foo/bar"));
+
+        Assert.assertEquals(2, result.size());
+        Assert.assertTrue(result.containsKey("a"));
+        Assert.assertTrue(result.containsKey("b"));
+        Assert.assertEquals("foo", result.get("a"));
+        Assert.assertEquals("bar", result.get("b"));
+    }
+
+
+    @Test
+    public void testOneOfTwo() throws Exception {
+        UriTemplate t = new UriTemplate("/{a}/{b}");
+        Map<String,String> result = t.match(new UriTemplate("/foo"));
+        Assert.assertNull(result);
+    }
+
+
+    @Test(expected=java.lang.IllegalArgumentException.class)
+    public void testBasicPrefix() throws Exception {
+        @SuppressWarnings("unused")
+        UriTemplate t = new UriTemplate("/x{a}/y{b}");
+    }
+
+
+    @Test(expected=java.lang.IllegalArgumentException.class)
+    public void testPrefixOneOfTwo() throws Exception {
+        UriTemplate t = new UriTemplate("/x{a}/y{b}");
+        t.match(new UriTemplate("/xfoo"));
+    }
+
+
+    @Test(expected=java.lang.IllegalArgumentException.class)
+    public void testPrefixTwoOfTwo() throws Exception {
+        UriTemplate t = new UriTemplate("/x{a}/y{b}");
+        t.match(new UriTemplate("/ybar"));
+    }
+
+
+    @Test(expected=java.lang.IllegalArgumentException.class)
+    public void testQuote1() throws Exception {
+        UriTemplate t = new UriTemplate("/.{a}");
+        t.match(new UriTemplate("/yfoo"));
+    }
+
+
+    @Test(expected=java.lang.IllegalArgumentException.class)
+    public void testQuote2() throws Exception {
+        @SuppressWarnings("unused")
+        UriTemplate t = new UriTemplate("/.{a}");
+    }
+
+
+    @Test
+    public void testNoParams() throws Exception {
+        UriTemplate t = new UriTemplate("/foo/bar");
+        Map<String,String> result = t.match(new UriTemplate("/foo/bar"));
+
+        Assert.assertEquals(0, result.size());
+    }
+
+
+    @Test
+    public void testSpecExample1_01() throws Exception {
+        UriTemplate t = new UriTemplate("/a/b");
+        Map<String,String> result = t.match(new UriTemplate("/a/b"));
+
+        Assert.assertEquals(0, result.size());
+    }
+
+
+    @Test
+    public void testSpecExample1_02() throws Exception {
+        UriTemplate t = new UriTemplate("/a/b");
+        Map<String,String> result = t.match(new UriTemplate("/a"));
+
+        Assert.assertNull(result);
+    }
+
+
+    @Test
+    public void testSpecExample1_03() throws Exception {
+        UriTemplate t = new UriTemplate("/a/b");
+        Map<String,String> result = t.match(new UriTemplate("/a/bb"));
+
+        Assert.assertNull(result);
+    }
+
+
+    @Test
+    public void testSpecExample2_01() throws Exception {
+        UriTemplate t = new UriTemplate("/a/{var}");
+        Map<String,String> result = t.match(new UriTemplate("/a/b"));
+
+        Assert.assertEquals(1, result.size());
+        Assert.assertEquals("b", result.get("var"));
+    }
+
+
+    @Test
+    public void testSpecExample2_02() throws Exception {
+        UriTemplate t = new UriTemplate("/a/{var}");
+        Map<String,String> result = t.match(new UriTemplate("/a/apple"));
+
+        Assert.assertEquals(1, result.size());
+        Assert.assertEquals("apple", result.get("var"));
+    }
+
+
+    @Test
+    public void testSpecExample2_03() throws Exception {
+        UriTemplate t = new UriTemplate("/a/{var}");
+        Map<String,String> result = t.match(new UriTemplate("/a"));
+
+        Assert.assertNull(result);
+    }
+
+
+    @Test
+    public void testSpecExample2_04() throws Exception {
+        UriTemplate t = new UriTemplate("/a/{var}");
+        Map<String,String> result = t.match(new UriTemplate("/a/b/c"));
+
+        Assert.assertNull(result);
+    }
+
+
+    @Test(expected=java.lang.IllegalArgumentException.class)
+    public void testDuplicate01() throws Exception {
+        @SuppressWarnings("unused")
+        UriTemplate t = new UriTemplate("/{var}/{var}");
+    }
+
+
+    @Test
+    public void testDuplicate02() throws Exception {
+        UriTemplate t = new UriTemplate("/{a}/{b}");
+        Map<String,String> result = t.match(new UriTemplate("/x/x"));
+
+        Assert.assertEquals(2, result.size());
+        Assert.assertEquals("x", result.get("a"));
+        Assert.assertEquals("x", result.get("b"));
+    }
+
+
+    public void testEgMailingList01() throws Exception {
+        UriTemplate t = new UriTemplate("/a/{var}");
+        Map<String,String> result = t.match(new UriTemplate("/a/b/"));
+
+        Assert.assertNull(result);
+    }
+
+
+    public void testEgMailingList02() throws Exception {
+        UriTemplate t = new UriTemplate("/a/{var}");
+        Map<String,String> result = t.match(new UriTemplate("/a/"));
+
+        Assert.assertNull(result);
+    }
+
+
+    @Test
+    public void testEgMailingList03() throws Exception {
+        UriTemplate t = new UriTemplate("/a/{var}");
+        Map<String,String> result = t.match(new UriTemplate("/a"));
+
+        Assert.assertNull(result);
+    }
+
+
+    @Test(expected=java.lang.IllegalArgumentException.class)
+    public void testEgMailingList04() throws Exception {
+        UriTemplate t = new UriTemplate("/a/{var1}/{var2}");
+        @SuppressWarnings("unused")
+        Map<String,String> result = t.match(new UriTemplate("/a//c"));
+    }
+
+
+    @Test(expected=java.lang.IllegalArgumentException.class)
+    public void testEgMailingList05() throws Exception {
+        UriTemplate t = new UriTemplate("/a/{var}/");
+        @SuppressWarnings("unused")
+        Map<String,String> result = t.match(new UriTemplate("/a/b/"));
+    }
+}
diff --git a/test/org/apache/tomcat/websocket/server/TestWsServerContainer.java b/test/org/apache/tomcat/websocket/server/TestWsServerContainer.java
new file mode 100644
index 0000000..407bbe4
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/server/TestWsServerContainer.java
@@ -0,0 +1,178 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.websocket.server;
+
+import javax.servlet.ServletContextEvent;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.deploy.ApplicationListener;
+import org.apache.catalina.filters.TesterServletContext;
+import org.apache.catalina.servlets.DefaultServlet;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.websocket.TesterEchoServer;
+
+
+public class TestWsServerContainer extends TomcatBaseTest {
+
+    @Test
+    public void testBug54807() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(new ApplicationListener(
+                Bug54807Config.class.getName(), false));
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMapping("/", "default");
+
+        tomcat.start();
+
+        Assert.assertEquals(LifecycleState.STARTED, ctx.getState());
+    }
+
+
+    public static class Bug54807Config extends WsContextListener {
+
+        @Override
+        public void contextInitialized(ServletContextEvent sce) {
+            super.contextInitialized(sce);
+
+            ServerContainer sc =
+                    (ServerContainer) sce.getServletContext().getAttribute(
+                            Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
+
+            ServerEndpointConfig sec = ServerEndpointConfig.Builder.create(
+                    TesterEchoServer.Basic.class, "/{param}").build();
+
+            try {
+                sc.addEndpoint(sec);
+            } catch (DeploymentException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+
+    @Test
+    public void testSpecExample3() throws Exception {
+        WsServerContainer sc =
+                new WsServerContainer(new TesterServletContext());
+
+        ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
+                Object.class, "/a/{var}/c").build();
+        ServerEndpointConfig configB = ServerEndpointConfig.Builder.create(
+                Object.class, "/a/b/c").build();
+        ServerEndpointConfig configC = ServerEndpointConfig.Builder.create(
+                Object.class, "/a/{var1}/{var2}").build();
+
+        sc.addEndpoint(configA);
+        sc.addEndpoint(configB);
+        sc.addEndpoint(configC);
+
+        Assert.assertEquals(configB, sc.findMapping("/a/b/c").getConfig());
+        Assert.assertEquals(configA, sc.findMapping("/a/d/c").getConfig());
+        Assert.assertEquals(configC, sc.findMapping("/a/x/y").getConfig());
+    }
+
+
+    @Test
+    public void testSpecExample4() throws Exception {
+        WsServerContainer sc =
+                new WsServerContainer(new TesterServletContext());
+
+        ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
+                Object.class, "/{var1}/d").build();
+        ServerEndpointConfig configB = ServerEndpointConfig.Builder.create(
+                Object.class, "/b/{var2}").build();
+
+        sc.addEndpoint(configA);
+        sc.addEndpoint(configB);
+
+        Assert.assertEquals(configB, sc.findMapping("/b/d").getConfig());
+    }
+
+
+    @Test(expected = javax.websocket.DeploymentException.class)
+    public void testDuplicatePaths_01() throws Exception {
+        WsServerContainer sc =
+                new WsServerContainer(new TesterServletContext());
+
+        ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
+                Object.class, "/a/b/c").build();
+        ServerEndpointConfig configB = ServerEndpointConfig.Builder.create(
+                Object.class, "/a/b/c").build();
+
+        sc.addEndpoint(configA);
+        sc.addEndpoint(configB);
+    }
+
+
+    @Test(expected = javax.websocket.DeploymentException.class)
+    public void testDuplicatePaths_02() throws Exception {
+        WsServerContainer sc =
+                new WsServerContainer(new TesterServletContext());
+
+        ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
+                Object.class, "/a/b/{var}").build();
+        ServerEndpointConfig configB = ServerEndpointConfig.Builder.create(
+                Object.class, "/a/b/{var}").build();
+
+        sc.addEndpoint(configA);
+        sc.addEndpoint(configB);
+    }
+
+
+    @Test(expected = javax.websocket.DeploymentException.class)
+    public void testDuplicatePaths_03() throws Exception {
+        WsServerContainer sc =
+                new WsServerContainer(new TesterServletContext());
+
+        ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
+                Object.class, "/a/b/{var1}").build();
+        ServerEndpointConfig configB = ServerEndpointConfig.Builder.create(
+                Object.class, "/a/b/{var2}").build();
+
+        sc.addEndpoint(configA);
+        sc.addEndpoint(configB);
+    }
+
+
+    @Test
+    public void testDuplicatePaths_04() throws Exception {
+        WsServerContainer sc =
+                new WsServerContainer(new TesterServletContext());
+
+        ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
+                Object.class, "/a/{var1}/{var2}").build();
+        ServerEndpointConfig configB = ServerEndpointConfig.Builder.create(
+                Object.class, "/a/b/{var2}").build();
+
+        sc.addEndpoint(configA);
+        sc.addEndpoint(configB);
+
+        Assert.assertEquals(configA, sc.findMapping("/a/x/y").getConfig());
+        Assert.assertEquals(configB, sc.findMapping("/a/b/y").getConfig());
+    }
+}
diff --git a/test/webapp-2.3/WEB-INF/tags12.tld b/test/webapp-2.3/WEB-INF/tags12.tld
index 55908d9..c2d02b6 100644
--- a/test/webapp-2.3/WEB-INF/tags12.tld
+++ b/test/webapp-2.3/WEB-INF/tags12.tld
@@ -25,7 +25,7 @@
 
   <tag>
     <name>Echo</name>
-    <tagclass>org.apache.jasper.compiler.TestValidator$Echo</tagclass>
+    <tag-class>org.apache.jasper.compiler.TestValidator$Echo</tag-class>
     <body-content>empty</body-content>
     <attribute>
       <name>echo</name>
diff --git a/test/webapp-2.3/WEB-INF/tags20.tld b/test/webapp-2.3/WEB-INF/tags20.tld
index 9130867..056c484 100644
--- a/test/webapp-2.3/WEB-INF/tags20.tld
+++ b/test/webapp-2.3/WEB-INF/tags20.tld
@@ -14,10 +14,10 @@
   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.
---><taglib xmlns="http://java.sun.com/xml/ns/javaee"
+--><taglib xmlns="http://java.sun.com/xml/ns/j2ee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
-      http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_0.xsd"
+      xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+      http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
       version="2.0">
   <tlib-version>1.0</tlib-version>
   <short-name>Tags20</short-name>
@@ -25,7 +25,7 @@
 
   <tag>
     <name>Echo</name>
-    <tagclass>org.apache.jasper.compiler.TestValidator$Echo</tagclass>
+    <tag-class>org.apache.jasper.compiler.TestValidator$Echo</tag-class>
     <body-content>empty</body-content>
     <attribute>
       <name>echo</name>
diff --git a/test/webapp-2.3/WEB-INF/tags21.tld b/test/webapp-2.3/WEB-INF/tags21.tld
index 7b8c3c5..4a19675 100644
--- a/test/webapp-2.3/WEB-INF/tags21.tld
+++ b/test/webapp-2.3/WEB-INF/tags21.tld
@@ -25,7 +25,7 @@
 
   <tag>
     <name>Echo</name>
-    <tagclass>org.apache.jasper.compiler.TestValidator$Echo</tagclass>
+    <tag-class>org.apache.jasper.compiler.TestValidator$Echo</tag-class>
     <body-content>empty</body-content>
     <attribute>
       <name>echo</name>
diff --git a/test/webapp-2.4/WEB-INF/tags20.tld b/test/webapp-2.4/WEB-INF/tags20.tld
index 9130867..056c484 100644
--- a/test/webapp-2.4/WEB-INF/tags20.tld
+++ b/test/webapp-2.4/WEB-INF/tags20.tld
@@ -14,10 +14,10 @@
   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.
---><taglib xmlns="http://java.sun.com/xml/ns/javaee"
+--><taglib xmlns="http://java.sun.com/xml/ns/j2ee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
-      http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_0.xsd"
+      xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+      http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
       version="2.0">
   <tlib-version>1.0</tlib-version>
   <short-name>Tags20</short-name>
@@ -25,7 +25,7 @@
 
   <tag>
     <name>Echo</name>
-    <tagclass>org.apache.jasper.compiler.TestValidator$Echo</tagclass>
+    <tag-class>org.apache.jasper.compiler.TestValidator$Echo</tag-class>
     <body-content>empty</body-content>
     <attribute>
       <name>echo</name>
diff --git a/test/webapp-2.4/WEB-INF/tags21.tld b/test/webapp-2.4/WEB-INF/tags21.tld
index 7b8c3c5..4a19675 100644
--- a/test/webapp-2.4/WEB-INF/tags21.tld
+++ b/test/webapp-2.4/WEB-INF/tags21.tld
@@ -25,7 +25,7 @@
 
   <tag>
     <name>Echo</name>
-    <tagclass>org.apache.jasper.compiler.TestValidator$Echo</tagclass>
+    <tag-class>org.apache.jasper.compiler.TestValidator$Echo</tag-class>
     <body-content>empty</body-content>
     <attribute>
       <name>echo</name>
diff --git a/test/webapp-2.4/WEB-INF/web.xml b/test/webapp-2.4/WEB-INF/web.xml
index 3568cbb..9d5cff6 100644
--- a/test/webapp-2.4/WEB-INF/web.xml
+++ b/test/webapp-2.4/WEB-INF/web.xml
@@ -15,10 +15,10 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
-                      http://java.sun.com/xml/ns/javaee/web-app_2_4.xsd"
+  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+                      http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
   version="2.4">
   <display-name>Tomcat Servlet 2.4 Tests</display-name>
   <description>
diff --git a/test/webapp-2.5/WEB-INF/tags20.tld b/test/webapp-2.5/WEB-INF/tags20.tld
index 9130867..056c484 100644
--- a/test/webapp-2.5/WEB-INF/tags20.tld
+++ b/test/webapp-2.5/WEB-INF/tags20.tld
@@ -14,10 +14,10 @@
   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.
---><taglib xmlns="http://java.sun.com/xml/ns/javaee"
+--><taglib xmlns="http://java.sun.com/xml/ns/j2ee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
-      http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_0.xsd"
+      xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+      http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
       version="2.0">
   <tlib-version>1.0</tlib-version>
   <short-name>Tags20</short-name>
@@ -25,7 +25,7 @@
 
   <tag>
     <name>Echo</name>
-    <tagclass>org.apache.jasper.compiler.TestValidator$Echo</tagclass>
+    <tag-class>org.apache.jasper.compiler.TestValidator$Echo</tag-class>
     <body-content>empty</body-content>
     <attribute>
       <name>echo</name>
diff --git a/test/webapp-2.5/WEB-INF/tags21.tld b/test/webapp-2.5/WEB-INF/tags21.tld
index 7b8c3c5..4a19675 100644
--- a/test/webapp-2.5/WEB-INF/tags21.tld
+++ b/test/webapp-2.5/WEB-INF/tags21.tld
@@ -25,7 +25,7 @@
 
   <tag>
     <name>Echo</name>
-    <tagclass>org.apache.jasper.compiler.TestValidator$Echo</tagclass>
+    <tag-class>org.apache.jasper.compiler.TestValidator$Echo</tag-class>
     <body-content>empty</body-content>
     <attribute>
       <name>echo</name>
diff --git a/test/webapp-3.0-fragments/WEB-INF/lib/resources.jar b/test/webapp-3.0-fragments/WEB-INF/lib/resources.jar
index e8524e6..dffd58e 100644
Binary files a/test/webapp-3.0-fragments/WEB-INF/lib/resources.jar and b/test/webapp-3.0-fragments/WEB-INF/lib/resources.jar differ
diff --git a/webapps/examples/index.html b/test/webapp-3.0/WEB-INF/tags/bug55198.tagx
similarity index 60%
copy from webapps/examples/index.html
copy to test/webapp-3.0/WEB-INF/tags/bug55198.tagx
index 475dc9f..aef4a1e 100644
--- a/webapps/examples/index.html
+++ b/test/webapp-3.0/WEB-INF/tags/bug55198.tagx
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
 <!--
-  Licensed to the Apache Software Foundation (ASF) under one or more
+ Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You under the Apache License, Version 2.0
@@ -14,17 +15,9 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<HTML><HEAD><TITLE>Apache Tomcat Examples</TITLE>
-<META http-equiv=Content-Type content="text/html">
-</HEAD>
-<BODY>
-<P>
-<H3>Apache Tomcat Examples</H3>
-<P></P>
-<ul>
-<li><a href="servlets">Servlets examples</a></li>
-<li><a href="jsp">JSP Examples</a></li>
-<li><a href="websocket">WebSocket Examples</a></li>
-</ul>
-</BODY></HTML>
+<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0">
+<jsp:directive.tag body-content="scriptless" />
+<a href="#" onclick="window.alert("${'foo'}")">foo</a>
+<a href="#" onclick="window.alert("bar")">bar</a>
+<jsp:doBody />
+</jsp:root>
diff --git a/test/webapp-3.0/WEB-INF/tags20.tld b/test/webapp-3.0/WEB-INF/tags20.tld
index 9130867..056c484 100644
--- a/test/webapp-3.0/WEB-INF/tags20.tld
+++ b/test/webapp-3.0/WEB-INF/tags20.tld
@@ -14,10 +14,10 @@
   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.
---><taglib xmlns="http://java.sun.com/xml/ns/javaee"
+--><taglib xmlns="http://java.sun.com/xml/ns/j2ee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
-      http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_0.xsd"
+      xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+      http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
       version="2.0">
   <tlib-version>1.0</tlib-version>
   <short-name>Tags20</short-name>
@@ -25,7 +25,7 @@
 
   <tag>
     <name>Echo</name>
-    <tagclass>org.apache.jasper.compiler.TestValidator$Echo</tagclass>
+    <tag-class>org.apache.jasper.compiler.TestValidator$Echo</tag-class>
     <body-content>empty</body-content>
     <attribute>
       <name>echo</name>
diff --git a/test/webapp-3.0/WEB-INF/tags21.tld b/test/webapp-3.0/WEB-INF/tags21.tld
index 7b8c3c5..4a19675 100644
--- a/test/webapp-3.0/WEB-INF/tags21.tld
+++ b/test/webapp-3.0/WEB-INF/tags21.tld
@@ -25,7 +25,7 @@
 
   <tag>
     <name>Echo</name>
-    <tagclass>org.apache.jasper.compiler.TestValidator$Echo</tagclass>
+    <tag-class>org.apache.jasper.compiler.TestValidator$Echo</tag-class>
     <body-content>empty</body-content>
     <attribute>
       <name>echo</name>
diff --git a/test/webapp-3.0/WEB-INF/web.xml b/test/webapp-3.0/WEB-INF/web.xml
index 64b9aa5..d8f968c 100644
--- a/test/webapp-3.0/WEB-INF/web.xml
+++ b/test/webapp-3.0/WEB-INF/web.xml
@@ -43,9 +43,9 @@
   </filter-mapping>
   <filter-mapping>
     <filter-name>Bug49922</filter-name>
+    <servlet-name>Bug49922Target</servlet-name>
     <dispatcher>FORWARD</dispatcher>
     <dispatcher>INCLUDE</dispatcher>
-    <servlet-name>Bug49922Target</servlet-name>
   </filter-mapping>
   <servlet>
     <servlet-name>Bug49922Forward</servlet-name>
@@ -98,9 +98,9 @@
 
   <jsp-config>
     <jsp-property-group>
-      <default-content-type>text/plain</default-content-type>
       <url-pattern>/bug49nnn/bug49726a.jsp</url-pattern>
       <url-pattern>/bug49nnn/bug49726b.jsp</url-pattern>
+      <default-content-type>text/plain</default-content-type>
     </jsp-property-group>
   </jsp-config>
 
@@ -129,8 +129,8 @@
   <env-entry>
     <description>Resource for testing bug 53465</description>
     <env-entry-name>bug53465</env-entry-name>
-    <env-entry-value>10</env-entry-value>
     <env-entry-type>java.lang.Integer</env-entry-type>
+    <env-entry-value>10</env-entry-value>
     <mapped-name>Bug53465MappedName</mapped-name>
   </env-entry>
 
diff --git a/webapps/examples/index.html b/test/webapp-3.0/bug5nnnn/bug55198.jsp
similarity index 60%
copy from webapps/examples/index.html
copy to test/webapp-3.0/bug5nnnn/bug55198.jsp
index 475dc9f..9820979 100644
--- a/webapps/examples/index.html
+++ b/test/webapp-3.0/bug5nnnn/bug55198.jsp
@@ -1,5 +1,5 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one or more
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You under the Apache License, Version 2.0
@@ -13,18 +13,12 @@
   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.
--->
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<HTML><HEAD><TITLE>Apache Tomcat Examples</TITLE>
-<META http-equiv=Content-Type content="text/html">
-</HEAD>
-<BODY>
-<P>
-<H3>Apache Tomcat Examples</H3>
-<P></P>
-<ul>
-<li><a href="servlets">Servlets examples</a></li>
-<li><a href="jsp">JSP Examples</a></li>
-<li><a href="websocket">WebSocket Examples</a></li>
-</ul>
-</BODY></HTML>
+--%>
+<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>
+<html>
+  <head><title>Bug 55198 test case</title></head>
+  <body>
+    <p><tags:bug55198 /></p>
+  </body>
+</html>
+
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 24f2f5c..6e088b5 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -55,7 +55,412 @@
   They eventually become mixed with the numbered issues. (I.e., numbered
   issues to not "pop up" wrt. others).
 -->
-<section name="Tomcat 7.0.42 (markt)">
+<section name="Tomcat 7.0.45 (violetagg)">
+  <subsection name="Catalina">
+    <changelog>
+      <add>
+        <bug>55576</bug>: Preserve the order in which request parameters were
+        received when accessing them via the Servlet API. (markt)
+      </add>
+    </changelog>
+  </subsection>
+  <subsection name="Cluster">
+    <changelog>
+      <fix>
+        Logger instance of cluster session manager is changed to non-static in
+        order to enable logging of each application. (kfujino)
+      </fix>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 7.0.44 (violetagg)" rtext="not released">
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        <bug>55582</bug>: Correct concurrency issue that can result in two
+        instances of JspServletWrapper being created for one tag Patch provided
+        by Sheldon Shao. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 7.0.43 (violetagg)" rtext="not released">
+  <subsection name="Catalina">
+    <changelog>
+      <add>
+        <bug>51526</bug>: <code>o.a.catalina.startup.Tomcat#addWebapp</code>
+        methods now process the web application's <code>META-INF/context.xml</code>
+        when it is available in the provided path. (violetagg)
+      </add>
+      <fix>
+        <bug>55186</bug>: Ensure local name is recycled between requests so IP
+        virtual hosting works correctly. (markt)
+      </fix>
+      <fix>
+        <bug>55210</bug>: Correct the processing of the provider-configuration
+        file for <code>javax.servlet.ServletContainerInitializer</code> in the
+        resource directory <code>META-INF/services</code> when this file
+        contains comments and multiple SCIs. Patch provided by Nick Williams.
+        (violetagg)
+      </fix>
+      <fix>
+        <bug>55230</bug>: Use the correct resource path when obtaining an
+        InputStream for resources served by a ProxyDirContext. (markt)
+      </fix>
+      <fix>
+        Ensure that the JAR scanning process scans the Apache Log4j version 2
+        JARs. Patch provided by Nick Williams. (markt)
+      </fix>
+      <fix>
+        <bug>55261</bug>: Fix failing unit test for file upload checks when
+        running on platform / JVM combinations that have large network buffers.
+        (markt)
+      </fix>
+      <fix>
+        <bug>55268</bug>: Added optional --service-start-wait-time
+        command-line option to change service start wait time from default of 10
+        seconds.
+      </fix>
+      <fix>
+        The <code>contextClass</code> attribute of <code>HostConfig</code>
+        refers to the value of the <code>contextClass</code> attribute of Host.
+        (kfujino)
+      </fix>
+      <fix>
+        <bug>55331</bug>: Dispatching to an asychronous servlet from
+        <code>AsyncListener.onTimeout()</code> should not trigger an
+        <code>IllegalStateException</code>. (markt)
+      </fix>
+      <fix>
+        <bug>55333</bug>: Correct a regression in the fix for <bug>55071</bug>.
+        (markt)
+      </fix>
+      <fix>
+        When using a security manager, ensure that calls to the ServletContext
+        that are routed via an <code>AccessController.doPrivileged</code> block
+        do not result in a call to a different underlying method on the
+        ServletContext. (markt)
+      </fix>
+      <fix>
+        <bug>55354</bug>: Ensure that the naming context environment parameters
+        are restored after associating the Principle with the user name. Based
+        on patch provided by Richard Begg. (violetagg)
+      </fix>
+      <fix>
+        <bug>55357</bug>: Ensure the web application class loader is set as a
+        thread context class loader during session deserialization. (violetagg)
+      </fix>
+      <fix>
+        <bug>55404</bug>: Log warnings about using security roles in web.xml
+        without defining them as warnings. (markt)
+      </fix>
+      <fix>
+        <bug>55439</bug>: Don't try a forced stop when <code>stop
+        -force</code> is used if Tomcat has already been stopped. This avoids
+        error messages when the PID file has been cleared. If a forced stop is
+        required, improve handling of the case when the PID file can be read
+        from or written to but not deleted. (markt)
+      </fix>
+      <fix>
+        <bug>55454</bug>: Avoid NPE when parsing an incorrect content type.
+        (violetagg)
+      </fix>
+      <update>
+        Back-port the JSR-356 Java WebSocket 1.0 implementation from Tomcat 8.
+        Note that use of this functionality requires Java 7. (markt)
+      </update>
+      <update>
+        Deprecate the Tomcat proprietary WebSocket API in favour of the new
+        JSR-356 implementation. (markt) 
+      </update>
+      <fix>
+        <bug>55494</bug>: Reduce severity of log message from warning to
+        information for JNDI Realm connection issues where the JNDI Realm
+        automatically re-tries the action that failed. Make clear in the log
+        message that the action is being re-tried. (markt)
+      </fix>
+      <fix>
+        Correct several incorrect formats of <code>JdkLoggerFormatter</code>.
+        (kfujino)
+      </fix>
+      <fix>
+        <bug>55521</bug>: Ensure that calls to
+        <code>HttpSession.invalidate()</code> do not return until the session
+        has been invalidated. Also ensure that checks on the validity of a
+        session return a result consistent with any previous call to
+        <code>HttpSession.invalidate()</code>. (markt)
+      </fix>
+      <fix>
+        <bug>55524</bug>: Refactor to avoid a possible deadlock when handling an
+        <code>IOException</code> during output when using Tomcat'
+        proprietary (and deprecated) WebSocket API. (markt)
+      </fix>
+      <fix>
+        The loaded attribute never exists in <code>PersistentManager</code>.
+        isLoaded is defined as operation in mbeans-descriptors. (kfujino)
+      </fix>
+      <add>
+          Added logging of logging.properties location when system property
+          <code>org.apache.juli.ClassLoaderLogManager.debug=true</code>
+          is set.
+      </add>
+      <fix>
+        <bug>55570</bug>: Correctly log exceptions for all error conditions in
+        the SPNEGO authenticator. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        <bug>55228</bug>: Allow web applications to set a HTTP Date header.
+        (markt)
+      </fix>
+      <add>
+        Expose the current connection count for each protocol handler via JMX.
+        (markt)
+      </add>
+      <fix>
+        <bug>55267</bug>: If an application configures a timeout for a Comet
+        connection ensure it is only used for read and not write operations.
+        This prevents a long timeout delaying the closing of the socket
+        associated with a Comet connection after an error occurs. (markt)
+      </fix>
+      <fix>
+        Ensure that <code>java.lang.VirtualMachineError</code>s are not
+        swallowed when using the HTTP or AJP NIO connectors. (markt)
+      </fix>
+      <fix>
+        <bug>55399</bug>: Use the response locale to select the language to use
+        for the status message in the HTTP response. (markt)
+      </fix>
+      <update>
+        Refactor the connectors to support the new JSR-356 Java WebSocket
+        1.0 implementation. The most noticeable change is that the AJP
+        APR/native and HTTP APR/native connectors no longer support multiple
+        poller threads. Both connectors now use a single poller thread. (markt)  
+      </update>
+      <fix>
+        Internally, content length is managed as a <code>long</code>. Fix a few
+        places in the AJP connector where this was restricted to an
+        <code>int</code>. (markt)
+      </fix>
+      <fix>
+        <bug>55453</bug>: Ensure that the AJP connector does not permit response
+        bodies to be included for responses with status codes and/or request
+        methods that are not permitted to have a response body. (markt)
+      </fix>
+      <fix>
+        <bug>55500</bug>: Don't ignore the value of an asynchronous context
+        timeout when using the AJP NIO connector. (markt)
+      </fix>
+      <fix>
+        Better adherence to RFC2616 for content-length headers. (markt)
+      </fix>
+      <fix>
+        Add support for limiting the size of chunk extensions when using chunked
+        encoding. (markt)
+      </fix>
+      <fix>
+        Update the APR/native connector to version 1.1.28. Make this the minimum
+        acceptable version as the correct behaviour of the JSR-356 WebSocket
+        implementation when using the APR/native HTTP connector depends on a bug
+        fix in the 1.1.28 release. (markt) 
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        <bug>55198</bug>: Ensure attribute values in tagx files that include EL
+        and quoted XML characters are correctly quoted in the output. (markt)
+      </fix>
+      <fix>
+        Ensure that <code>javax.el.ELContext.getContext(Class)</code> will
+        throw <code>NullPointerException</code> when the provided class is
+        null. (violetagg)
+      </fix>
+      <fix>
+        Ensure that <code>FeatureDescriptor</code> objects returned by
+        <code>javax.el.MapELResolver.getFeatureDescriptors(ELContext,Object)</code>
+        will be created with a correct <code>shortDescription</code> - an empty string and
+        a named attribute <code>ELResolver.RESOLVABLE_AT_DESIGN_TIME</code> -
+        true. (violetagg)
+      </fix>
+      <fix>
+        Ensure that <code>FeatureDescriptor</code> objects returned by
+        <code>javax.el.ResourceBundleELResolver.getFeatureDescriptors(ELContext,Object)</code>
+        will be created with a correct <code>shortDescription</code> - an empty
+        string.
+        <code>javax.el.ResourceBundleELResolver.isReadOnly(ELContext,Object,Object)</code>
+        returns true if the base object is an instance of ResourceBundle.
+        (violetagg)
+      </fix>
+      <fix>
+        <bug>55207</bug>: Enforce the restriction that a <jsp:text>
+        element may not contain any sub-elements from any namespace. Patch
+        provided by Jeremy Boynes. (markt)
+      </fix>
+      <fix>
+        Ensure that
+        <code>javax.el.ListELResolver.getFeatureDescriptors(ELContext,Object)</code>
+        will always return null.
+        <code>javax.el.ListELResolver.isReadOnly(ELContext,Object,Object)</code>
+        will return a result when the property cannot be coerced into an
+        integer. (violetagg)
+      </fix>
+      <fix>
+        Ensure that
+        <code>javax.el.ArrayELResolver.getFeatureDescriptors(ELContext,Object)</code>
+        will always return null.
+        <code>javax.el.ArrayELResolver.isReadOnly(ELContext,Object,Object)</code>
+        and
+        <code>javax.el.ArrayELResolver.getType(ELContext,Object,Object)</code>
+        will return a result when the property cannot be coerced into an
+        integer. (violetagg)
+      </fix>
+      <fix>
+        <bug>55309</bug>: Fix concurrency issue with JSP compilation and the
+        tag plug-in manager. Patch provided by Sheldon Shao. (markt)
+      </fix>
+      <fix>
+        Ensure that
+        <code>javax.el.BeanELResolver.getFeatureDescriptors(ELContext,Object)</code>
+        and
+        <code>javax.el.BeanELResolver.getCommonPropertyType(ELContext,Object)</code>
+        do not throw <code>NullPointerException</code> when the provided context
+        is null. (violetagg)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Cluster">
+    <changelog>
+      <add>
+        Add new attribute terminateOnStartFailure. Set to true if you wish to
+        terminate replication map when replication map fails to start.
+        If replication map is terminated, associated context will fail to start.
+        If you set this attribute to false, replication map does not end.
+        It will try to join the map membership in the heartbeat. Default value
+        is false. (kfujino)
+      </add>
+      <fix>
+        Avoid ConcurrentModificationException when sending a heartbeat.
+        (kfujino)
+      </fix>
+      <fix>
+        Avoid NPE when the channel fails to start. (kfujino)
+      </fix>
+      <fix>
+        <bug>55301</bug>: Fix <code>IllegalArgumentException</code> thrown by
+        simple test for McastService. (kfujino)
+      </fix>
+      <fix>
+        <bug>55332</bug>: Fix NPE in <code>FileMessageFactory.main</code> when
+        specify empty file as arguments. (kfujino)
+      </fix>
+      <fix>
+        More definite thread name for <code>MessageDispatch15Interceptor</code>.
+        (kfujino)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Web applications">
+    <changelog>
+      <update>
+        Remove the experimental label from the AJP NIO connector documentation.
+        (markt)
+      </update>
+      <fix>
+        Correctly associated the default resource bundle with the English locale
+        so that requests that specify an Accept-Language of English ahead of
+        French, Spanish or Japanese get the English messages they asked for.
+        (markt) 
+      </fix>
+      <fix>
+        <bug>55469</bug>: Fixed tags that were not properly closed. Based on a
+        patch provided by Larry Shatzer, jr. (violetagg)
+      </fix>
+      <update>
+        The WebSocket examples in the examples web application have been changed
+        to use the new JSR-356 Java WebSocket 1.0 implementation. (markt)
+      </update>
+      <add>
+        Add document for
+        <code>org.apache.catalina.tribes.group.GroupChannel</code>. (kfujino)
+      </add>
+      <fix>
+        Correct Realm Component page of Tomcat documentation. (violetagg)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="jdbc-pool">
+    <changelog>
+      <fix>
+        <bug>54693</bug>: Add a validationQueryTimeout property. Patch provided
+        by Daniel Mikusa. (kfujino)
+      </fix>
+      <fix>
+        <bug>54693#c6</bug>: Avoid NPE caused by <code>createConnection()</code>
+        method returns null. Patch provided by Daniel Mikusa. (kfujino)
+      </fix>
+      <fix>
+        <bug>55342</bug>: Remove unnecessary reset of interrupted flag. If
+        <code>InterruptedException</code> is thrown, the interrupted flag has
+        been cleared. (kfujino)
+      </fix>
+      <fix>
+        <bug>55343</bug>: Add flag to ignore exceptions of connection creation
+        while initializing the pool. (kfujino)
+      </fix>
+      <fix>
+        Add undefined attributes and operations to mbeans-descriptor. (kfujino)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <add>
+        <bug>45428</bug>: Trigger a thread dump written to standard out if
+        Tomcat fails to stop in a timely manner to aid diagnostics. This is only
+        available on platforms that use <code>catalina.sh</code>. (markt)
+      </add>
+      <fix>
+        <bug>55204</bug>: Correct namespace used in Servlet 2.4 test web 
+        application. Patch provided by Jeremy Boynes. (markt)
+      </fix>
+      <fix>
+        <bug>55205</bug>: Reorder elements so web.xml complies with schema for
+        Servlet 3.0 test web application. Patch provided by Jeremy Boynes.
+        (markt)
+      </fix>
+      <fix>
+        <bug>55211</bug>: Correct namespace in TLD files used in test web
+        applications. Rename elements <code>tagclass</code> to
+        <code>tag-class</code> so TLD files complies with DTD/schema. Patch
+        provided by Jeremy Boynes. (violetagg)
+      </fix>
+      <update>
+        Update package renamed version of Commons BCEL to the latest code from
+        Commons BCEL trunk. (markt)
+      </update>
+      <update>
+        Update package renamed version of Commons FileUpload to the latest code
+        from Commons FileUpload trunk. (markt)
+      </update>
+      <fix>
+        <bug>55297</bug>: When looking for the jsvc executable, if an explicit
+        path is not set and it is not found in $CATALINA_BASE, look in
+        $CATALINA_HOME as well. (markt)
+      </fix>
+      <fix>
+        <bug>55336</bug>: Correctly escape parameters passed to eval in the
+        catalina.sh script to ensure that Tomcat starts when installed on a path
+        that contains multiple consecutive spaces. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 7.0.42 (markt)" rtext="2013-07-05">
   <subsection name="Catalina">
     <changelog>
       <fix>
diff --git a/webapps/docs/config/ajp.xml b/webapps/docs/config/ajp.xml
index 2d0fca7..2b7fde6 100644
--- a/webapps/docs/config/ajp.xml
+++ b/webapps/docs/config/ajp.xml
@@ -263,9 +263,6 @@
   following attributes in addition to the common Connector attributes listed
   above.</p>
 
-  <p><strong><font color="red">WARNING</font></strong>: The NIO connector for
-  AJP is experimental.</p>
-
   <attributes>
 
     <attribute name="acceptCount" required="false">
diff --git a/webapps/docs/config/cluster-channel.xml b/webapps/docs/config/cluster-channel.xml
index 0322735..18422e2 100644
--- a/webapps/docs/config/cluster-channel.xml
+++ b/webapps/docs/config/cluster-channel.xml
@@ -96,6 +96,30 @@
 
   </subsection>
 
+  <subsection name="org.apache.catalina.tribes.group.GroupChannel Attributes">
+
+    <attributes>
+
+      <attribute name="heartbeat" required="false">
+         Flag whether the channel manages its own heartbeat.
+         If set to true,  the channel start a local thread for the heart beat.
+         If set this flag to false, you must set SimpleTcpCluster#heartbeatBackgroundEnabled
+         to true. default value is true.
+      </attribute>
+
+      <attribute name="heartbeatSleeptime" required="false">
+        If heartbeat == true, specifies the interval of heartbeat thread in milliseconds.
+      </attribute>
+
+      <attribute name="optionCheck" required="false">
+        If set to true, the GroupChannel will check the option flags that each
+        interceptor is using. Reports an error if two interceptor share the same
+        flag.
+      </attribute>
+
+    </attributes>
+
+  </subsection>
 
 </section>
 
diff --git a/webapps/docs/config/cluster-manager.xml b/webapps/docs/config/cluster-manager.xml
index 7842a80..6f8cb60 100644
--- a/webapps/docs/config/cluster-manager.xml
+++ b/webapps/docs/config/cluster-manager.xml
@@ -146,6 +146,13 @@
         another map.
         Default value is <code>15000</code> milliseconds.
       </attribute>
+      <attribute name="terminateOnStartFailure" required="false">
+        Set to true if you wish to terminate replication map when replication
+        map fails to start. If replication map is terminated, associated context
+        will fail to start. If you set this attribute to false, replication map
+        does not end. It will try to join the map membership in the heartbeat.
+        Default value is <code>false</code> .
+      </attribute>
     </attributes>
   </subsection>
 </section>
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index 246de98..a7be71e 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -399,6 +399,12 @@
       and connections are not counted.</p>
     </attribute>
 
+    <attribute name="maxExtensionSize" required="false">
+      <p>Limits the total length of chunk extensions in chunked HTTP requests.
+      If the value is <code>-1</code>, no limit will be imposed. If not
+      specified, the default value of <code>8192</code> will be used.</p>
+    </attribute>
+
     <attribute name="maxHttpHeaderSize" required="false">
       <p>The maximum size of the request and response HTTP header, specified
       in bytes. If not specified, this attribute is set to 8192 (8 KB).</p>
@@ -778,13 +784,6 @@
         connections. This is a synonym for maxConnections.</p>
       </attribute>
 
-      <attribute name="pollerThreadCount" required="false">
-        <p>Number of threads used to poll kept alive connections. On Windows the
-        default is chosen so that the sockets managed by each thread is
-        less than 1024. For Linux the default is 1. Changing the default on
-        Windows is likely to have a negative performance impact.</p>
-      </attribute>
-
       <attribute name="pollTime" required="false">
         <p>Duration of a poll call in microseconds. Lowering this value will
         slightly decrease latency of connections being kept alive in some cases,
@@ -803,13 +802,6 @@
         specified amount. The default value is 1024.</p>
       </attribute>
 
-      <attribute name="sendfileThreadCount" required="false">
-        <p>Number of threads used service sendfile sockets. On Windows the
-        default is chosen so that the sockets managed by each thread is
-        less than 1024. For Linux the default is 1. Changing the default on
-        Windows is likely to have a negative performance impact.</p>
-      </attribute>
-
       <attribute name="threadPriority" required="false">
         <p>(int)The priority of the acceptor and poller threads.
         The default value is <code>5</code> (the value of the
diff --git a/webapps/docs/realm-howto.xml b/webapps/docs/realm-howto.xml
index 74d3a6d..e9d71a6 100644
--- a/webapps/docs/realm-howto.xml
+++ b/webapps/docs/realm-howto.xml
@@ -81,7 +81,7 @@ a servlet container to some existing authentication database or mechanism
 that already exists in the production environment.  Therefore, Tomcat
 defines a Java interface (<code>org.apache.catalina.Realm</code>) that
 can be implemented by "plug in" components to establish this connection.
-Five standard plug-ins are provided, supporting connections to various
+Six standard plug-ins are provided, supporting connections to various
 sources of authentication information:</p>
 <ul>
 <li><a href="#JDBCRealm">JDBCRealm</a> - Accesses authentication information
diff --git a/webapps/docs/web-socket-howto.xml b/webapps/docs/web-socket-howto.xml
index 4ee87e0..fbb5302 100644
--- a/webapps/docs/web-socket-howto.xml
+++ b/webapps/docs/web-socket-howto.xml
@@ -34,19 +34,66 @@
 
 <section name="Overview">
 <p>Tomcat provides support for WebSocket as defined by
-   <a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a>. This feature is
-   not yet finalised and you are encouraged to provide feedback in the form
-   of bug reports (via
-   <a href="https://issues.apache.org/bugzilla">Bugzilla</a>), suggested API
-   changes (via the <a href="mailto:dev at tomcat.apache.org">dev list</a>) or
-   other comments (again via the <a href="mailto:dev at tomcat.apache.org">dev
-   list</a>).</p>
+   <a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a>.</p>
 </section>
 
 <section name="Application development">
-<p>The API used for application development has not yet been finalised. Rather
-   than document something here that will quickly be out of date, please see the
-   Javadoc for the
+<p>Tomcat implements the Java WebSocket 1.0 API defined by <a
+   href="http://www.jcp.org/en/jsr/detail?id=356">JSR-356</a>.</p>
+
+<p>There are several example applications that demonstrate how the WebSocket API
+   can be used. You will need to look at both the client side <a
+   href="http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/websocket/">
+   HTML</a> and the server side <a
+   href="http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/">
+   code</a>.</p>
+</section>
+
+<section name="Tomcat WebSocket specific configuration">
+<p>The JSR-356 Java WebSocket 1.0 implementation is only available when Tomcat
+   is running on Java 7 or later.</p>
+
+<p>Tomcat provides a number of Tomcat specific configuration options for
+   WebSocket. It is anticipated that these will be absorbed into the WebSocket
+   specification over time.</p>
+
+<p>The write timeout used when sending WebSocket messages in blocking mode
+   defaults to 20000 milliseconds (20 seconds). This may be changed by setting
+   the property <code>org.apache.tomcat.websocket.BLOCKING_SEND_TIMEOUT</code>
+   in the user properties collection attached to the WebSocket session. The
+   value assigned to this property should be a <code>Long</code> and represents
+   the timeout to use in milliseconds. For an infinite timeout, use
+   <code>-1</code>.</p>
+
+<p>The default buffer size for binary messages is 8192 bytes. This may be
+   changed for a web application by setting the servlet context initialization
+   parameter <code>org.apache.tomcat.websocket.binaryBufferSize</code> to the
+   desired value in bytes.</p>
+
+<p>The default buffer size for text messages is 8192 bytes. This may be
+   changed for a web application by setting the servlet context initialization
+   parameter <code>org.apache.tomcat.websocket.textBufferSize</code> to the
+   desired value in bytes.</p>
+
+<p>The Java WebSocket specification 1.0 does not permit programmatic deployment
+   after the first endpoint has started a WebSocket handshake. By default,
+   Tomcat continues to permit additional programmatic deployment. This
+   behavior is controlled by the
+   <code>org.apache.tomcat.websocket.noAddAfterHandshake</code> servlet context
+   initialization parameter. The default may be changed by setting the
+   <code>org.apache.tomcat.websocket.STRICT_SPEC_COMPLIANCE</code> system
+   property to <code>true</code> but any explicit setting on the servlet context
+   will always take priority.</p>
+</section>
+
+<section name="Deprecated proprietary API">
+
+<p>Prior to the development of JRS-356, Tomcat provided a proprietary WebSocket
+   API. This API has been deprecated in Tomcat 7 and will be removed in Tomcat
+   8. There is unlikely to be any further development of this proprietary API
+   apart from bug fixes.</p>
+
+<p>For information on this API, please see the Javadoc for the
    <a href="api/index.html?org/apache/catalina/websocket/package-summary.html">
    <code>org.apache.catalina.websocket</code></a> package. The Javadoc
    pages are not included with Tomcat binary distributions. To view them
@@ -58,14 +105,12 @@
 
 <p>There are also several example applications that demonstrate how the
    WebSocket API can be used. You'll need to look at both the client side <a
-   href="http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/examples/websocket/">
-   html</a> and the server side <a
-   href="http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/">
-   code</a>.</p>
-
-<p>Do keep in mind that the API is fluid and is likely to change, possibly
-   significantly, between point releases. The documentation will be updated once
-   the API is considered stable (unlikely to be until JSR 356 is complete).</p>
+   href="http://svn.apache.org/viewvc/tomcat/tc7.0.x/tags/TOMCAT_7_0_42/webapps/examples/websocket/">
+   HTML</a> and the server side <a
+   href="http://svn.apache.org/viewvc/tomcat/tc7.0.x/tags/TOMCAT_7_0_42/webapps/examples/WEB-INF/classes/websocket/">
+   code</a>. Note that as of 7.0.43, these sample applications have been
+   refactored to use the JSR-356 WebSocket implementation so the links above are
+   for the 7.0.42 tag.</p>
 </section>
 
 
diff --git a/webapps/examples/WEB-INF/classes/websocket/chat/ChatAnnotation.java b/webapps/examples/WEB-INF/classes/websocket/chat/ChatAnnotation.java
new file mode 100644
index 0000000..9735c09
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/websocket/chat/ChatAnnotation.java
@@ -0,0 +1,92 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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 websocket.chat;
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.websocket.OnClose;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import util.HTMLFilter;
+
+ at ServerEndpoint(value = "/websocket/chat")
+public class ChatAnnotation {
+
+    private static final String GUEST_PREFIX = "Guest";
+    private static final AtomicInteger connectionIds = new AtomicInteger(0);
+    private static final Set<ChatAnnotation> connections =
+            new CopyOnWriteArraySet<ChatAnnotation>();
+
+    private final String nickname;
+    private Session session;
+
+    public ChatAnnotation() {
+        nickname = GUEST_PREFIX + connectionIds.getAndIncrement();
+    }
+
+
+    @OnOpen
+    public void start(Session session) {
+        this.session = session;
+        connections.add(this);
+        String message = String.format("* %s %s", nickname, "has joined.");
+        broadcast(message);
+    }
+
+
+    @OnClose
+    public void end() {
+        connections.remove(this);
+        String message = String.format("* %s %s",
+                nickname, "has disconnected.");
+        broadcast(message);
+    }
+
+
+    @OnMessage
+    public void incoming(String message) {
+        // Never trust the client
+        String filteredMessage = String.format("%s: %s",
+                nickname, HTMLFilter.filter(message.toString()));
+        broadcast(filteredMessage);
+    }
+
+
+    private static void broadcast(String msg) {
+        for (ChatAnnotation client : connections) {
+            try {
+                client.session.getBasicRemote().sendText(msg);
+            } catch (IOException e) {
+                connections.remove(client);
+                try {
+                    client.session.close();
+                } catch (IOException e1) {
+                    // Ignore
+                }
+                String message = String.format("* %s %s",
+                        client.nickname, "has been disconnected.");
+                broadcast(message);
+            }
+        }
+    }
+}
diff --git a/webapps/examples/WEB-INF/classes/websocket/echo/EchoAnnotation.java b/webapps/examples/WEB-INF/classes/websocket/echo/EchoAnnotation.java
new file mode 100644
index 0000000..6a04b46
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/websocket/echo/EchoAnnotation.java
@@ -0,0 +1,70 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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 websocket.echo;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import javax.websocket.OnMessage;
+import javax.websocket.PongMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+ at ServerEndpoint("/websocket/echoAnnotation")
+public class EchoAnnotation {
+
+    @OnMessage
+    public void echoTextMessage(Session session, String msg, boolean last) {
+        try {
+            if (session.isOpen()) {
+                session.getBasicRemote().sendText(msg, last);
+            }
+        } catch (IOException e) {
+            try {
+                session.close();
+            } catch (IOException e1) {
+                // Ignore
+            }
+        }
+    }
+
+    @OnMessage
+    public void echoBinaryMessage(Session session, ByteBuffer bb,
+            boolean last) {
+        try {
+            if (session.isOpen()) {
+                session.getBasicRemote().sendBinary(bb, last);
+            }
+        } catch (IOException e) {
+            try {
+                session.close();
+            } catch (IOException e1) {
+                // Ignore
+            }
+        }
+    }
+
+    /**
+     * Process a received pong. This is a NO-OP.
+     *
+     * @param pm    Ignored.
+     */
+    @OnMessage
+    public void echoPongMessage(PongMessage pm) {
+        // NO-OP
+    }
+}
diff --git a/webapps/examples/WEB-INF/classes/websocket/echo/EchoEndpoint.java b/webapps/examples/WEB-INF/classes/websocket/echo/EchoEndpoint.java
new file mode 100644
index 0000000..374add1
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/websocket/echo/EchoEndpoint.java
@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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 websocket.echo;
+
+import java.io.IOException;
+
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
+
+public class EchoEndpoint extends Endpoint{
+
+    @Override
+    public void onOpen(Session session, EndpointConfig endpointConfig) {
+        RemoteEndpoint.Basic remoteEndpointBasic = session.getBasicRemote();
+        session.addMessageHandler(new EchoMessageHandler(remoteEndpointBasic));
+    }
+
+    private static class EchoMessageHandler
+            implements MessageHandler.Whole<String> {
+
+        private final RemoteEndpoint.Basic remoteEndpointBasic;
+
+        private EchoMessageHandler(RemoteEndpoint.Basic remoteEndpointBasic) {
+            this.remoteEndpointBasic = remoteEndpointBasic;
+        }
+
+        @Override
+        public void onMessage(String message) {
+            try {
+                if (remoteEndpointBasic != null) {
+                    remoteEndpointBasic.sendText(message);
+                }
+            } catch (IOException e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/webapps/examples/WEB-INF/classes/websocket/echo/WsConfigListener.java b/webapps/examples/WEB-INF/classes/websocket/echo/WsConfigListener.java
new file mode 100644
index 0000000..029881d
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/websocket/echo/WsConfigListener.java
@@ -0,0 +1,46 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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 websocket.echo;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.annotation.WebListener;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+ at WebListener
+public class WsConfigListener implements ServletContextListener {
+
+    @Override
+    public void contextInitialized(ServletContextEvent sce) {
+        ServerContainer sc =
+                (ServerContainer) sce.getServletContext().getAttribute(
+                        "javax.websocket.server.ServerContainer");
+        try {
+            sc.addEndpoint(ServerEndpointConfig.Builder.create(
+                    EchoEndpoint.class, "/websocket/echoProgrammatic").build());
+        } catch (DeploymentException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Override
+    public void contextDestroyed(ServletContextEvent sce) {
+        // NO-OP
+    }
+}
diff --git a/webapps/examples/WEB-INF/classes/websocket/snake/Location.java b/webapps/examples/WEB-INF/classes/websocket/snake/Location.java
index fddfdee..acbfeb7 100644
--- a/webapps/examples/WEB-INF/classes/websocket/snake/Location.java
+++ b/webapps/examples/WEB-INF/classes/websocket/snake/Location.java
@@ -29,13 +29,13 @@ public class Location {
     public Location getAdjacentLocation(Direction direction) {
         switch (direction) {
             case NORTH:
-                return new Location(x, y - SnakeWebSocketServlet.GRID_SIZE);
+                return new Location(x, y - SnakeAnnotation.GRID_SIZE);
             case SOUTH:
-                return new Location(x, y + SnakeWebSocketServlet.GRID_SIZE);
+                return new Location(x, y + SnakeAnnotation.GRID_SIZE);
             case EAST:
-                return new Location(x + SnakeWebSocketServlet.GRID_SIZE, y);
+                return new Location(x + SnakeAnnotation.GRID_SIZE, y);
             case WEST:
-                return new Location(x - SnakeWebSocketServlet.GRID_SIZE, y);
+                return new Location(x - SnakeAnnotation.GRID_SIZE, y);
             case NONE:
                 // fall through
             default:
diff --git a/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java b/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java
index e9f3b97..471a351 100644
--- a/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java
+++ b/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java
@@ -17,55 +17,53 @@
 package websocket.snake;
 
 import java.io.IOException;
-import java.nio.CharBuffer;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
 
-import org.apache.catalina.websocket.WsOutbound;
+import javax.websocket.Session;
 
 public class Snake {
 
     private static final int DEFAULT_LENGTH = 5;
 
     private final int id;
-    private final WsOutbound outbound;
+    private final Session session;
 
     private Direction direction;
     private int length = DEFAULT_LENGTH;
     private Location head;
-    private Deque<Location> tail = new ArrayDeque<Location>();
-    private String hexColor;
+    private final Deque<Location> tail = new ArrayDeque<Location>();
+    private final String hexColor;
 
-    public Snake(int id, WsOutbound outbound) {
+    public Snake(int id, Session session) {
         this.id = id;
-        this.outbound = outbound;
-        this.hexColor = SnakeWebSocketServlet.getRandomHexColor();
+        this.session = session;
+        this.hexColor = SnakeAnnotation.getRandomHexColor();
         resetState();
     }
 
     private void resetState() {
         this.direction = Direction.NONE;
-        this.head = SnakeWebSocketServlet.getRandomLocation();
+        this.head = SnakeAnnotation.getRandomLocation();
         this.tail.clear();
         this.length = DEFAULT_LENGTH;
     }
 
     private synchronized void kill() {
         resetState();
-        try {
-            CharBuffer response = CharBuffer.wrap("{'type': 'dead'}");
-            outbound.writeTextMessage(response);
-        } catch (IOException ioe) {
-            // Ignore
-        }
+        sendMessage("{'type': 'dead'}");
     }
 
     private synchronized void reward() {
         length++;
+        sendMessage("{'type': 'kill'}");
+    }
+
+
+    protected void sendMessage(String msg) {
         try {
-            CharBuffer response = CharBuffer.wrap("{'type': 'kill'}");
-            outbound.writeTextMessage(response);
+            session.getBasicRemote().sendText(msg);
         } catch (IOException ioe) {
             // Ignore
         }
@@ -73,17 +71,17 @@ public class Snake {
 
     public synchronized void update(Collection<Snake> snakes) {
         Location nextLocation = head.getAdjacentLocation(direction);
-        if (nextLocation.x >= SnakeWebSocketServlet.PLAYFIELD_WIDTH) {
+        if (nextLocation.x >= SnakeAnnotation.PLAYFIELD_WIDTH) {
             nextLocation.x = 0;
         }
-        if (nextLocation.y >= SnakeWebSocketServlet.PLAYFIELD_HEIGHT) {
+        if (nextLocation.y >= SnakeAnnotation.PLAYFIELD_HEIGHT) {
             nextLocation.y = 0;
         }
         if (nextLocation.x < 0) {
-            nextLocation.x = SnakeWebSocketServlet.PLAYFIELD_WIDTH;
+            nextLocation.x = SnakeAnnotation.PLAYFIELD_WIDTH;
         }
         if (nextLocation.y < 0) {
-            nextLocation.y = SnakeWebSocketServlet.PLAYFIELD_HEIGHT;
+            nextLocation.y = SnakeAnnotation.PLAYFIELD_HEIGHT;
         }
         if (direction != Direction.NONE) {
             tail.addFirst(head);
diff --git a/webapps/examples/WEB-INF/classes/websocket/snake/SnakeAnnotation.java b/webapps/examples/WEB-INF/classes/websocket/snake/SnakeAnnotation.java
new file mode 100644
index 0000000..ba63250
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/websocket/snake/SnakeAnnotation.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 websocket.snake;
+
+import java.awt.Color;
+import java.util.Iterator;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.websocket.OnClose;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+ at ServerEndpoint(value = "/websocket/snake")
+public class SnakeAnnotation {
+
+    public static final int PLAYFIELD_WIDTH = 640;
+    public static final int PLAYFIELD_HEIGHT = 480;
+    public static final int GRID_SIZE = 10;
+
+    private static final AtomicInteger snakeIds = new AtomicInteger(0);
+    private static final Random random = new Random();
+
+
+    private final int id;
+    private Snake snake;
+
+    public static String getRandomHexColor() {
+        float hue = random.nextFloat();
+        // sat between 0.1 and 0.3
+        float saturation = (random.nextInt(2000) + 1000) / 10000f;
+        float luminance = 0.9f;
+        Color color = Color.getHSBColor(hue, saturation, luminance);
+        return '#' + Integer.toHexString(
+                (color.getRGB() & 0xffffff) | 0x1000000).substring(1);
+    }
+
+
+    public static Location getRandomLocation() {
+        int x = roundByGridSize(random.nextInt(PLAYFIELD_WIDTH));
+        int y = roundByGridSize(random.nextInt(PLAYFIELD_HEIGHT));
+        return new Location(x, y);
+    }
+
+
+    private static int roundByGridSize(int value) {
+        value = value + (GRID_SIZE / 2);
+        value = value / GRID_SIZE;
+        value = value * GRID_SIZE;
+        return value;
+    }
+
+    public SnakeAnnotation() {
+        this.id = snakeIds.getAndIncrement();
+    }
+
+
+    @OnOpen
+    public void onOpen(Session session) {
+        this.snake = new Snake(id, session);
+        SnakeTimer.addSnake(snake);
+        StringBuilder sb = new StringBuilder();
+        for (Iterator<Snake> iterator = SnakeTimer.getSnakes().iterator();
+                iterator.hasNext();) {
+            Snake snake = iterator.next();
+            sb.append(String.format("{id: %d, color: '%s'}",
+                    Integer.valueOf(snake.getId()), snake.getHexColor()));
+            if (iterator.hasNext()) {
+                sb.append(',');
+            }
+        }
+        SnakeTimer.broadcast(String.format("{'type': 'join','data':[%s]}",
+                sb.toString()));
+    }
+
+
+    @OnMessage
+    public void onTextMessage(String message) {
+        if ("west".equals(message)) {
+            snake.setDirection(Direction.WEST);
+        } else if ("north".equals(message)) {
+            snake.setDirection(Direction.NORTH);
+        } else if ("east".equals(message)) {
+            snake.setDirection(Direction.EAST);
+        } else if ("south".equals(message)) {
+            snake.setDirection(Direction.SOUTH);
+        }
+    }
+
+
+    @OnClose
+    public void onClose() {
+        SnakeTimer.removeSnake(snake);
+        SnakeTimer.broadcast(String.format("{'type': 'leave', 'id': %d}",
+                Integer.valueOf(id)));
+    }
+}
diff --git a/webapps/examples/WEB-INF/classes/websocket/snake/SnakeTimer.java b/webapps/examples/WEB-INF/classes/websocket/snake/SnakeTimer.java
new file mode 100644
index 0000000..d831c18
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/websocket/snake/SnakeTimer.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 websocket.snake;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Sets up the timer for the multi-player snake game WebSocket example.
+ */
+public class SnakeTimer {
+
+    private static final Log log =
+            LogFactory.getLog(SnakeTimer.class);
+
+    private static Timer gameTimer = null;
+
+    private static final long TICK_DELAY = 100;
+
+    private static final ConcurrentHashMap<Integer, Snake> snakes =
+            new ConcurrentHashMap<Integer, Snake>();
+
+    protected static synchronized void addSnake(Snake snake) {
+        if (snakes.size() == 0) {
+            startTimer();
+        }
+        snakes.put(Integer.valueOf(snake.getId()), snake);
+    }
+
+
+    protected static Collection<Snake> getSnakes() {
+        return Collections.unmodifiableCollection(snakes.values());
+    }
+
+
+    protected static synchronized void removeSnake(Snake snake) {
+        snakes.remove(Integer.valueOf(snake.getId()));
+        if (snakes.size() == 0) {
+            stopTimer();
+        }
+    }
+
+
+    protected static void tick() {
+        StringBuilder sb = new StringBuilder();
+        for (Iterator<Snake> iterator = SnakeTimer.getSnakes().iterator();
+                iterator.hasNext();) {
+            Snake snake = iterator.next();
+            snake.update(SnakeTimer.getSnakes());
+            sb.append(snake.getLocationsJson());
+            if (iterator.hasNext()) {
+                sb.append(',');
+            }
+        }
+        broadcast(String.format("{'type': 'update', 'data' : [%s]}",
+                sb.toString()));
+    }
+
+    protected static void broadcast(String message) {
+        for (Snake snake : SnakeTimer.getSnakes()) {
+            snake.sendMessage(message);
+        }
+    }
+
+
+    public static void startTimer() {
+        gameTimer = new Timer(SnakeTimer.class.getSimpleName() + " Timer");
+        gameTimer.scheduleAtFixedRate(new TimerTask() {
+            @Override
+            public void run() {
+                try {
+                    tick();
+                } catch (RuntimeException e) {
+                    log.error("Caught to prevent timer from shutting down", e);
+                }
+            }
+        }, TICK_DELAY, TICK_DELAY);
+    }
+
+
+    public static void stopTimer() {
+        if (gameTimer != null) {
+            gameTimer.cancel();
+        }
+    }
+}
diff --git a/webapps/examples/WEB-INF/classes/websocket/chat/ChatWebSocketServlet.java b/webapps/examples/WEB-INF/classes/websocket/tc7/chat/ChatWebSocketServlet.java
similarity index 97%
rename from webapps/examples/WEB-INF/classes/websocket/chat/ChatWebSocketServlet.java
rename to webapps/examples/WEB-INF/classes/websocket/tc7/chat/ChatWebSocketServlet.java
index 4b06586..2afd694 100644
--- a/webapps/examples/WEB-INF/classes/websocket/chat/ChatWebSocketServlet.java
+++ b/webapps/examples/WEB-INF/classes/websocket/tc7/chat/ChatWebSocketServlet.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package websocket.chat;
+package websocket.tc7.chat;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -34,7 +34,9 @@ import util.HTMLFilter;
 
 /**
  * Example web socket servlet for chat.
+ * @deprecated See {@link websocket.chat.ChatAnnotation}
  */
+ at Deprecated
 public class ChatWebSocketServlet extends WebSocketServlet {
 
     private static final long serialVersionUID = 1L;
diff --git a/webapps/examples/WEB-INF/classes/websocket/echo/EchoMessage.java b/webapps/examples/WEB-INF/classes/websocket/tc7/echo/EchoMessage.java
similarity index 96%
rename from webapps/examples/WEB-INF/classes/websocket/echo/EchoMessage.java
rename to webapps/examples/WEB-INF/classes/websocket/tc7/echo/EchoMessage.java
index 8a1d981..e7d42e8 100644
--- a/webapps/examples/WEB-INF/classes/websocket/echo/EchoMessage.java
+++ b/webapps/examples/WEB-INF/classes/websocket/tc7/echo/EchoMessage.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package websocket.echo;
+package websocket.tc7.echo;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -26,8 +26,10 @@ import javax.servlet.http.HttpServletRequest;
 import org.apache.catalina.websocket.MessageInbound;
 import org.apache.catalina.websocket.StreamInbound;
 import org.apache.catalina.websocket.WebSocketServlet;
-
-
+/**
+ * @deprecated See {@link websocket.echo.EchoAnnotation}
+ */
+ at Deprecated
 public class EchoMessage extends WebSocketServlet {
 
     private static final long serialVersionUID = 1L;
diff --git a/webapps/examples/WEB-INF/classes/websocket/echo/EchoStream.java b/webapps/examples/WEB-INF/classes/websocket/tc7/echo/EchoStream.java
similarity index 95%
rename from webapps/examples/WEB-INF/classes/websocket/echo/EchoStream.java
rename to webapps/examples/WEB-INF/classes/websocket/tc7/echo/EchoStream.java
index a6acd7c..f14ebac 100644
--- a/webapps/examples/WEB-INF/classes/websocket/echo/EchoStream.java
+++ b/webapps/examples/WEB-INF/classes/websocket/tc7/echo/EchoStream.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package websocket.echo;
+package websocket.tc7.echo;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -26,7 +26,10 @@ import org.apache.catalina.websocket.StreamInbound;
 import org.apache.catalina.websocket.WebSocketServlet;
 import org.apache.catalina.websocket.WsOutbound;
 
-
+/**
+ * @deprecated See {@link websocket.echo.EchoAnnotation}
+ */
+ at Deprecated
 public class EchoStream extends WebSocketServlet {
 
     private static final long serialVersionUID = 1L;
diff --git a/java/javax/annotation/PreDestroy.java b/webapps/examples/WEB-INF/classes/websocket/tc7/snake/Direction.java
similarity index 71%
copy from java/javax/annotation/PreDestroy.java
copy to webapps/examples/WEB-INF/classes/websocket/tc7/snake/Direction.java
index d5be75a..3bd54d6 100644
--- a/java/javax/annotation/PreDestroy.java
+++ b/webapps/examples/WEB-INF/classes/websocket/tc7/snake/Direction.java
@@ -5,27 +5,21 @@
  * The ASF licenses this file to You 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 websocket.tc7.snake;
 
-
-package javax.annotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
- at Target({ElementType.METHOD})
- at Retention(RetentionPolicy.RUNTIME)
-
-public @interface PreDestroy {
-    // No attributes
+/**
+ * @deprecated See {@link websocket.snake.Direction}
+ */
+ at Deprecated
+public enum Direction {
+    NONE, NORTH, SOUTH, EAST, WEST
 }
diff --git a/webapps/examples/WEB-INF/classes/websocket/snake/Location.java b/webapps/examples/WEB-INF/classes/websocket/tc7/snake/Location.java
similarity index 95%
copy from webapps/examples/WEB-INF/classes/websocket/snake/Location.java
copy to webapps/examples/WEB-INF/classes/websocket/tc7/snake/Location.java
index fddfdee..96c91ac 100644
--- a/webapps/examples/WEB-INF/classes/websocket/snake/Location.java
+++ b/webapps/examples/WEB-INF/classes/websocket/tc7/snake/Location.java
@@ -14,8 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package websocket.snake;
+package websocket.tc7.snake;
 
+/**
+ * @deprecated See {@link websocket.snake.Location}
+ */
+ at Deprecated
 public class Location {
 
     public int x;
diff --git a/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java b/webapps/examples/WEB-INF/classes/websocket/tc7/snake/Snake.java
similarity index 97%
copy from webapps/examples/WEB-INF/classes/websocket/snake/Snake.java
copy to webapps/examples/WEB-INF/classes/websocket/tc7/snake/Snake.java
index e9f3b97..949dcf1 100644
--- a/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java
+++ b/webapps/examples/WEB-INF/classes/websocket/tc7/snake/Snake.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package websocket.snake;
+package websocket.tc7.snake;
 
 import java.io.IOException;
 import java.nio.CharBuffer;
@@ -24,6 +24,10 @@ import java.util.Deque;
 
 import org.apache.catalina.websocket.WsOutbound;
 
+/**
+ * @deprecated See {@link websocket.snake.Snake}
+ */
+ at Deprecated
 public class Snake {
 
     private static final int DEFAULT_LENGTH = 5;
diff --git a/webapps/examples/WEB-INF/classes/websocket/snake/SnakeWebSocketServlet.java b/webapps/examples/WEB-INF/classes/websocket/tc7/snake/SnakeWebSocketServlet.java
similarity index 97%
rename from webapps/examples/WEB-INF/classes/websocket/snake/SnakeWebSocketServlet.java
rename to webapps/examples/WEB-INF/classes/websocket/tc7/snake/SnakeWebSocketServlet.java
index bf7e2be..53f3289 100644
--- a/webapps/examples/WEB-INF/classes/websocket/snake/SnakeWebSocketServlet.java
+++ b/webapps/examples/WEB-INF/classes/websocket/tc7/snake/SnakeWebSocketServlet.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package websocket.snake;
+package websocket.tc7.snake;
 
 import java.awt.Color;
 import java.io.IOException;
@@ -40,8 +40,10 @@ import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 
 /**
- * Example web socket servlet for simple multiplayer snake.
+ * Example web socket servlet for simple multi-player snake.
+ * @deprecated See {@link websocket.snake.SnakeAnnotation}
  */
+ at Deprecated
 public class SnakeWebSocketServlet extends WebSocketServlet {
 
     private static final long serialVersionUID = 1L;
diff --git a/webapps/examples/WEB-INF/web.xml b/webapps/examples/WEB-INF/web.xml
index e4e5020..c75eb86 100644
--- a/webapps/examples/WEB-INF/web.xml
+++ b/webapps/examples/WEB-INF/web.xml
@@ -352,19 +352,19 @@
       <servlet-name>stock</servlet-name>
       <url-pattern>/async/stockticker</url-pattern>
     </servlet-mapping>
-
-    <!-- WebSocket Examples -->
+    
+    <!-- WebSocket Examples using Deprecated Tomcat 7 API-->
     <servlet>
       <servlet-name>wsEchoStream</servlet-name>
-      <servlet-class>websocket.echo.EchoStream</servlet-class>
+      <servlet-class>websocket.tc7.echo.EchoStream</servlet-class>
     </servlet>
     <servlet-mapping>
       <servlet-name>wsEchoStream</servlet-name>
-      <url-pattern>/websocket/echoStream</url-pattern>
+      <url-pattern>/websocket/tc7/echoStream</url-pattern>
     </servlet-mapping>
     <servlet>
       <servlet-name>wsEchoMessage</servlet-name>
-      <servlet-class>websocket.echo.EchoMessage</servlet-class>
+      <servlet-class>websocket.tc7.echo.EchoMessage</servlet-class>
       <!-- Uncomment the following block to increase the default maximum
            WebSocket buffer size from 2MB to 20MB which is required for the
            Autobahn test suite to pass fully. -->
@@ -381,23 +381,22 @@
     </servlet>
     <servlet-mapping>
       <servlet-name>wsEchoMessage</servlet-name>
-      <url-pattern>/websocket/echoMessage</url-pattern>
+      <url-pattern>/websocket/tc7/echoMessage</url-pattern>
     </servlet-mapping>
     <servlet>
         <servlet-name>wsChat</servlet-name>
-        <servlet-class>websocket.chat.ChatWebSocketServlet</servlet-class>
+        <servlet-class>websocket.tc7.chat.ChatWebSocketServlet</servlet-class>
     </servlet>
     <servlet-mapping>
         <servlet-name>wsChat</servlet-name>
-        <url-pattern>/websocket/chat</url-pattern>
+        <url-pattern>/websocket/tc7/chat</url-pattern>
     </servlet-mapping>
     <servlet>
       <servlet-name>wsSnake</servlet-name>
-      <servlet-class>websocket.snake.SnakeWebSocketServlet</servlet-class>
+      <servlet-class>websocket.tc7.snake.SnakeWebSocketServlet</servlet-class>
     </servlet>
     <servlet-mapping>
       <servlet-name>wsSnake</servlet-name>
-      <url-pattern>/websocket/snake</url-pattern>
+      <url-pattern>/websocket/tc7/snake</url-pattern>
     </servlet-mapping>
-
 </web-app>
diff --git a/webapps/examples/index.html b/webapps/examples/index.html
index 475dc9f..9039457 100644
--- a/webapps/examples/index.html
+++ b/webapps/examples/index.html
@@ -25,6 +25,8 @@
 <ul>
 <li><a href="servlets">Servlets examples</a></li>
 <li><a href="jsp">JSP Examples</a></li>
-<li><a href="websocket">WebSocket Examples</a></li>
+<li><a href="websocket">WebSocket (JSR356) Examples</a></li>
+<li><a href="websocket-deprecated">WebSocket Examples using the deprecated
+    Apache Tomcat proprietary API</a></li>
 </ul>
 </BODY></HTML>
diff --git a/webapps/examples/websocket-deprecated/chat.html b/webapps/examples/websocket-deprecated/chat.html
new file mode 100644
index 0000000..3ab0b91
--- /dev/null
+++ b/webapps/examples/websocket-deprecated/chat.html
@@ -0,0 +1,125 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+-->
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Apache Tomcat WebSocket Examples: Chat</title>
+    <style type="text/css">
+        input#chat {
+            width: 410px
+        }
+
+        #console-container {
+            width: 400px;
+        }
+
+        #console {
+            border: 1px solid #CCCCCC;
+            border-right-color: #999999;
+            border-bottom-color: #999999;
+            height: 170px;
+            overflow-y: scroll;
+            padding: 5px;
+            width: 100%;
+        }
+
+        #console p {
+            padding: 0;
+            margin: 0;
+        }
+    </style>
+    <script type="text/javascript">
+        var Chat = {};
+
+        Chat.socket = null;
+
+        Chat.connect = (function(host) {
+            if ('WebSocket' in window) {
+                Chat.socket = new WebSocket(host);
+            } else if ('MozWebSocket' in window) {
+                Chat.socket = new MozWebSocket(host);
+            } else {
+                Console.log('Error: WebSocket is not supported by this browser.');
+                return;
+            }
+
+            Chat.socket.onopen = function () {
+                Console.log('Info: WebSocket connection opened.');
+                document.getElementById('chat').onkeydown = function(event) {
+                    if (event.keyCode == 13) {
+                        Chat.sendMessage();
+                    }
+                };
+            };
+
+            Chat.socket.onclose = function () {
+                document.getElementById('chat').onkeydown = null;
+                Console.log('Info: WebSocket closed.');
+            };
+
+            Chat.socket.onmessage = function (message) {
+                Console.log(message.data);
+            };
+        });
+
+        Chat.initialize = function() {
+            if (window.location.protocol == 'http:') {
+                Chat.connect('ws://' + window.location.host + '/examples/websocket/tc7/chat');
+            } else {
+                Chat.connect('wss://' + window.location.host + '/examples/websocket/tc7/chat');
+            }
+        };
+
+        Chat.sendMessage = (function() {
+            var message = document.getElementById('chat').value;
+            if (message != '') {
+                Chat.socket.send(message);
+                document.getElementById('chat').value = '';
+            }
+        });
+
+        var Console = {};
+
+        Console.log = (function(message) {
+            var console = document.getElementById('console');
+            var p = document.createElement('p');
+            p.style.wordWrap = 'break-word';
+            p.innerHTML = message;
+            console.appendChild(p);
+            while (console.childNodes.length > 25) {
+                console.removeChild(console.firstChild);
+            }
+            console.scrollTop = console.scrollHeight;
+        });
+
+        Chat.initialize();
+
+    </script>
+</head>
+<body>
+<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled. Please enable
+    Javascript and reload this page!</h2></noscript>
+<div>
+    <p>
+        <input type="text" placeholder="type and press enter to chat" id="chat">
+    </p>
+    <div id="console-container">
+        <div id="console"></div>
+    </div>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/webapps/examples/websocket/echo.html b/webapps/examples/websocket-deprecated/echo.html
similarity index 98%
copy from webapps/examples/websocket/echo.html
copy to webapps/examples/websocket-deprecated/echo.html
index 052cea7..a5258c1 100644
--- a/webapps/examples/websocket/echo.html
+++ b/webapps/examples/websocket-deprecated/echo.html
@@ -132,10 +132,10 @@
         <div>
             <span>Connect using:</span>
             <!-- echo example using streams on the server side -->
-            <input id="radio1" type="radio" name="group1" value="/examples/websocket/echoStream"
+            <input id="radio1" type="radio" name="group1" value="/examples/websocket/tc7/echoStream"
                    onclick="updateTarget(this.value);"> <label for="radio1">streams</label>
             <!-- echo example using messages on the server side -->
-            <input id="radio2" type="radio" name="group1" value="/examples/websocket/echoMessage"
+            <input id="radio2" type="radio" name="group1" value="/examples/websocket/tc7/echoMessage"
                    onclick="updateTarget(this.value);"> <label for="radio2">messages</label>
         </div>
         <div>
diff --git a/webapps/examples/index.html b/webapps/examples/websocket-deprecated/index.html
similarity index 61%
copy from webapps/examples/index.html
copy to webapps/examples/websocket-deprecated/index.html
index 475dc9f..3947fa8 100644
--- a/webapps/examples/index.html
+++ b/webapps/examples/websocket-deprecated/index.html
@@ -15,16 +15,19 @@
   limitations under the License.
 -->
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<HTML><HEAD><TITLE>Apache Tomcat Examples</TITLE>
-<META http-equiv=Content-Type content="text/html">
-</HEAD>
-<BODY>
-<P>
-<H3>Apache Tomcat Examples</H3>
-<P></P>
+<html>
+<head>
+    <meta http-equiv=Content-Type content="text/html">
+    <title>Apache Tomcat Deprecated WebSocket Examples</title>
+</head>
+<body>
+<h3>Apache Tomcat Deprecated WebSocket Examples</h3>
 <ul>
-<li><a href="servlets">Servlets examples</a></li>
-<li><a href="jsp">JSP Examples</a></li>
-<li><a href="websocket">WebSocket Examples</a></li>
+    <li><a href="echo.html">Echo example</a></li>
+    <li><a href="chat.html">Chat example</a></li>
+    <li><a href="snake.html">Multiplayer snake example</a></li>
 </ul>
-</BODY></HTML>
+<p>This API has been deprecated. The examples are also available using the JSR
+   356 <a href="../websocket/index.html">Java WebSocket 1.0 API</a>.</p> 
+</body>
+</html>
\ No newline at end of file
diff --git a/webapps/examples/websocket-deprecated/snake.html b/webapps/examples/websocket-deprecated/snake.html
new file mode 100644
index 0000000..7704b61
--- /dev/null
+++ b/webapps/examples/websocket-deprecated/snake.html
@@ -0,0 +1,258 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+    <title>Apache Tomcat WebSocket Examples: Multiplayer Snake</title>
+    <style type="text/css">
+        #playground {
+            width: 640px;
+            height: 480px;
+            background-color: #000;
+        }
+
+        #console-container {
+            float: left;
+            margin-left: 15px;
+            width: 300px;
+        }
+
+        #console {
+            border: 1px solid #CCCCCC;
+            border-right-color: #999999;
+            border-bottom-color: #999999;
+            height: 480px;
+            overflow-y: scroll;
+            padding-left: 5px;
+            padding-right: 5px;
+            width: 100%;
+        }
+
+        #console p {
+            padding: 0;
+            margin: 0;
+        }
+    </style>
+</head>
+<body>
+    <noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled. Please enable
+    Javascript and reload this page!</h2></noscript>
+    <div style="float: left">
+        <canvas id="playground" width="640" height="480"></canvas>
+    </div>
+    <div id="console-container">
+        <div id="console"></div>
+    </div>
+    <script type="text/javascript">
+
+        var Game = {};
+
+        Game.fps = 30;
+        Game.socket = null;
+        Game.nextFrame = null;
+        Game.interval = null;
+        Game.direction = 'none';
+        Game.gridSize = 10;
+
+        function Snake() {
+            this.snakeBody = [];
+            this.color = null;
+        }
+
+        Snake.prototype.draw = function(context) {
+            for (var id in this.snakeBody) {
+                context.fillStyle = this.color;
+                context.fillRect(this.snakeBody[id].x, this.snakeBody[id].y, Game.gridSize, Game.gridSize);
+            }
+        };
+
+        Game.initialize = function() {
+            this.entities = [];
+            canvas = document.getElementById('playground');
+            if (!canvas.getContext) {
+                Console.log('Error: 2d canvas not supported by this browser.');
+                return;
+            }
+            this.context = canvas.getContext('2d');
+            window.addEventListener('keydown', function (e) {
+                var code = e.keyCode;
+                if (code > 36 && code < 41) {
+                    switch (code) {
+                        case 37:
+                            if (Game.direction != 'east') Game.setDirection('west');
+                            break;
+                        case 38:
+                            if (Game.direction != 'south') Game.setDirection('north');
+                            break;
+                        case 39:
+                            if (Game.direction != 'west') Game.setDirection('east');
+                            break;
+                        case 40:
+                            if (Game.direction != 'north') Game.setDirection('south');
+                            break;
+                    }
+                }
+            }, false);
+            if (window.location.protocol == 'http:') {
+                Game.connect('ws://' + window.location.host + '/examples/websocket/tc7/snake');
+            } else {
+                Game.connect('wss://' + window.location.host + '/examples/websocket/tc7/snake');
+            }
+        };
+
+        Game.setDirection  = function(direction) {
+            Game.direction = direction;
+            Game.socket.send(direction);
+            Console.log('Sent: Direction ' + direction);
+        };
+
+        Game.startGameLoop = function() {
+            if (window.webkitRequestAnimationFrame) {
+                Game.nextFrame = function () {
+                    webkitRequestAnimationFrame(Game.run);
+                };
+            } else if (window.mozRequestAnimationFrame) {
+                Game.nextFrame = function () {
+                    mozRequestAnimationFrame(Game.run);
+                };
+            } else {
+                Game.interval = setInterval(Game.run, 1000 / Game.fps);
+            }
+            if (Game.nextFrame != null) {
+                Game.nextFrame();
+            }
+        };
+
+        Game.stopGameLoop = function () {
+            Game.nextFrame = null;
+            if (Game.interval != null) {
+                clearInterval(Game.interval);
+            }
+        };
+
+        Game.draw = function() {
+            this.context.clearRect(0, 0, 640, 480);
+            for (var id in this.entities) {
+                this.entities[id].draw(this.context);
+            }
+        };
+
+        Game.addSnake = function(id, color) {
+            Game.entities[id] = new Snake();
+            Game.entities[id].color = color;
+        };
+
+        Game.updateSnake = function(id, snakeBody) {
+            if (typeof Game.entities[id] != "undefined") {
+                Game.entities[id].snakeBody = snakeBody;
+            }
+        };
+
+        Game.removeSnake = function(id) {
+            Game.entities[id] = null;
+            // Force GC.
+            delete Game.entities[id];
+        };
+
+        Game.run = (function() {
+            var skipTicks = 1000 / Game.fps, nextGameTick = (new Date).getTime();
+
+            return function() {
+                while ((new Date).getTime() > nextGameTick) {
+                    nextGameTick += skipTicks;
+                }
+                Game.draw();
+                if (Game.nextFrame != null) {
+                    Game.nextFrame();
+                }
+            };
+        })();
+
+        Game.connect = (function(host) {
+            if ('WebSocket' in window) {
+                Game.socket = new WebSocket(host);
+            } else if ('MozWebSocket' in window) {
+                Game.socket = new MozWebSocket(host);
+            } else {
+                Console.log('Error: WebSocket is not supported by this browser.');
+                return;
+            }
+
+            Game.socket.onopen = function () {
+                // Socket open.. start the game loop.
+                Console.log('Info: WebSocket connection opened.');
+                Console.log('Info: Press an arrow key to begin.');
+                Game.startGameLoop();
+                setInterval(function() {
+                    // Prevent server read timeout.
+                    Game.socket.send('ping');
+                }, 5000);
+            };
+
+            Game.socket.onclose = function () {
+                Console.log('Info: WebSocket closed.');
+                Game.stopGameLoop();
+            };
+
+            Game.socket.onmessage = function (message) {
+                // _Potential_ security hole, consider using json lib to parse data in production.
+                var packet = eval('(' + message.data + ')');
+                switch (packet.type) {
+                    case 'update':
+                        for (var i = 0; i < packet.data.length; i++) {
+                            Game.updateSnake(packet.data[i].id, packet.data[i].body);
+                        }
+                        break;
+                    case 'join':
+                        for (var j = 0; j < packet.data.length; j++) {
+                            Game.addSnake(packet.data[j].id, packet.data[j].color);
+                        }
+                        break;
+                    case 'leave':
+                        Game.removeSnake(packet.id);
+                        break;
+                    case 'dead':
+                        Console.log('Info: Your snake is dead, bad luck!');
+                        Game.direction = 'none';
+                        break;
+                    case 'kill':
+                        Console.log('Info: Head shot!');
+                        break;
+                }
+            };
+        });
+
+        var Console = {};
+
+        Console.log = (function(message) {
+            var console = document.getElementById('console');
+            var p = document.createElement('p');
+            p.style.wordWrap = 'break-word';
+            p.innerHTML = message;
+            console.appendChild(p);
+            while (console.childNodes.length > 25) {
+                console.removeChild(console.firstChild);
+            }
+            console.scrollTop = console.scrollHeight;
+        });
+
+        Game.initialize();
+    </script>
+</body>
+</html>
diff --git a/webapps/examples/websocket/echo.html b/webapps/examples/websocket/echo.html
index 052cea7..67d33ab 100644
--- a/webapps/examples/websocket/echo.html
+++ b/webapps/examples/websocket/echo.html
@@ -130,13 +130,13 @@
 <div>
     <div id="connect-container">
         <div>
-            <span>Connect using:</span>
-            <!-- echo example using streams on the server side -->
-            <input id="radio1" type="radio" name="group1" value="/examples/websocket/echoStream"
-                   onclick="updateTarget(this.value);"> <label for="radio1">streams</label>
-            <!-- echo example using messages on the server side -->
-            <input id="radio2" type="radio" name="group1" value="/examples/websocket/echoMessage"
-                   onclick="updateTarget(this.value);"> <label for="radio2">messages</label>
+            <span>Connect to service implemented using:</span>
+            <!-- echo example using new programmatic API on the server side -->
+            <input id="radio1" type="radio" name="group1" value="/examples/websocket/echoProgrammatic"
+                   onclick="updateTarget(this.value);"> <label for="radio1">programmatic API</label>
+            <!-- echo example using new annotation API on the server side -->
+            <input id="radio2" type="radio" name="group1" value="/examples/websocket/echoAnnotation"
+                   onclick="updateTarget(this.value);"> <label for="radio2">annotation API</label>
         </div>
         <div>
             <input id="target" type="text" size="40" style="width: 350px"/>

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/tomcat7.git



More information about the pkg-java-commits mailing list