[tomcat7] 01/01: Imported Upstream version 7.0.47

Gianfranco Costamagna locutusofborg-guest at alioth.debian.org
Tue Oct 22 16:05:05 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.47
in repository tomcat7.

commit 2a6b6a9a17416723611a4ed778d6173befd7e561
Author: Gianfranco Costamagna <costamagnagianfranco at yahoo.it>
Date:   Tue Oct 22 14:59:59 2013 +0200

    Imported Upstream version 7.0.47
---
 NOTICE                                             |    2 +-
 build.properties.default                           |    6 +-
 build.xml                                          |    1 +
 conf/catalina.policy                               |    8 +-
 java/org/apache/catalina/Lifecycle.java            |    4 +-
 .../apache/catalina/core/AprLifecycleListener.java |    6 +-
 .../apache/catalina/ha/session/BackupManager.java  |   14 +-
 .../apache/catalina/ha/tcp/SimpleTcpCluster.java   |    6 +-
 .../apache/catalina/loader/WebappClassLoader.java  |   63 +-
 .../catalina/manager/StatusManagerServlet.java     |   12 +-
 java/org/apache/catalina/realm/CombinedRealm.java  |   14 +
 java/org/apache/coyote/AbstractProtocol.java       |    2 +
 .../http11/upgrade/AprServletInputStream.java      |   15 +-
 .../http11/upgrade/AprServletOutputStream.java     |   20 +-
 .../coyote/http11/upgrade/LocalStrings.properties  |    7 +-
 .../http11/upgrade/NioServletOutputStream.java     |    1 +
 java/org/apache/jasper/compiler/Node.java          |    3 +-
 java/org/apache/tomcat/jni/Poll.java               |   22 +-
 java/org/apache/tomcat/util/net/AprEndpoint.java   |  307 ++++++--
 java/org/apache/tomcat/util/net/JIoEndpoint.java   |    1 +
 java/org/apache/tomcat/util/net/NioEndpoint.java   |   53 +-
 java/org/apache/tomcat/util/net/SocketWrapper.java |    3 +
 .../tomcat/util/net/res/LocalStrings.properties    |   13 +-
 java/org/apache/tomcat/websocket/WsFrameBase.java  |   11 +-
 .../org/apache/tomcat/websocket/WsFrameClient.java |    9 +-
 .../tomcat/websocket/WsRemoteEndpointImplBase.java |    6 +-
 java/org/apache/tomcat/websocket/WsSession.java    |   12 +-
 .../websocket/server/WsHttpUpgradeHandler.java     |    3 +
 .../server/WsRemoteEndpointImplServer.java         |   14 +-
 res/META-INF/default.notice                        |    2 +-
 res/META-INF/servlet-api.jar.notice                |    2 +-
 res/maven/mvn.properties.default                   |    2 +-
 .../apache/catalina/mbeans/TestRegistration.java   |   33 +-
 test/org/apache/jasper/compiler/TestNode.java      |   49 ++
 .../webapp-3.0/bug5nnnn/bug55642a.jsp              |   26 +-
 .../webapp-3.0/bug5nnnn/bug55642b.jsp              |   23 +-
 webapps/docs/appdev/deployment.xml                 |    4 +-
 webapps/docs/changelog.xml                         |  116 ++-
 webapps/docs/cluster-howto.xml                     |    8 +-
 webapps/docs/config/cluster-valve.xml              |    5 +-
 webapps/docs/config/context.xml                    |   13 +-
 webapps/docs/proxy-howto.xml                       |    2 +-
 webapps/docs/tomcat-docs.xsl                       |    4 +-
 webapps/docs/web-socket-howto.xml                  |   12 +-
 .../WEB-INF/classes/websocket/ExamplesConfig.java  |   66 ++
 .../classes/websocket/drawboard/Client.java        |  207 +++++
 .../classes/websocket/drawboard/DrawMessage.java   |  270 +++++++
 .../DrawboardContextListener.java}                 |   25 +-
 .../websocket/drawboard/DrawboardEndpoint.java     |  227 ++++++
 .../WEB-INF/classes/websocket/drawboard/Room.java  |  415 ++++++++++
 .../wsmessages/AbstractWebsocketMessage.java       |   25 +
 .../wsmessages/BinaryWebsocketMessage.java         |   34 +
 .../wsmessages/CloseWebsocketMessage.java          |   24 +
 .../wsmessages/StringWebsocketMessage.java         |   34 +
 .../classes/websocket/echo/EchoEndpoint.java       |    2 +-
 .../WEB-INF/classes/websocket/snake/Snake.java     |   10 +-
 .../classes/websocket/snake/SnakeAnnotation.java   |   22 +
 .../classes/websocket/snake/SnakeTimer.java        |   10 +-
 webapps/examples/WEB-INF/web.xml                   |    5 +
 webapps/examples/index.html                        |   20 +-
 webapps/examples/jsp/jsptoserv/jts.html            |    2 +-
 webapps/examples/websocket-deprecated/index.html   |    2 +-
 .../examples/websocket/{chat.html => chat.xhtml}   |   29 +-
 webapps/examples/websocket/drawboard.xhtml         |  807 ++++++++++++++++++++
 .../examples/websocket/{echo.html => echo.xhtml}   |   31 +-
 .../examples/websocket/{index.html => index.xhtml} |   15 +-
 .../examples/websocket/{snake.html => snake.xhtml} |   34 +-
 67 files changed, 2949 insertions(+), 306 deletions(-)

diff --git a/NOTICE b/NOTICE
index 2ff79c8..a811284 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,7 +1,7 @@
 Apache Tomcat
 Copyright 1999-2013 The Apache Software Foundation
 
-This product includes software developed by
+This product includes software developed at
 The Apache Software Foundation (http://www.apache.org/).
 
 The Windows Installer is built with the Nullsoft
diff --git a/build.properties.default b/build.properties.default
index 202b624..b834bb2 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 1526165 2013-09-25 12:23:00Z violetagg $
+# $Id: build.properties.default 1533319 2013-10-18 03:02:47Z violetagg $
 # -----------------------------------------------------------------------------
 
 # ----- Version Control Flags -----
 version.major=7
 version.minor=0
-version.build=45
+version.build=47
 version.patch=0
 version.suffix=
 
@@ -136,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.28
+tomcat-native.version=1.1.29
 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 ae999df..6075e03 100644
--- a/build.xml
+++ b/build.xml
@@ -253,6 +253,7 @@
     <include name="**/*.tasks"/>
     <include name="**/*.tld"/>
     <include name="**/*.txt"/>
+    <include name="**/*.xhtml"/>
     <include name="**/*.xml"/>
     <include name="**/*.xsd"/>
     <include name="**/*.xsl"/>
diff --git a/conf/catalina.policy b/conf/catalina.policy
index e67d6af..4672f5e 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 1524081 2013-09-17 14:55:01Z schultz $
+// $Id: catalina.policy 1532508 2013-10-15 20:05:48Z markt $
 // ============================================================================
 
 
@@ -186,8 +186,12 @@ grant {
     // Applications using Comet need to be able to access this package
     permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina.comet";
 
-    // Applications using WebSocket need to be able to access this package
+    // Applications using the legacy WebSocket implementation need to be able to access this package
     permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina.websocket";
+
+    // Applications using the JSR-356 WebSocket implementation need to be able to access these packages
+    permission java.lang.RuntimePermission "accessClassInPackage.org.apache.tomcat.websocket";
+    permission java.lang.RuntimePermission "accessClassInPackage.org.apache.tomcat.websocket.server";
 };
 
 
diff --git a/java/org/apache/catalina/Lifecycle.java b/java/org/apache/catalina/Lifecycle.java
index 446cfa6..791ed40 100644
--- a/java/org/apache/catalina/Lifecycle.java
+++ b/java/org/apache/catalina/Lifecycle.java
@@ -83,7 +83,7 @@ package org.apache.catalina;
  * the component as soon as {@link #start()} exits. It is typically used when a
  * component has failed to start.
  *
- * MUST_DESTROY is used to indicate that the {@link #stop()} should be called on
+ * MUST_DESTROY is used to indicate that the {@link #destroy()} should be called on
  * the component as soon as {@link #stop()} exits. It is typically used when a
  * component is not designed to be restarted.
  *
@@ -98,7 +98,7 @@ package org.apache.catalina;
  *       components should use MUST_DESTROY to signal this.
  *
  * @author Craig R. McClanahan
- * @version $Id: Lifecycle.java 1200159 2011-11-10 05:33:31Z kkolinko $
+ * @version $Id: Lifecycle.java 1526412 2013-09-26 08:14:14Z kfujino $
  */
 public interface Lifecycle {
 
diff --git a/java/org/apache/catalina/core/AprLifecycleListener.java b/java/org/apache/catalina/core/AprLifecycleListener.java
index 9401287..c9ee5d8 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 1523649 2013-09-16 13:52:15Z markt $
+ * @version $Id: AprLifecycleListener.java 1533036 2013-10-17 10:41:35Z 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 = 28;
+    protected static final int TCN_REQUIRED_PATCH = 29;
     protected static final int TCN_RECOMMENDED_MINOR = 1;
-    protected static final int TCN_RECOMMENDED_PV = 28;
+    protected static final int TCN_RECOMMENDED_PV = 29;
 
 
     // ---------------------------------------------- Properties
diff --git a/java/org/apache/catalina/ha/session/BackupManager.java b/java/org/apache/catalina/ha/session/BackupManager.java
index c2d8a99..47aa859 100644
--- a/java/org/apache/catalina/ha/session/BackupManager.java
+++ b/java/org/apache/catalina/ha/session/BackupManager.java
@@ -51,7 +51,11 @@ public class BackupManager extends ClusterManagerBase
 
     protected static long DEFAULT_REPL_TIMEOUT = 15000;//15 seconds
 
-    /** Set to true if we don't want the sessions to expire on shutdown */
+    /**
+     * Set to true if we don't want the sessions to expire on shutdown
+     * @deprecated  Unused - will be removed in Tomcat 8.0.x
+     */
+    @Deprecated
     protected boolean mExpireSessionsOnShutdown = true;
 
     /**
@@ -91,11 +95,19 @@ public class BackupManager extends ClusterManagerBase
     public void messageDataReceived(ClusterMessage msg) {
     }
 
+    /**
+     * @deprecated  Unused - will be removed in Tomcat 8.0.x
+     */
+    @Deprecated
     public void setExpireSessionsOnShutdown(boolean expireSessionsOnShutdown)
     {
         mExpireSessionsOnShutdown = expireSessionsOnShutdown;
     }
 
+    /**
+     * @deprecated  Unused - will be removed in Tomcat 8.0.x
+     */
+    @Deprecated
     public boolean getExpireSessionsOnShutdown()
     {
         return mExpireSessionsOnShutdown;
diff --git a/java/org/apache/catalina/ha/tcp/SimpleTcpCluster.java b/java/org/apache/catalina/ha/tcp/SimpleTcpCluster.java
index 84cf06b..6d1cae6 100644
--- a/java/org/apache/catalina/ha/tcp/SimpleTcpCluster.java
+++ b/java/org/apache/catalina/ha/tcp/SimpleTcpCluster.java
@@ -32,6 +32,7 @@ import org.apache.catalina.Container;
 import org.apache.catalina.Context;
 import org.apache.catalina.Engine;
 import org.apache.catalina.Host;
+import org.apache.catalina.Lifecycle;
 import org.apache.catalina.LifecycleEvent;
 import org.apache.catalina.LifecycleException;
 import org.apache.catalina.LifecycleListener;
@@ -73,7 +74,7 @@ import org.apache.tomcat.util.res.StringManager;
  * @author Filip Hanik
  * @author Remy Maucherat
  * @author Peter Rossbach
- * @version $Id: SimpleTcpCluster.java 1505638 2013-07-22 09:28:04Z kfujino $
+ * @version $Id: SimpleTcpCluster.java 1526409 2013-09-26 08:07:48Z kfujino $
  */
 public class SimpleTcpCluster extends LifecycleMBeanBase
         implements CatalinaCluster, LifecycleListener, IDynamicProperty,
@@ -633,6 +634,9 @@ public class SimpleTcpCluster extends LifecycleMBeanBase
        
         //send a heartbeat through the channel        
         if ( isHeartbeatBackgroundEnabled() && channel !=null ) channel.heartbeat();
+
+        // periodic event
+        fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
     }
 
 
diff --git a/java/org/apache/catalina/loader/WebappClassLoader.java b/java/org/apache/catalina/loader/WebappClassLoader.java
index 7ae81db..84eb30c 100644
--- a/java/org/apache/catalina/loader/WebappClassLoader.java
+++ b/java/org/apache/catalina/loader/WebappClassLoader.java
@@ -43,6 +43,7 @@ import java.security.PrivilegedAction;
 import java.security.ProtectionDomain;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.ConcurrentModificationException;
 import java.util.Enumeration;
 import java.util.HashMap;
@@ -119,7 +120,7 @@ import org.apache.tomcat.util.res.StringManager;
  *
  * @author Remy Maucherat
  * @author Craig R. McClanahan
- * @version $Id: WebappClassLoader.java 1479177 2013-05-04 21:11:18Z markt $
+ * @version $Id: WebappClassLoader.java 1532508 2013-10-15 20:05:48Z markt $
  */
 public class WebappClassLoader
     extends URLClassLoader
@@ -1355,20 +1356,7 @@ public class WebappClassLoader
 
         }
 
-        final Iterator<URL> iterator = result.iterator();
-
-        return new Enumeration<URL>() {
-            @Override
-            public boolean hasMoreElements() {
-                return iterator.hasNext();
-            }
-
-            @Override
-            public URL nextElement() {
-                return iterator.next();
-            }
-        };
-
+        return Collections.enumeration(result);
     }
 
 
@@ -2223,6 +2211,7 @@ public class WebappClassLoader
     @SuppressWarnings("deprecation") // thread.stop()
     private void clearReferencesThreads() {
         Thread[] threads = getThreads();
+        List<Thread> executorThreadsToStop = new ArrayList<Thread>();
 
         // Iterate over the set of threads
         for (Thread thread : threads) {
@@ -2281,6 +2270,7 @@ public class WebappClassLoader
 
                     // If the thread has been started via an executor, try
                     // shutting down the executor
+                    boolean usingExecutor = false;
                     try {
 
                         // Runnable wrapped by Thread
@@ -2313,6 +2303,7 @@ public class WebappClassLoader
                             Object executor = executorField.get(target);
                             if (executor instanceof ThreadPoolExecutor) {
                                 ((ThreadPoolExecutor) executor).shutdownNow();
+                                usingExecutor = true;
                             }
                         }
                     } catch (SecurityException e) {
@@ -2333,12 +2324,44 @@ public class WebappClassLoader
                                 thread.getName(), contextName), e);
                     }
 
-                    // This method is deprecated and for good reason. This is
-                    // very risky code but is the only option at this point.
-                    // A *very* good reason for apps to do this clean-up
-                    // themselves.
-                    thread.stop();
+                    if (usingExecutor) {
+                        // Executor may take a short time to stop all the
+                        // threads. Make a note of threads that should be
+                        // stopped and check them at the end of the method.
+                        executorThreadsToStop.add(thread);
+                    } else {
+                        // This method is deprecated and for good reason. This
+                        // is very risky code but is the only option at this
+                        // point. A *very* good reason for apps to do this
+                        // clean-up themselves.
+                        thread.stop();
+                    }
+                }
+            }
+        }
+
+        // If thread stopping is enabled, executor threads should have been
+        // stopped above when the executor was shut down but that depends on the
+        // thread correctly handling the interrupt. Give all the executor
+        // threads a few seconds shutdown and if they are still running
+        // Give threads up to 2 seconds to shutdown
+        int count = 0;
+        for (Thread t : executorThreadsToStop) {
+            while (t.isAlive() && count < 100) {
+                try {
+                    Thread.sleep(20);
+                } catch (InterruptedException e) {
+                    // Quit the while loop
+                    break;
                 }
+                count++;
+            }
+            if (t.isAlive()) {
+                // This method is deprecated and for good reason. This is
+                // very risky code but is the only option at this point.
+                // A *very* good reason for apps to do this clean-up
+                // themselves.
+                t.stop();
             }
         }
     }
diff --git a/java/org/apache/catalina/manager/StatusManagerServlet.java b/java/org/apache/catalina/manager/StatusManagerServlet.java
index d091a24..c3b88bb 100644
--- a/java/org/apache/catalina/manager/StatusManagerServlet.java
+++ b/java/org/apache/catalina/manager/StatusManagerServlet.java
@@ -47,7 +47,7 @@ import org.apache.tomcat.util.res.StringManager;
  * This servlet will display a complete status of the HTTP/1.1 connector.
  *
  * @author Remy Maucherat
- * @version $Id: StatusManagerServlet.java 1136216 2011-06-15 21:58:00Z markt $
+ * @version $Id: StatusManagerServlet.java 1529796 2013-10-07 08:52:23Z violetagg $
  */
 
 public class StatusManagerServlet
@@ -165,7 +165,15 @@ public class StatusManagerServlet
     @Override
     public void destroy() {
 
-        // No actions necessary
+        // Unregister with MBean server
+        String onStr = "JMImplementation:type=MBeanServerDelegate";
+        ObjectName objectName;
+        try {
+            objectName = new ObjectName(onStr);
+            mBeanServer.removeNotificationListener(objectName, this, null, null);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
 
     }
 
diff --git a/java/org/apache/catalina/realm/CombinedRealm.java b/java/org/apache/catalina/realm/CombinedRealm.java
index a94efe0..2f036ca 100644
--- a/java/org/apache/catalina/realm/CombinedRealm.java
+++ b/java/org/apache/catalina/realm/CombinedRealm.java
@@ -232,6 +232,20 @@ public class CombinedRealm extends RealmBase {
 
 
     /**
+     * Ensure child Realms are destroyed when this Realm is destroyed.
+     */
+    @Override
+    protected void destroyInternal() throws LifecycleException {
+        for (Realm realm : realms) {
+            if (realm instanceof Lifecycle) {
+                ((Lifecycle) realm).destroy();
+            }
+        }
+        super.destroyInternal();
+    }
+
+
+    /**
      * Return the Principal associated with the specified chain of X509
      * client certificates.  If there is none, return <code>null</code>.
      *
diff --git a/java/org/apache/coyote/AbstractProtocol.java b/java/org/apache/coyote/AbstractProtocol.java
index cd7ff6d..31f9a97 100644
--- a/java/org/apache/coyote/AbstractProtocol.java
+++ b/java/org/apache/coyote/AbstractProtocol.java
@@ -664,6 +664,8 @@ public abstract class AbstractProtocol implements ProtocolHandler,
                     connections.remove(socket);
                     release(wrapper, processor, false, false);
                 } else if (state == SocketState.UPGRADED) {
+                    // Need to keep the connection associated with the processor
+                    connections.put(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
diff --git a/java/org/apache/coyote/http11/upgrade/AprServletInputStream.java b/java/org/apache/coyote/http11/upgrade/AprServletInputStream.java
index 3c339e2..f579ccd 100644
--- a/java/org/apache/coyote/http11/upgrade/AprServletInputStream.java
+++ b/java/org/apache/coyote/http11/upgrade/AprServletInputStream.java
@@ -16,10 +16,12 @@
  */
 package org.apache.coyote.http11.upgrade;
 
+import java.io.EOFException;
 import java.io.IOException;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
 
+import org.apache.tomcat.jni.OS;
 import org.apache.tomcat.jni.Socket;
 import org.apache.tomcat.jni.Status;
 import org.apache.tomcat.util.net.SocketWrapper;
@@ -51,7 +53,7 @@ public class AprServletInputStream extends AbstractServletInputStream {
             readLock.lock();
             if (wrapper.getBlockingStatus() == block) {
                 if (closed) {
-                    throw new IOException(sm.getString("apr.closed"));
+                    throw new IOException(sm.getString("apr.closed", Long.valueOf(socket)));
                 }
                 result = Socket.recv(socket, b, off, len);
                 readDone = true;
@@ -71,7 +73,7 @@ public class AprServletInputStream extends AbstractServletInputStream {
                     readLock.lock();
                     writeLock.unlock();
                     if (closed) {
-                        throw new IOException(sm.getString("apr.closed"));
+                        throw new IOException(sm.getString("apr.closed", Long.valueOf(socket)));
                     }
                     result = Socket.recv(socket, b, off, len);
                 } finally {
@@ -92,9 +94,16 @@ public class AprServletInputStream extends AbstractServletInputStream {
         } else if (-result == Status.EAGAIN) {
             eagain = true;
             return 0;
+        } else if ((OS.IS_WIN32 || OS.IS_WIN64) &&
+                (-result == Status.APR_OS_START_SYSERR + 10053)) {
+            // 10053 on Windows is connection aborted
+            throw new EOFException(sm.getString("apr.clientAbort"));
+        } else if (-result == Status.APR_EGENERAL && wrapper.isSecure()) {
+            // Connection abort by client during SSL handshake
+            throw new EOFException(sm.getString("apr.clientAbort"));
         } else {
             throw new IOException(sm.getString("apr.read.error",
-                    Integer.valueOf(-result)));
+                    Integer.valueOf(-result), Long.valueOf(socket)));
         }
     }
 
diff --git a/java/org/apache/coyote/http11/upgrade/AprServletOutputStream.java b/java/org/apache/coyote/http11/upgrade/AprServletOutputStream.java
index fb15fd1..c6bbca9 100644
--- a/java/org/apache/coyote/http11/upgrade/AprServletOutputStream.java
+++ b/java/org/apache/coyote/http11/upgrade/AprServletOutputStream.java
@@ -16,11 +16,13 @@
  */
 package org.apache.coyote.http11.upgrade;
 
+import java.io.EOFException;
 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.OS;
 import org.apache.tomcat.jni.Socket;
 import org.apache.tomcat.jni.Status;
 import org.apache.tomcat.util.net.AprEndpoint;
@@ -61,7 +63,7 @@ public class AprServletOutputStream extends AbstractServletOutputStream {
             readLock.lock();
             if (wrapper.getBlockingStatus() == block) {
                 if (closed) {
-                    throw new IOException(sm.getString("apr.closed"));
+                    throw new IOException(sm.getString("apr.closed", Long.valueOf(socket)));
                 }
                 return doWriteInternal(b, off, len);
             }
@@ -84,7 +86,7 @@ public class AprServletOutputStream extends AbstractServletOutputStream {
                 readLock.lock();
                 writeLock.unlock();
                 if (closed) {
-                    throw new IOException(sm.getString("apr.closed"));
+                    throw new IOException(sm.getString("apr.closed", Long.valueOf(socket)));
                 }
                 return doWriteInternal(b, off, len);
             } finally {
@@ -122,7 +124,8 @@ public class AprServletOutputStream extends AbstractServletOutputStream {
                     // APR + SSL requires that exactly the same parameters are
                     // passed when re-attempting the write
                 }
-                written = Socket.sendb(socket, sslOutputBuffer, start, left);
+                written = Socket.sendb(socket, sslOutputBuffer,
+                        sslOutputBuffer.position(), sslOutputBuffer.limit());
                 if (written > 0) {
                     sslOutputBuffer.position(
                             sslOutputBuffer.position() + written);
@@ -132,9 +135,18 @@ public class AprServletOutputStream extends AbstractServletOutputStream {
             }
             if (Status.APR_STATUS_IS_EAGAIN(-written)) {
                 written = 0;
+            } else if (-written == Status.APR_EOF) {
+                throw new EOFException(sm.getString("apr.clientAbort"));
+            } else if ((OS.IS_WIN32 || OS.IS_WIN64) &&
+                    (-written == Status.APR_OS_START_SYSERR + 10053)) {
+                // 10053 on Windows is connection aborted
+                throw new EOFException(sm.getString("apr.clientAbort"));
+            } else if (-written == Status.APR_EGENERAL && wrapper.isSecure()) {
+                // Connection abort by client during SSL handshake
+                throw new EOFException(sm.getString("apr.clientAbort"));
             } else if (written < 0) {
                 throw new IOException(sm.getString("apr.write.error",
-                        Integer.valueOf(-written)));
+                        Integer.valueOf(-written), Long.valueOf(socket)));
             }
             start += written;
             left -= written;
diff --git a/java/org/apache/coyote/http11/upgrade/LocalStrings.properties b/java/org/apache/coyote/http11/upgrade/LocalStrings.properties
index c8ff408..25b168d 100644
--- a/java/org/apache/coyote/http11/upgrade/LocalStrings.properties
+++ b/java/org/apache/coyote/http11/upgrade/LocalStrings.properties
@@ -24,8 +24,9 @@ upgrade.sos.canWrite.ise=It is illegal to call canWrite() when the ServletOutput
 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.
+apr.clientAbort=The client aborted the connection.
+apr.read.error=Unexpected error [{0}] reading data from the APR/native socket [{1}].
+apr.write.error=Unexpected error [{0}] writing data to the APR/native socket [{1}].
+apr.closed=The socket [{0}] 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/NioServletOutputStream.java b/java/org/apache/coyote/http11/upgrade/NioServletOutputStream.java
index f41c446..558b8e5 100644
--- a/java/org/apache/coyote/http11/upgrade/NioServletOutputStream.java
+++ b/java/org/apache/coyote/http11/upgrade/NioServletOutputStream.java
@@ -59,6 +59,7 @@ public class NioServletOutputStream extends AbstractServletOutputStream {
 
             writtenThisLoop = doWriteInternal(block, b, offset, writeThisLoop);
             count += writtenThisLoop;
+            offset += writtenThisLoop;
             leftToWrite -= writtenThisLoop;
 
             if (writtenThisLoop < writeThisLoop) {
diff --git a/java/org/apache/jasper/compiler/Node.java b/java/org/apache/jasper/compiler/Node.java
index 52859bb..99603ee 100644
--- a/java/org/apache/jasper/compiler/Node.java
+++ b/java/org/apache/jasper/compiler/Node.java
@@ -14,7 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.jasper.compiler;
 
 import java.util.ArrayList;
@@ -2305,7 +2304,7 @@ abstract class Node implements TagConstants {
          *         time.
          */
         public boolean isLiteral() {
-            return !expression && (el != null) && !namedAttribute;
+            return !expression && (el == null) && !namedAttribute;
         }
 
         /**
diff --git a/java/org/apache/tomcat/jni/Poll.java b/java/org/apache/tomcat/jni/Poll.java
index 1396188..ae67c75 100644
--- a/java/org/apache/tomcat/jni/Poll.java
+++ b/java/org/apache/tomcat/jni/Poll.java
@@ -20,20 +20,26 @@ package org.apache.tomcat.jni;
 /** Poll
  *
  * @author Mladen Turk
- * @version $Id: Poll.java 1349932 2012-06-13 15:59:02Z markt $
+ * @version $Id: Poll.java 1528427 2013-10-02 11:12:38Z markt $
  */
 
 public class Poll {
 
     /**
-     * Poll options
+     * Poll return values
      */
-    public static final int APR_POLLIN   = 0x001; /** Can read without blocking */
-    public static final int APR_POLLPRI  = 0x002; /** Priority data available */
-    public static final int APR_POLLOUT  = 0x004; /** Can write without blocking */
-    public static final int APR_POLLERR  = 0x010; /** Pending error */
-    public static final int APR_POLLHUP  = 0x020; /** Hangup occurred */
-    public static final int APR_POLLNVAL = 0x040; /** Descriptor invalid */
+    /** Can read without blocking */
+    public static final int APR_POLLIN   = 0x001;
+    /** Priority data available */
+    public static final int APR_POLLPRI  = 0x002;
+    /** Can write without blocking */
+    public static final int APR_POLLOUT  = 0x004;
+    /** Pending error */
+    public static final int APR_POLLERR  = 0x010;
+    /** Hangup occurred */
+    public static final int APR_POLLHUP  = 0x020;
+    /** Descriptor invalid */
+    public static final int APR_POLLNVAL = 0x040;
 
     /**
      * Pollset Flags
diff --git a/java/org/apache/tomcat/util/net/AprEndpoint.java b/java/org/apache/tomcat/util/net/AprEndpoint.java
index b1f5892..41216e4 100644
--- a/java/org/apache/tomcat/util/net/AprEndpoint.java
+++ b/java/org/apache/tomcat/util/net/AprEndpoint.java
@@ -27,6 +27,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Executor;
 import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
@@ -791,6 +792,7 @@ public class AprEndpoint extends AbstractEndpoint {
                 AprSocketWrapper wrapper =
                         new AprSocketWrapper(Long.valueOf(socket));
                 wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
+                wrapper.setSecure(isSSLEnabled());
                 connections.put(Long.valueOf(socket), wrapper);
                 getExecutor().execute(new SocketWithOptionsProcessor(wrapper));
             }
@@ -888,7 +890,7 @@ public class AprEndpoint extends AbstractEndpoint {
         return true;
     }
 
-    private void destroySocket(long socket) {
+    private void closeSocket(long socket) {
         // If not running the socket will be destroyed by
         // parent pool or acceptor socket.
         // In any case disable double free which would cause JVM core.
@@ -899,16 +901,22 @@ public class AprEndpoint extends AbstractEndpoint {
         // countDownConnection() in that case
         Poller poller = this.poller;
         if (poller != null) {
-            poller.removeFromPoller(socket);
+            if (!poller.close(socket)) {
+                destroySocket(socket);
+            }
         }
-        connections.remove(Long.valueOf(socket));
-        destroySocket(socket, running);
     }
 
-    private void destroySocket(long socket, boolean doIt) {
+    /*
+     * This method should only be called if there is no chance that the socket
+     * is currently being used by the Poller. It is generally a bad idea to call
+     * this directly from a known error condition.
+     */
+    private void destroySocket(long socket) {
+        connections.remove(Long.valueOf(socket));
         if (log.isDebugEnabled()) {
             String msg = sm.getString("endpoint.debug.destroySocket",
-                    Long.valueOf(socket), Boolean.valueOf(doIt));
+                    Long.valueOf(socket));
             if (log.isTraceEnabled()) {
                 log.trace(msg, new Exception());
             } else {
@@ -919,7 +927,7 @@ public class AprEndpoint extends AbstractEndpoint {
         // twice for the same socket the JVM will core. Currently this is only
         // called from Poller.closePollset() to ensure kept alive connections
         // are closed when calling stop() followed by start().
-        if (doIt && socket != 0) {
+        if (socket != 0) {
             Socket.destroy(socket);
             countDownConnection();
         }
@@ -971,6 +979,13 @@ public class AprEndpoint extends AbstractEndpoint {
                         // Accept the next incoming connection from the server
                         // socket
                         socket = Socket.accept(serverSock);
+                        if (log.isDebugEnabled()) {
+                            long sa = Address.get(Socket.APR_REMOTE, socket);
+                            Sockaddr addr = Address.getInfo(sa);
+                            log.debug(sm.getString("endpoint.apr.remoteport",
+                                    Long.valueOf(socket),
+                                    Long.valueOf(addr.port)));
+                        }
                     } catch (Exception e) {
                         //we didn't get a socket
                         countDownConnection();
@@ -985,11 +1000,13 @@ public class AprEndpoint extends AbstractEndpoint {
                     if (running && !paused) {
                         // Hand this socket off to an appropriate processor
                         if (!processSocketWithOptions(socket)) {
-                            // Close socket and pool right away
-                            destroySocket(socket);
+                            // Close socket right away
+                            closeSocket(socket);
                         }
                     } else {
-                        // Close socket and pool right away
+                        // Close socket right away
+                        // No code path could have added the socket to the
+                        // Poller so use destroySocket()
                         destroySocket(socket);
                     }
                 } catch (Throwable t) {
@@ -1123,16 +1140,24 @@ public class AprEndpoint extends AbstractEndpoint {
             size++;
         }
 
-        public boolean remove(long socket) {
+        /**
+         * Removes the specified socket from the poller.
+         *
+         * @returns The configured timeout for the socket or zero if the socket
+         *          was not in the list of socket timeouts
+         */
+        public long remove(long socket) {
+            long result = 0;
             for (int i = 0; i < size; i++) {
                 if (sockets[i] == socket) {
+                    result = timeouts[i];
                     sockets[i] = sockets[size - 1];
                     timeouts[i] = timeouts[size - 1];
                     size--;
-                    return true;
+                    break;
                 }
             }
-            return false;
+            return result;
         }
 
         public long check(long date) {
@@ -1212,6 +1237,19 @@ public class AprEndpoint extends AbstractEndpoint {
             }
         }
 
+        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];
+                    flags[size] = flags[size -1];
+                    size--;
+                    return true;
+                }
+            }
+            return false;
+        }
+
         public void duplicate(SocketList copy) {
             copy.size = size;
             copy.pos = pos;
@@ -1252,6 +1290,13 @@ public class AprEndpoint extends AbstractEndpoint {
         protected int pollerTime;
 
         /**
+         * Variable poller timeout that adjusts depending on how many poll sets
+         * are in use so that the total poll time across all poll sets remains
+         * equal to pollTime.
+         */
+        private int nextPollerTime;
+
+        /**
          * Root pool.
          */
         protected long pool = 0;
@@ -1266,10 +1311,12 @@ public class AprEndpoint extends AbstractEndpoint {
          */
         protected SocketList addList = null;
 
+        
         /**
-         * List of sockets to be added to the poller.
+         * List of sockets to be closed.
          */
-        protected SocketList localAddList = null;
+        private SocketList closeList = null;
+
 
         /**
          * Structure used for storing timeouts.
@@ -1284,10 +1331,17 @@ public class AprEndpoint extends AbstractEndpoint {
 
 
         /**
-         * Amount of connections inside this poller.
+         * The number of connections currently inside this Poller. The correct
+         * operation of the Poller depends on this figure being correct. If it
+         * is not, it is possible that the Poller will enter a wait loop where
+         * it waits for the next connection to be added to the Poller before it
+         * calls poll when it should still be polling existing connections.
+         * Although not necessary at the time of writing this comment, it has
+         * been implemented as an AtomicInteger to ensure that it remains
+         * thread-safe.
          */
-        protected int connectionCount = 0;
-        public int getConnectionCount() { return connectionCount; }
+        private AtomicInteger connectionCount = new AtomicInteger(0);
+        public int getConnectionCount() { return connectionCount.get(); }
 
 
         private volatile boolean pollerRunning = true;
@@ -1330,6 +1384,7 @@ public class AprEndpoint extends AbstractEndpoint {
 
             pollerCount = defaultPollerSize / actualPollerSize;
             pollerTime = pollTime / pollerCount;
+            nextPollerTime = pollerTime;
 
             pollers = new long[pollerCount];
             pollers[0] = pollset;
@@ -1343,9 +1398,9 @@ public class AprEndpoint extends AbstractEndpoint {
             }
 
             desc = new long[actualPollerSize * 2];
-            connectionCount = 0;
+            connectionCount.set(0);
             addList = new SocketList(defaultPollerSize);
-            localAddList = new SocketList(defaultPollerSize);
+            closeList = new SocketList(defaultPollerSize);
         }
 
 
@@ -1380,6 +1435,8 @@ public class AprEndpoint extends AbstractEndpoint {
                         connections.get(Long.valueOf(info.socket)).isComet();
                 if (!comet || (comet && !processSocket(
                         info.socket, SocketStatus.STOP))) {
+                    // Poller isn't running at this point so use destroySocket()
+                    // directly
                     destroySocket(info.socket);
                 }
                 info = addList.get();
@@ -1394,13 +1451,13 @@ public class AprEndpoint extends AbstractEndpoint {
                                 Long.valueOf(desc[n*2+1])).isComet();
                         if (!comet || (comet && !processSocket(
                                 desc[n*2+1], SocketStatus.STOP))) {
-                            destroySocket(desc[n*2+1], true);
+                            destroySocket(desc[n*2+1]);
                         }
                     }
                 }
             }
             Pool.destroy(pool);
-            connectionCount = 0;
+            connectionCount.set(0);
         }
 
 
@@ -1456,13 +1513,15 @@ public class AprEndpoint extends AbstractEndpoint {
                         Long.valueOf(socket)).isComet();
                 if (!comet || (comet && !processSocket(
                         socket, SocketStatus.ERROR))) {
-                    destroySocket(socket);
+                    closeSocket(socket);
                 }
             }
         }
 
+
         /**
-         * Add specified socket to one of the pollers.
+         * Add specified socket to one of the pollers. Must only be called from
+         * {@link Poller#run()}.
          */
         protected boolean addToPoller(long socket, int events) {
             int rv = -1;
@@ -1471,7 +1530,7 @@ public class AprEndpoint extends AbstractEndpoint {
                     rv = Poll.add(pollers[i], socket, events);
                     if (rv == Status.APR_SUCCESS) {
                         pollerSpace[i]--;
-                        connectionCount++;
+                        connectionCount.incrementAndGet();
                         return true;
                     }
                 }
@@ -1479,21 +1538,47 @@ public class AprEndpoint extends AbstractEndpoint {
             return false;
         }
 
+
+        protected boolean close(long socket) {
+            if (!pollerRunning) {
+                return false;
+            }
+            synchronized (this) {
+                if (!pollerRunning) {
+                    return false;
+                }
+                closeList.add(socket, 0, 0);
+                this.notify();
+                return true;
+            }
+        }
+
+
         /**
-         * Remove specified socket from the pollers.
+         * Remove specified socket from the pollers. Must only be called from
+         * {@link Poller#run()}.
          */
-        protected boolean removeFromPoller(long socket) {
+        private boolean removeFromPoller(long socket) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("endpoint.debug.pollerRemove",
+                        Long.valueOf(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--;
+                        connectionCount.decrementAndGet();
+                        if (log.isDebugEnabled()) {
+                            log.debug(sm.getString("endpoint.debug.pollerRemoved",
+                                    Long.valueOf(socket)));
+                        }
                         break;
                     }
                 }
             }
+            timeouts.remove(socket);
             return (rv == Status.APR_SUCCESS);
         }
 
@@ -1555,6 +1640,9 @@ public class AprEndpoint extends AbstractEndpoint {
         public void run() {
 
             int maintain = 0;
+            SocketList localAddList = new SocketList(getMaxConnections());
+            SocketList localCloseList = new SocketList(getMaxConnections());
+
             // Loop until we receive a shutdown command
             while (pollerRunning) {
 
@@ -1567,8 +1655,8 @@ public class AprEndpoint extends AbstractEndpoint {
                     }
                 }
                 // Check timeouts if the poller is empty
-                while (pollerRunning && connectionCount < 1 &&
-                        addList.size() < 1) {
+                while (pollerRunning && connectionCount.get() < 1 &&
+                        addList.size() < 1 && closeList.size() < 1) {
                     // Reset maintain time.
                     try {
                         if (getSoTimeout() > 0 && pollerRunning) {
@@ -1591,7 +1679,18 @@ public class AprEndpoint extends AbstractEndpoint {
                 }
 
                 try {
-                    // Add sockets which are waiting to the poller
+                    // Duplicate the add and remove lists so that the syncs are
+                    // minimised
+                    if (closeList.size() > 0) {
+                        synchronized (this) {
+                            // Duplicate to another list, so that the syncing is
+                            // minimal
+                            closeList.duplicate(localCloseList);
+                            closeList.clear();
+                        }
+                    } else {
+                        localCloseList.clear();
+                    }
                     if (addList.size() > 0) {
                         synchronized (this) {
                             // Duplicate to another list, so that the syncing is
@@ -1599,6 +1698,23 @@ public class AprEndpoint extends AbstractEndpoint {
                             addList.duplicate(localAddList);
                             addList.clear();
                         }
+                    } else {
+                        localAddList.clear();
+                    }
+
+                    // Remove sockets
+                    if (localCloseList.size() > 0) {
+                        SocketInfo info = localCloseList.get();
+                        while (info != null) {
+                            localAddList.remove(info.socket);
+                            removeFromPoller(info.socket);
+                            destroySocket(info.socket);
+                            info = localCloseList.get();
+                        }
+                    }
+
+                    // Add sockets which are waiting to the poller
+                    if (localAddList.size() > 0) {
                         SocketInfo info = localAddList.get();
                         while (info != null) {
                             if (log.isDebugEnabled()) {
@@ -1625,7 +1741,7 @@ public class AprEndpoint extends AbstractEndpoint {
                                     // away
                                     if (!comet || (comet && !processSocket(
                                             info.socket, SocketStatus.ERROR))) {
-                                        destroySocket(info.socket);
+                                        closeSocket(info.socket);
                                     }
                                 } else {
                                     timeouts.add(info.socket,
@@ -1634,7 +1750,7 @@ public class AprEndpoint extends AbstractEndpoint {
                                 }
                             } else {
                                 // Should never happen.
-                                destroySocket(info.socket);
+                                closeSocket(info.socket);
                                 getLog().warn(sm.getString(
                                         "endpoint.apr.pollAddInvalid", info));
                             }
@@ -1652,13 +1768,24 @@ public class AprEndpoint extends AbstractEndpoint {
                         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);
+                            rv = Poll.poll(pollers[i], nextPollerTime, desc, true);
+                            // Reset the nextPollerTime
+                            nextPollerTime = pollerTime;
+                        } else {
+                            // Skipping an empty poll set means skipping a wait
+                            // time of pollerTime microseconds. If most of the
+                            // poll sets are skipped then this loop will be
+                            // tighter than expected which could lead to higher
+                            // than expected CPU usage. Extending the
+                            // nextPollerTime ensures that this loop always
+                            // takes about the same time to execute.
+                            nextPollerTime += pollerTime;
                         }
                         if (rv > 0) {
                             pollerSpace[i] += rv;
-                            connectionCount -= rv;
+                            connectionCount.addAndGet(-rv);
                             for (int n = 0; n < rv; n++) {
-                                timeouts.remove(desc[n*2+1]);
+                                long timeout = timeouts.remove(desc[n*2+1]);
                                 AprSocketWrapper wrapper = connections.get(
                                         Long.valueOf(desc[n*2+1]));
                                 if (getLog().isDebugEnabled()) {
@@ -1676,7 +1803,7 @@ public class AprEndpoint extends AbstractEndpoint {
                                             || ((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]);
+                                            closeSocket(desc[n*2+1]);
                                         }
                                     } else if ((desc[n*2] & Poll.APR_POLLIN) == Poll.APR_POLLIN) {
                                         if (wrapper.pollerFlags != 0) {
@@ -1684,7 +1811,7 @@ public class AprEndpoint extends AbstractEndpoint {
                                         }
                                         if (!processSocket(desc[n*2+1], SocketStatus.OPEN_READ)) {
                                             // Close socket and clear pool
-                                            destroySocket(desc[n*2+1]);
+                                            closeSocket(desc[n*2+1]);
                                         }
                                     } else if ((desc[n*2] & Poll.APR_POLLOUT) == Poll.APR_POLLOUT) {
                                         if (wrapper.pollerFlags != 0) {
@@ -1692,7 +1819,7 @@ public class AprEndpoint extends AbstractEndpoint {
                                         }
                                         if (!processSocket(desc[n*2+1], SocketStatus.OPEN_WRITE)) {
                                             // Close socket and clear pool
-                                            destroySocket(desc[n*2+1]);
+                                            closeSocket(desc[n*2+1]);
                                         }
                                     } else {
                                         // Unknown event
@@ -1701,14 +1828,55 @@ public class AprEndpoint extends AbstractEndpoint {
                                                 Long.valueOf(desc[n*2])));
                                         if (!processSocket(desc[n*2+1], SocketStatus.ERROR)) {
                                             // Close socket and clear pool
-                                            destroySocket(desc[n*2+1]);
+                                            closeSocket(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]);
+                                    if (wrapper.isUpgraded()) {
+                                        // Using non-blocking IO. Need to trigger error handling.
+                                        // Poller may return error codes plus the flags it was
+                                        // waiting for or it may just return an error code. By
+                                        // signalling read/write is possible, a read/write will be
+                                        // attempted, fail and that will trigger an exception the
+                                        // application will see.
+                                        // Check the return flags first, followed by what the socket
+                                        // was registered for
+                                        if ((desc[n*2] & Poll.APR_POLLIN) == Poll.APR_POLLIN) {
+                                            // Error probably occurred during a non-blocking read
+                                            if (!processSocket(desc[n*2+1], SocketStatus.OPEN_READ)) {
+                                                // Close socket and clear pool
+                                                closeSocket(desc[n*2+1]);
+                                            }
+                                        } else if ((desc[n*2] & Poll.APR_POLLOUT) == Poll.APR_POLLOUT) {
+                                            // Error probably occurred during a non-blocking write
+                                            if (!processSocket(desc[n*2+1], SocketStatus.OPEN_WRITE)) {
+                                                // Close socket and clear pool
+                                                closeSocket(desc[n*2+1]);
+                                            }
+                                        } else if ((wrapper.pollerFlags & Poll.APR_POLLIN) == Poll.APR_POLLIN) {
+                                            // Can't tell what was happening when the error occurred but the
+                                            // socket is registered for non-blocking read so use that
+                                            if (!processSocket(desc[n*2+1], SocketStatus.OPEN_READ)) {
+                                                // Close socket and clear pool
+                                                closeSocket(desc[n*2+1]);
+                                            }
+                                        } else if ((wrapper.pollerFlags & Poll.APR_POLLOUT) == Poll.APR_POLLOUT) {
+                                            // Can't tell what was happening when the error occurred but the
+                                            // socket is registered for non-blocking write so use that
+                                            if (!processSocket(desc[n*2+1], SocketStatus.OPEN_WRITE)) {
+                                                // Close socket and clear pool
+                                                closeSocket(desc[n*2+1]);
+                                            }
+                                        } else {
+                                            // Close socket and clear pool
+                                            closeSocket(desc[n*2+1]);
+                                        }
+                                    } else {
+                                        // Close socket and clear pool
+                                        closeSocket(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;
@@ -1716,13 +1884,38 @@ public class AprEndpoint extends AbstractEndpoint {
                                             !processSocket(desc[n*2+1], SocketStatus.OPEN_READ)) {
                                         error = true;
                                         // Close socket and clear pool
-                                        destroySocket(desc[n*2+1]);
+                                        closeSocket(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]);
+                                        error = true;
+                                        closeSocket(desc[n*2+1]);
+                                    }
+                                    if (!error && wrapper.pollerFlags != 0) {
+                                        // If socket was registered for multiple events but
+                                        // only some of the occurred, re-register for the
+                                        // remaining events.
+                                        // timeout is the value of System.currentTimeMillis() that
+                                        // was set as the point that the socket will timeout. When
+                                        // adding to the poller, the timeout from now in
+                                        // milliseconds is required.
+                                        // So first, subtract the current timestamp
+                                        if (timeout > 0) {
+                                            timeout = timeout - System.currentTimeMillis();
+                                        }
+                                        // If the socket should have already expired by now,
+                                        // re-add it with a very short timeout
+                                        if (timeout <= 0) {
+                                            timeout = 1;
+                                        }
+                                        // Should be impossible but just in case since timeout will
+                                        // be cast to an int.
+                                        if (timeout > Integer.MAX_VALUE) {
+                                            timeout = Integer.MAX_VALUE;
+                                        }
+                                        add(desc[n*2+1], (int) timeout, wrapper.pollerFlags);
                                     }
                                 } else {
                                     // Unknown event
@@ -1730,7 +1923,7 @@ public class AprEndpoint extends AbstractEndpoint {
                                             "endpoint.apr.pollUnknownEvent",
                                             Long.valueOf(desc[n*2])));
                                     // Close socket and clear pool
-                                    destroySocket(desc[n*2+1]);
+                                    closeSocket(desc[n*2+1]);
                                 }
                             }
                         } else if (rv < 0) {
@@ -1755,7 +1948,7 @@ public class AprEndpoint extends AbstractEndpoint {
                             long newPoller = allocatePoller(actualPollerSize, pool, -1);
                             // Don't restore connections for now, since I have not tested it
                             pollerSpace[i] = actualPollerSize;
-                            connectionCount -= count;
+                            connectionCount.addAndGet(-count);
                             Poll.destroy(pollers[i]);
                             pollers[i] = newPoller;
                         }
@@ -1872,13 +2065,13 @@ public class AprEndpoint extends AbstractEndpoint {
             // Close any socket remaining in the add queue
             for (int i = (addS.size() - 1); i >= 0; i--) {
                 SendfileData data = addS.get(i);
-                destroySocket(data.socket);
+                closeSocket(data.socket);
             }
             // Close all sockets still in the poller
             int rv = Poll.pollset(sendfilePollset, desc);
             if (rv > 0) {
                 for (int n = 0; n < rv; n++) {
-                    destroySocket(desc[n*2+1]);
+                    closeSocket(desc[n*2+1]);
                 }
             }
             Pool.destroy(pool);
@@ -2009,7 +2202,7 @@ public class AprEndpoint extends AbstractEndpoint {
                                             Integer.valueOf(rv),
                                             Error.strerror(rv)));
                                     // Can't do anything: close the socket right away
-                                    destroySocket(data.socket);
+                                    closeSocket(data.socket);
                                 }
                             }
                             addS.clear();
@@ -2031,7 +2224,7 @@ public class AprEndpoint extends AbstractEndpoint {
                                 remove(state);
                                 // Destroy file descriptor pool, which should close the file
                                 // Close the socket, as the response would be incomplete
-                                destroySocket(state.socket);
+                                closeSocket(state.socket);
                                 continue;
                             }
                             // Write some data using sendfile
@@ -2043,7 +2236,7 @@ public class AprEndpoint extends AbstractEndpoint {
                                 remove(state);
                                 // Close the socket, as the response would be incomplete
                                 // This will close the file too.
-                                destroySocket(state.socket);
+                                closeSocket(state.socket);
                                 continue;
                             }
 
@@ -2063,7 +2256,7 @@ public class AprEndpoint extends AbstractEndpoint {
                                 } else {
                                     // Close the socket since this is
                                     // the end of not keep-alive request.
-                                    destroySocket(state.socket);
+                                    closeSocket(state.socket);
                                 }
                             }
                         }
@@ -2099,7 +2292,7 @@ public class AprEndpoint extends AbstractEndpoint {
                                 remove(state);
                                 // Destroy file descriptor pool, which should close the file
                                 // Close the socket, as the response would be incomplete
-                                destroySocket(state.socket);
+                                closeSocket(state.socket);
                             }
                         }
                     }
@@ -2159,14 +2352,14 @@ public class AprEndpoint extends AbstractEndpoint {
                                 getSoTimeout(), true, false);
                     } else {
                         // Close socket and pool
-                        destroySocket(socket.getSocket().longValue());
+                        closeSocket(socket.getSocket().longValue());
                         socket = null;
                     }
                 } else {
                     // Process the request from this socket
                     if (!setSocketOptions(socket.getSocket().longValue())) {
                         // Close socket and pool
-                        destroySocket(socket.getSocket().longValue());
+                        closeSocket(socket.getSocket().longValue());
                         socket = null;
                         return;
                     }
@@ -2175,7 +2368,7 @@ public class AprEndpoint extends AbstractEndpoint {
                             SocketStatus.OPEN_READ);
                     if (state == Handler.SocketState.CLOSED) {
                         // Close socket and pool
-                        destroySocket(socket.getSocket().longValue());
+                        closeSocket(socket.getSocket().longValue());
                         socket = null;
                     } else if (state == Handler.SocketState.LONG) {
                         socket.access();
@@ -2237,7 +2430,7 @@ public class AprEndpoint extends AbstractEndpoint {
             SocketState state = handler.process(socket, status);
             if (state == Handler.SocketState.CLOSED) {
                 // Close socket and pool
-                destroySocket(socket.getSocket().longValue());
+                closeSocket(socket.getSocket().longValue());
                 socket.socket = null;
             } else if (state == Handler.SocketState.LONG) {
                 socket.access();
diff --git a/java/org/apache/tomcat/util/net/JIoEndpoint.java b/java/org/apache/tomcat/util/net/JIoEndpoint.java
index 058f6de..8833cf8 100644
--- a/java/org/apache/tomcat/util/net/JIoEndpoint.java
+++ b/java/org/apache/tomcat/util/net/JIoEndpoint.java
@@ -519,6 +519,7 @@ public class JIoEndpoint extends AbstractEndpoint {
         try {
             SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);
             wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
+            wrapper.setSecure(isSSLEnabled());
             // During shutdown, executor may be null - avoid NPE
             if (!running) {
                 return false;
diff --git a/java/org/apache/tomcat/util/net/NioEndpoint.java b/java/org/apache/tomcat/util/net/NioEndpoint.java
index f4ee3b9..d114ac8 100644
--- a/java/org/apache/tomcat/util/net/NioEndpoint.java
+++ b/java/org/apache/tomcat/util/net/NioEndpoint.java
@@ -1048,19 +1048,20 @@ public class NioEndpoint extends AbstractEndpoint {
             return result;
         }
 
-        public void register(final NioChannel socket)
-        {
+        public void register(final NioChannel socket) {
             socket.setPoller(this);
             KeyAttachment key = keyCache.poll();
             final KeyAttachment ka = key!=null?key:new KeyAttachment(socket);
             ka.reset(this,socket,getSocketProperties().getSoTimeout());
             ka.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
+            ka.setSecure(isSSLEnabled());
             PollerEvent r = eventCache.poll();
             ka.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
             if ( r==null) r = new PollerEvent(socket,ka,OP_REGISTER);
             else r.reset(socket,ka,OP_REGISTER);
             addEvent(r);
         }
+        
         public void cancelledKey(SelectionKey key, SocketStatus status, boolean dispatch) {
             try {
                 if ( key == null ) return;//nothing to do
@@ -1247,46 +1248,22 @@ public class NioEndpoint extends AbstractEndpoint {
                     if (sk.isReadable() || sk.isWritable() ) {
                         if ( attachment.getSendfileData() != null ) {
                             processSendfile(sk,attachment, false);
-                        } else if ( attachment.isComet() ) {
-                            //check if thread is available
-                            if ( isWorkerAvailable() ) {
-                                //set interest ops to 0 so we don't get multiple
-                                //Invocations for both read and write on separate threads
-                                reg(sk, attachment, 0);
-                                //read goes before write
-                                if (sk.isReadable()) {
-                                    //read notification
-                                    if (!processSocket(channel, SocketStatus.OPEN_READ, true))
-                                        processSocket(channel, SocketStatus.DISCONNECT, true);
-                                } else {
-                                    //future placement of a WRITE notif
-                                    if (!processSocket(channel, SocketStatus.OPEN_WRITE, true))
-                                        processSocket(channel, SocketStatus.DISCONNECT, true);
-                                }
-                            } else {
-                                result = false;
-                            }
                         } else {
-                            //later on, improve latch behavior
                             if ( isWorkerAvailable() ) {
-
-                                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
+                                unreg(sk, attachment, sk.readyOps());
+                                boolean closeSocket = false;
+                                // 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 (!processSocket(channel, SocketStatus.OPEN_READ, true)) {
+                                        closeSocket = true;
+                                    }
+                                }
+                                if (!closeSocket && sk.isWritable()) {
+                                    if (!processSocket(channel, SocketStatus.OPEN_WRITE, true)) {
+                                        closeSocket = true;
+                                    }
                                 }
-                                if (close) {
+                                if (closeSocket) {
                                     cancelledKey(sk,SocketStatus.DISCONNECT,false);
                                 }
                             } else {
diff --git a/java/org/apache/tomcat/util/net/SocketWrapper.java b/java/org/apache/tomcat/util/net/SocketWrapper.java
index 964e7f3..143fbee 100644
--- a/java/org/apache/tomcat/util/net/SocketWrapper.java
+++ b/java/org/apache/tomcat/util/net/SocketWrapper.java
@@ -33,6 +33,7 @@ public class SocketWrapper<E> {
     protected boolean async = false;
     protected boolean keptAlive = false;
     private boolean upgraded = false;
+    private boolean secure = false;
 
     /*
      * Used if block/non-blocking is set at the socket level. The client is
@@ -69,6 +70,8 @@ public class SocketWrapper<E> {
     public void setAsync(boolean async) { this.async = async; }
     public boolean isUpgraded() { return upgraded; }
     public void setUpgraded(boolean upgraded) { this.upgraded = upgraded; }
+    public boolean isSecure() { return secure; }
+    public void setSecure(boolean secure) { this.secure = secure; }
     public long getLastAccess() { return lastAccess; }
     public void access() { access(System.currentTimeMillis()); }
     public void access(long access) { lastAccess = access; }
diff --git a/java/org/apache/tomcat/util/net/res/LocalStrings.properties b/java/org/apache/tomcat/util/net/res/LocalStrings.properties
index 85eec46..6b41f90 100644
--- a/java/org/apache/tomcat/util/net/res/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/net/res/LocalStrings.properties
@@ -24,13 +24,15 @@ endpoint.warn.noInsecureReneg=Secure re-negotiation is not supported by the SSL
 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.destroySocket=Destroying socket [{0}]
+endpoint.debug.pollerAdd=Add to addList socket [{0}], timeout [{1}], flags [{2}]
+endpoint.debug.pollerAddDo=Add to poller socket [{0}]
 endpoint.debug.pollerProcess=Processing socket [{0}] for event(s) [{1}]
+endpoint.debug.pollerRemove=Attempting to remove [{0}] from poller
+endpoint.debug.pollerRemoved=Removed [{0}] from poller
 endpoint.debug.socket=socket [{0}]
 endpoint.debug.socketCloseFail=Failed to close socket
-endpoint.debug.socketTimeout=Timing out [{0}
+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
 
@@ -47,10 +49,11 @@ endpoint.process.fail=Error allocating socket processor
 endpoint.sendfile.error=Unexpected sendfile error
 endpoint.sendfile.addfail=Sendfile failure: [{0}] {1}
 endpoint.timeout.err=Error processing socket timeout
+endpoint.apr.invalidSslProtocol=An invalid value [{0}] was provided for the SSLProtocol attribute
 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.apr.remoteport=APR socket [{0}] opened with remote port [{1}]
 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/websocket/WsFrameBase.java b/java/org/apache/tomcat/websocket/WsFrameBase.java
index a56e9d4..94afd3f 100644
--- a/java/org/apache/tomcat/websocket/WsFrameBase.java
+++ b/java/org/apache/tomcat/websocket/WsFrameBase.java
@@ -97,7 +97,6 @@ public abstract class WsFrameBase {
     protected void processInputBuffer() throws IOException {
         while (true) {
             wsSession.updateLastActive();
-
             if (state == State.NEW_FRAME) {
                 if (!processInitialHeader()) {
                     break;
@@ -266,14 +265,16 @@ public abstract class WsFrameBase {
 
 
     private boolean processData() throws IOException {
-        checkRoomPayload();
+        boolean result;
         if (Util.isControl(opCode)) {
-            return processDataControl();
+            result = processDataControl();
         } else if (textMessage) {
-            return processDataText();
+            result = processDataText();
         } else {
-            return processDataBinary();
+            result = processDataBinary();
         }
+        checkRoomPayload();
+        return result;
     }
 
 
diff --git a/java/org/apache/tomcat/websocket/WsFrameClient.java b/java/org/apache/tomcat/websocket/WsFrameClient.java
index bd40d43..beb51cc 100644
--- a/java/org/apache/tomcat/websocket/WsFrameClient.java
+++ b/java/org/apache/tomcat/websocket/WsFrameClient.java
@@ -102,7 +102,14 @@ public class WsFrameClient extends WsFrameBase {
             try {
                 processSocketRead();
             } catch (IOException e) {
-                close(e);
+                // Only send a close message on an IOException if the client
+                // has not yet received a close control message from the server
+                // as the IOException may be in response to the client
+                // continuing to send a message after the server sent a close
+                // control message.
+                if (isOpen()) {
+                    close(e);
+                }
             }
         }
 
diff --git a/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java b/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
index b90d50f..77158cb 100644
--- a/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
+++ b/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
@@ -281,7 +281,11 @@ public abstract class WsRemoteEndpointImplBase implements RemoteEndpoint {
             MessagePart mpNext = messagePartQueue.poll();
             if (mpNext == null) {
                 messagePartInProgress = false;
-            } else {
+            } else if (!closed){
+                // Session may have been closed unexpectedly in the middle of
+                // sending a fragmented message closing the endpoint. If this
+                // happens, clearly there is no point trying to send the rest of
+                // the message.
                 writeMessagePart(mpNext);
             }
         }
diff --git a/java/org/apache/tomcat/websocket/WsSession.java b/java/org/apache/tomcat/websocket/WsSession.java
index e10c3b7..6fd03b6 100644
--- a/java/org/apache/tomcat/websocket/WsSession.java
+++ b/java/org/apache/tomcat/websocket/WsSession.java
@@ -30,6 +30,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
 
 import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCode;
 import javax.websocket.CloseReason.CloseCodes;
 import javax.websocket.DeploymentException;
 import javax.websocket.Endpoint;
@@ -464,7 +465,8 @@ public class WsSession implements Session {
     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());
+        CloseCode closeCode = closeReason.getCloseCode();
+        msg.putShort((short) closeCode.getCode());
 
         String reason = closeReason.getReasonPhrase();
         if (reason != null && reason.length() > 0) {
@@ -481,7 +483,13 @@ public class WsSession implements Session {
                 log.debug(sm.getString("wsSession.sendCloseFail"), ioe);
             }
             wsRemoteEndpoint.close();
-            localEndpoint.onError(this, ioe);
+            // Failure to send a close message is not unexpected in the case of
+            // an abnormal closure (usually triggered by a failure to read/write
+            // from/to the client. In this case do not trigger the endpoint's
+            // error handling
+            if (closeCode != CloseCodes.CLOSED_ABNORMALLY) {
+                localEndpoint.onError(this, ioe);
+            }
         } finally {
             webSocketContainer.unregisterSession(localEndpoint, this);
         }
diff --git a/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java b/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java
index b611671..8842e66 100644
--- a/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java
+++ b/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java
@@ -198,6 +198,9 @@ public class WsHttpUpgradeHandler implements HttpUpgradeHandler {
                 wsProtocolHandler.close(cr);
             } catch (IOException ioe) {
                 onError(ioe);
+                CloseReason cr = new CloseReason(
+                        CloseCodes.CLOSED_ABNORMALLY, ioe.getMessage());
+                wsProtocolHandler.close(cr);
             }
         }
 
diff --git a/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java b/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
index 435d9a5..487f345 100644
--- a/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
+++ b/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
@@ -89,23 +89,18 @@ public class WsRemoteEndpointImplServer extends WsRemoteEndpointImplBase {
                 }
                 if (complete) {
                     wsWriteTimeout.unregister(this);
+                    clearHandler(null);
                     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);
+            close();
         }
         if (!complete) {
             // Async write is in progress
@@ -150,6 +145,11 @@ public class WsRemoteEndpointImplServer extends WsRemoteEndpointImplBase {
 
 
     private void clearHandler(Throwable t) {
+        // 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.
         SendHandler sh = handler;
         handler = null;
         if (sh != null) {
diff --git a/res/META-INF/default.notice b/res/META-INF/default.notice
index 79c89d8..9b66b20 100644
--- a/res/META-INF/default.notice
+++ b/res/META-INF/default.notice
@@ -1,5 +1,5 @@
 Apache Tomcat
 Copyright 1999- at YEAR@ The Apache Software Foundation
 
-This product includes software developed by
+This product includes software developed at
 The Apache Software Foundation (http://www.apache.org/).
diff --git a/res/META-INF/servlet-api.jar.notice b/res/META-INF/servlet-api.jar.notice
index cd168f7..8852a4b 100644
--- a/res/META-INF/servlet-api.jar.notice
+++ b/res/META-INF/servlet-api.jar.notice
@@ -1,7 +1,7 @@
 Apache Tomcat
 Copyright 1999- at YEAR@ The Apache Software Foundation
 
-This product includes software developed by
+This product includes software developed at
 The Apache Software Foundation (http://www.apache.org/).
 
 The original XML Schemas for Java EE Deployment Descriptors:
diff --git a/res/maven/mvn.properties.default b/res/maven/mvn.properties.default
index 0bc29c0..b56b2f3 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.45
+maven.asf.release.deploy.version=7.0.47
 
 #Where do we load the libraries from
 tomcat.lib.path=../../output/build/lib
diff --git a/test/org/apache/catalina/mbeans/TestRegistration.java b/test/org/apache/catalina/mbeans/TestRegistration.java
index 8e3adbb..c8978b8 100644
--- a/test/org/apache/catalina/mbeans/TestRegistration.java
+++ b/test/org/apache/catalina/mbeans/TestRegistration.java
@@ -33,7 +33,11 @@ import static org.junit.Assert.fail;
 
 import org.junit.Test;
 
+import org.apache.catalina.Context;
+import org.apache.catalina.Realm;
 import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.realm.CombinedRealm;
+import org.apache.catalina.realm.NullRealm;
 import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
 import org.apache.tomcat.util.modeler.Registry;
@@ -91,6 +95,14 @@ public class TestRegistration extends TomcatBaseTest {
         }
     }
 
+    private static String[] requestMBeanNames(String port, String type) {
+        return new String[] {
+            "Tomcat:type=RequestProcessor,worker=" +
+                    ObjectName.quote("http-" + type + "-" + ADDRESS + "-" + port) +
+                    ",name=HttpRequest1",
+        };
+    }
+
     private static String[] contextMBeanNames(String host, String context) {
         return new String[] {
             "Tomcat:j2eeType=WebModule,name=//" + host + context +
@@ -106,6 +118,10 @@ public class TestRegistration extends TomcatBaseTest {
                 ",host=" + host + ",name=StandardContextValve",
             "Tomcat:type=WebappClassLoader,context=" + context +
                 ",host=" + host,
+            "Tomcat:type=Realm,realmPath=/realm0,context=" + context +
+                ",host=" + host,
+            "Tomcat:type=Realm,realmPath=/realm0/realm0,context=" + context +
+                ",host=" + host,
         };
     }
 
@@ -146,9 +162,17 @@ public class TestRegistration extends TomcatBaseTest {
         if (!contextDir.mkdirs() && !contextDir.isDirectory()) {
             fail("Failed to create: [" + contextDir.toString() + "]");
         }
-        tomcat.addContext(contextName, contextDir.getAbsolutePath());
+        Context ctx = tomcat.addContext(contextName, contextDir.getAbsolutePath());
+
+        CombinedRealm combinedRealm = new CombinedRealm();
+        Realm nullRealm = new NullRealm();
+        combinedRealm.addRealm(nullRealm);
+        ctx.setRealm(combinedRealm);
+
         tomcat.start();
 
+        getUrl("http://localhost:" + getPort());
+
         // Verify there are no Catalina MBeans
         onames = mbeanServer.queryNames(new ObjectName("Catalina:*"), null);
         log.info(MBeanDumper.dumpBeans(mbeanServer, onames));
@@ -162,8 +186,7 @@ public class TestRegistration extends TomcatBaseTest {
         }
 
         // Create the list of expected MBean names
-        String protocol=
-            getTomcatInstance().getConnector().getProtocolHandlerClassName();
+        String protocol = tomcat.getConnector().getProtocolHandlerClassName();
         if (protocol.indexOf("Nio") > 0) {
             protocol = "nio";
         } else if (protocol.indexOf("Apr") > 0) {
@@ -171,12 +194,14 @@ public class TestRegistration extends TomcatBaseTest {
         } else {
             protocol = "bio";
         }
-        String index = getTomcatInstance().getConnector().getProperty("nameIndex").toString();
+        String index = tomcat.getConnector().getProperty("nameIndex").toString();
         ArrayList<String> expected = new ArrayList<String>(Arrays.asList(basicMBeanNames()));
         expected.addAll(Arrays.asList(hostMBeanNames("localhost")));
         expected.addAll(Arrays.asList(contextMBeanNames("localhost", contextName)));
         expected.addAll(Arrays.asList(connectorMBeanNames("auto-" + index, protocol)));
         expected.addAll(Arrays.asList(optionalMBeanNames("localhost")));
+        expected.addAll(Arrays.asList(requestMBeanNames(
+                "auto-" + index + "-" + getPort(), protocol)));
 
         // Did we find all expected MBeans?
         ArrayList<String> missing = new ArrayList<String>(expected);
diff --git a/test/org/apache/jasper/compiler/TestNode.java b/test/org/apache/jasper/compiler/TestNode.java
new file mode 100644
index 0000000..db61637
--- /dev/null
+++ b/test/org/apache/jasper/compiler/TestNode.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.jasper.compiler;
+
+import java.io.File;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+public class TestNode extends TomcatBaseTest {
+
+    @Test
+    public void testJspAttributeIsLiteral() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+
+        File appDir =
+            new File("test/webapp-3.0");
+        // app dir is relative to server home
+        tomcat.addWebapp(null, "", appDir.getAbsolutePath());
+
+        tomcat.start();
+
+        ByteChunk res = getUrl("http://localhost:" + getPort() +
+                "/bug5nnnn/bug55642a.jsp");
+
+        String result = res.toString();
+        
+        Assert.assertTrue(
+                result.indexOf("/bug5nnnn/bug55642b.jsp?foo=bar&a=1&b=2") > 0);
+    }
+}
diff --git a/webapps/examples/websocket/index.html b/test/webapp-3.0/bug5nnnn/bug55642a.jsp
similarity index 59%
copy from webapps/examples/websocket/index.html
copy to test/webapp-3.0/bug5nnnn/bug55642a.jsp
index e90dc65..85d6b26 100644
--- a/webapps/examples/websocket/index.html
+++ b/test/webapp-3.0/bug5nnnn/bug55642a.jsp
@@ -1,4 +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.
@@ -6,26 +6,16 @@
   (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.
--->
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-    <meta http-equiv=Content-Type content="text/html">
-    <title>Apache Tomcat WebSocket Examples</title>
-</head>
-<body>
-<h3>Apache Tomcat WebSocket Examples</h3>
-<ul>
-    <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>
\ No newline at end of file
+  --%>
+<% request.setAttribute("target","bug55642b.jsp?foo=bar"); %>
+<jsp:forward page="${requestScope.target}">
+  <jsp:param name="a" value="1"/>
+  <jsp:param name="b" value="2"/>
+</jsp:forward>
\ No newline at end of file
diff --git a/webapps/examples/websocket/index.html b/test/webapp-3.0/bug5nnnn/bug55642b.jsp
similarity index 59%
copy from webapps/examples/websocket/index.html
copy to test/webapp-3.0/bug5nnnn/bug55642b.jsp
index e90dc65..3c51e9e 100644
--- a/webapps/examples/websocket/index.html
+++ b/test/webapp-3.0/bug5nnnn/bug55642b.jsp
@@ -1,4 +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.
@@ -6,26 +6,13 @@
   (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.
--->
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-    <meta http-equiv=Content-Type content="text/html">
-    <title>Apache Tomcat WebSocket Examples</title>
-</head>
-<body>
-<h3>Apache Tomcat WebSocket Examples</h3>
-<ul>
-    <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>
\ No newline at end of file
+  --%>
+<% String qs = request.getQueryString(); %>
+<p><%= request.getRequestURL() + ( qs == null ? "" : "?" + qs) %>
\ No newline at end of file
diff --git a/webapps/docs/appdev/deployment.xml b/webapps/docs/appdev/deployment.xml
index b626aca..8464562 100644
--- a/webapps/docs/appdev/deployment.xml
+++ b/webapps/docs/appdev/deployment.xml
@@ -178,7 +178,7 @@ configuration options, such as an access log, data sources, session manager
 configuration and more. This XML file must contain one Context element, which
 will be considered as if it was the child of the Host element corresponding
 to the Host to which the web application is being deployed. The
-<a href="../config/index.html">Tomcat configuration documentation</a> contains
+<a href="../config/context.html">Tomcat configuration documentation</a> contains
 information on the Context element.</p>
 
 </section>
@@ -224,7 +224,7 @@ approaches:</p>
     web applications</em>.  Tomcat includes a web application, deployed
     by default on context path <code>/manager</code>, that allows you to
     deploy and undeploy applications on a running Tomcat server without
-    restarting it.  See the administrator documentation (TODO: hyperlink)
+    restarting it. See <a href="../manager-howto.html">Manager App HOW-TO</a>
     for more information on using the Manager web application.<br/><br/></li>
 <li><em>Use "Manager" Ant Tasks In Your Build Script</em>.  Tomcat
     includes a set of custom task definitions for the <code>Ant</code>
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 6e088b5..8da6e91 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -55,7 +55,121 @@
   They eventually become mixed with the numbered issues. (I.e., numbered
   issues to not "pop up" wrt. others).
 -->
-<section name="Tomcat 7.0.45 (violetagg)">
+<section name="Tomcat 7.0.47 (violetagg)">
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        Fix regression with legacy WebSocket implementation in NIO and APR
+        connectors. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Web applications">
+    <changelog>
+      <fix>
+        Avoid hang observed with Java 6 on Windows when stopping the Tomcat
+        process via CTRL-C. (markt) 
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <fix>
+        <bug>55663</bug>: NOTICE files are corrected according to
+        <a href="http://www.apache.org/legal/src-headers.html#notice">NOTICE files requirements</a>.
+        (violetagg)
+      </fix>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 7.0.46 (violetagg)" rtext="not released">
+  <subsection name="Catalina">
+    <changelog>
+      <fix>
+        Only send a WebSocket close message on an IOException if the client has
+        not yet received a close control message from the server as the
+        IOException may be in response to the client continuing to send a
+        message after the server sent a close control message. (markt)
+      </fix>
+      <fix>
+        <bug>49134</bug>: Ensure nested realms are correctly destroyed, when a
+        CombinedRealm is destroyed. This ensures that the associated MBeans are
+        deregistered. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <scode>
+        Refactor APR/native connector to reduce the scope of
+        <code>localAddList</code>. (markt)
+      </scode>
+      <fix>
+        <bug>55602</bug>: Ensure that sockets removed from the Poller and then
+        closed in the APR/native connector are removed and then closed in a
+        thread-safe manner. (markt)
+      </fix>
+      <fix>
+        Update the APR/native connector to version 1.1.29. (violetagg)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        <bug>55642</bug>: Correct logic error in the JSP parser that was
+        incorrectly identifying EL expressions in jsp:param element values as a
+        literal string. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Cluster">
+    <changelog>
+      <add>
+        Add support for notify periodic event of cluster. (kfujino)
+      </add>
+    </changelog>
+  </subsection>
+  <subsection name="Web applications">
+    <changelog>
+      <fix>
+        Correct the javadoc for <code>org.apache.catalina.Lifecycle</code>.
+        (kfujino)
+      </fix>
+      <add>
+        Add document for sessionIdAttribute attribute in
+        <code>org.apache.catalina.ha.session.JvmRouteBinderValve</code>.
+        (kfujino)
+      </add>
+      <fix>
+        Handle the case when a user closes the browser whilst playing the
+        snake game in the JSR356 WebSocket examples. (markt)
+      </fix>
+      <fix>
+        Ensure Javadoc comments are associated with the correct elements in
+        <code>org.apache.tomcat.jni.Poll</code>. (markt)
+      </fix>
+      <add>
+        Expand Context documentation for the use of
+        <code>sessionCookiePath="/"</code> to make the implications
+        for session fixation protection clearer. (markt)
+      </add>
+      <fix>
+        <bug>55629</bug>: Ensure that the JMX notification listener added during
+        initialization of the servlet org.apache.catalina.manager.StatusManagerServlet
+        is removed in the destroy phase. (violetagg)
+      </fix>
+      <fix>
+        Correct the documentation for Deployment Organization in the App Dev
+        Guide. (violetagg)
+      </fix>
+      <add>
+        <bug>55639</bug>: Add a Drawboard WebSocket example. (kpreisser)
+      </add>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 7.0.45 (violetagg)" rtext="not released">
   <subsection name="Catalina">
     <changelog>
       <add>
diff --git a/webapps/docs/cluster-howto.xml b/webapps/docs/cluster-howto.xml
index 23c0b7a..16bdf81 100644
--- a/webapps/docs/cluster-howto.xml
+++ b/webapps/docs/cluster-howto.xml
@@ -221,7 +221,7 @@ should be completed:</p>
 <p>
     <b>Hint:</b><br/>
     With attribute <i>sessionIdAttribute</i> you can change the request attribute name that included the old session id.
-    Default attribute name is <i>org.apache.catalina.cluster.session.JvmRouteOrignalSessionID</i>.
+    Default attribute name is <i>org.apache.catalina.ha.session.JvmRouteOrignalSessionID</i>.
 </p>
 <p>
     <b>Trick:</b><br/>
@@ -564,7 +564,7 @@ should be completed:</p>
     </p><p></p>
 </li>
 <li><b><code>TomcatA</code> receives a request, invalidate is called on the session (<code>S1</code>)</b>
-    <p>The invalidate is call is intercepted, and the session is queued with invalidated sessions.
+    <p>The invalidate call is intercepted, and the session is queued with invalidated sessions.
         When the request is complete, instead of sending out the session that has changed, it sends out
         an "expire" message to TomcatB and TomcatB will invalidate the session as well.
     </p><p></p>
@@ -577,9 +577,9 @@ should be completed:</p>
 
 </li>
 <li><code>TomcatA</code> The session <code>S2</code> expires due to inactivity.
-    <p>The invalidate is call is intercepted the same was as when a session is invalidated by the user,
+    <p>The invalidate call is intercepted the same was as when a session is invalidated by the user,
        and the session is queued with invalidated sessions.
-       At this point, the invalidet session will not be replicated across until
+       At this point, the invalidated session will not be replicated across until
        another request comes through the system and checks the invalid queue.
     </p><p></p>
 </li>
diff --git a/webapps/docs/config/cluster-valve.xml b/webapps/docs/config/cluster-valve.xml
index 4f962bc..b27aa56 100644
--- a/webapps/docs/config/cluster-valve.xml
+++ b/webapps/docs/config/cluster-valve.xml
@@ -96,7 +96,10 @@
         Default value is <code>true</code>
         Runtime attribute to turn on and off turn over of the session's jvmRoute value.
       </attribute>
-
+      <attribute name="sessionIdAttribute" required="false">
+        Old sessionid before failover is registered in request attributes with  this attribute.
+        Default attribute name is <code>org.apache.catalina.ha.session.JvmRouteOrignalSessionID</code>.
+      </attribute>
     </attributes>
   </subsection>
 </section>
diff --git a/webapps/docs/config/context.xml b/webapps/docs/config/context.xml
index a1135e4..1ce7966 100644
--- a/webapps/docs/config/context.xml
+++ b/webapps/docs/config/context.xml
@@ -448,6 +448,14 @@
         useful for portlet specification implementations) set this attribute to
         <code>/</code> in the global <code>CATALINA_BASE/conf/context.xml</code>
         file.</p>
+        <p>Note: Once one web application using
+        <code>sessionCookiePath="/"</code> obtains a session, all
+        subsequent sessions for any other web application in the same host also
+        configured with <code>sessionCookiePath="/"</code> will always
+        use the same session ID. This holds even if the session is invalidated
+        and a new one created. This makes session fixation protection more
+        difficult and requires custom, Tomcat specific code to change the
+        session ID shared by the multiple applications.</p>
       </attribute>
 
       <attribute name="sessionCookiePathUsesTrailingSlash" required="false">
@@ -706,7 +714,10 @@
         is likely to result in instability. As such, enabling this should be
         viewed as an option of last resort in a development environment and is
         not recommended in a production environment. If not specified, the
-        default value of <code>false</code> will be used.</p>
+        default value of <code>false</code> will be used. If this feature is
+        enabled, web applications may take up to two seconds longer to stop as
+        executor threads are given up to two seconds to stop gracefully before
+        <code>Thread.stop()</code> is called on any remaining threads.</p>
       </attribute>
 
       <attribute name="clearReferencesStopTimerThreads" required = "false">
diff --git a/webapps/docs/proxy-howto.xml b/webapps/docs/proxy-howto.xml
index bc8f819..1aeb33e 100644
--- a/webapps/docs/proxy-howto.xml
+++ b/webapps/docs/proxy-howto.xml
@@ -38,7 +38,7 @@
 <p>Using standard configurations of Tomcat, web applications can ask for
 the server name and port number to which the request was directed for
 processing.  When Tomcat is running standalone with the
-<a href="config/coyote.html">Coyote HTTP/1.1 Connector</a>, it will generally
+<a href="config/http.html">HTTP/1.1 Connector</a>, it will generally
 report the server name specified in the request, and the port number on
 which the <strong>Connector</strong> is listening.  The servlet API
 calls of interest, for this purpose, are:</p>
diff --git a/webapps/docs/tomcat-docs.xsl b/webapps/docs/tomcat-docs.xsl
index 0a5c497..eddd459 100644
--- a/webapps/docs/tomcat-docs.xsl
+++ b/webapps/docs/tomcat-docs.xsl
@@ -17,7 +17,7 @@
 -->
 <!-- Content Stylesheet for "tomcat-docs" Documentation -->
 
-<!-- $Id: tomcat-docs.xsl 1460331 2013-03-24 12:21:31Z kkolinko $ -->
+<!-- $Id: tomcat-docs.xsl 1526466 2013-09-26 12:39:36Z kpreisser $ -->
 
 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   version="1.0">
@@ -61,7 +61,7 @@
   <xsl:variable name="attributes-color" select="'#023264'"/>
   <xsl:variable name="table-th-bg"      select="'#039acc'"/>
   <xsl:variable name="table-td-bg"      select="'#a0ddf0'"/>
-  <xsl:variable name="commentslink">/<xsl:value-of select="$sitedir"/>comments.html</xsl:variable>
+  <xsl:variable name="commentslink"><xsl:value-of select="$relative-path"/>/comments.html</xsl:variable>
 
   <!-- Process an entire document into an HTML page -->
   <xsl:template match="document">
diff --git a/webapps/docs/web-socket-howto.xml b/webapps/docs/web-socket-howto.xml
index fbb5302..14b98ec 100644
--- a/webapps/docs/web-socket-howto.xml
+++ b/webapps/docs/web-socket-howto.xml
@@ -43,9 +43,9 @@
 
 <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/">
+   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/trunk/webapps/examples/WEB-INF/classes/websocket/">
+   href="http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/">
    code</a>.</p>
 </section>
 
@@ -105,12 +105,10 @@
 
 <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/tags/TOMCAT_7_0_42/webapps/examples/websocket/">
+   href="http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/examples/websocket-deprecated/">
    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>
+   href="http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/tc7/">
+   code</a>.</p>
 </section>
 
 
diff --git a/webapps/examples/WEB-INF/classes/websocket/ExamplesConfig.java b/webapps/examples/WEB-INF/classes/websocket/ExamplesConfig.java
new file mode 100644
index 0000000..8b37b12
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/websocket/ExamplesConfig.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.websocket.Endpoint;
+import javax.websocket.server.ServerApplicationConfig;
+import javax.websocket.server.ServerEndpointConfig;
+
+import websocket.drawboard.DrawboardEndpoint;
+import websocket.echo.EchoEndpoint;
+
+public class ExamplesConfig implements ServerApplicationConfig {
+
+    @Override
+    public Set<ServerEndpointConfig> getEndpointConfigs(
+            Set<Class<? extends Endpoint>> scanned) {
+
+        Set<ServerEndpointConfig> result = new HashSet<ServerEndpointConfig>();
+
+        if (scanned.contains(EchoEndpoint.class)) {
+            result.add(ServerEndpointConfig.Builder.create(
+                    EchoEndpoint.class,
+                    "/websocket/echoProgrammatic").build());
+        }
+
+        if (scanned.contains(DrawboardEndpoint.class)) {
+            result.add(ServerEndpointConfig.Builder.create(
+                    DrawboardEndpoint.class,
+                    "/websocket/drawboard").build());
+        }
+
+        return result;
+    }
+
+
+    @Override
+    public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned) {
+        // Deploy all WebSocket endpoints defined by annotations in the examples
+        // web application. Filter out all others to avoid issues when running
+        // tests on Gump
+        Set<Class<?>> results = new HashSet<Class<?>>();
+        for (Class<?> clazz : scanned) {
+            if (clazz.getPackage().getName().startsWith("websocket.")) {
+                results.add(clazz);
+            }
+        }
+        return results;
+    }
+}
diff --git a/webapps/examples/WEB-INF/classes/websocket/drawboard/Client.java b/webapps/examples/WEB-INF/classes/websocket/drawboard/Client.java
new file mode 100644
index 0000000..32c8a66
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/websocket/drawboard/Client.java
@@ -0,0 +1,207 @@
+/*
+ *  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.drawboard;
+
+import java.io.IOException;
+import java.util.LinkedList;
+
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.RemoteEndpoint.Async;
+import javax.websocket.SendHandler;
+import javax.websocket.SendResult;
+import javax.websocket.Session;
+
+import websocket.drawboard.wsmessages.AbstractWebsocketMessage;
+import websocket.drawboard.wsmessages.BinaryWebsocketMessage;
+import websocket.drawboard.wsmessages.CloseWebsocketMessage;
+import websocket.drawboard.wsmessages.StringWebsocketMessage;
+
+/**
+ * Represents a client with methods to send messages asynchronously.
+ */
+public class Client {
+
+    private final Session session;
+    private final Async async;
+
+    /**
+     * Contains the messages wich are buffered until the previous
+     * send operation has finished.
+     */
+    private final LinkedList<AbstractWebsocketMessage> messagesToSend =
+            new LinkedList<AbstractWebsocketMessage>();
+    /**
+     * If this client is currently sending a messages asynchronously.
+     */
+    private volatile boolean isSendingMessage = false;
+    /**
+     * If this client is closing. If <code>true</code>, new messages to
+     * send will be ignored.
+     */
+    private volatile boolean isClosing = false;
+    /**
+     * The length of all current buffered messages, to avoid iterating
+     * over a linked list.
+     */
+    private volatile long messagesToSendLength = 0;
+
+    public Client(Session session) {
+        this.session = session;
+        this.async = session.getAsyncRemote();
+    }
+
+    /**
+     * Asynchronously closes the Websocket session. This will wait until all
+     * remaining messages have been sent to the Client and then close
+     * the Websocket session.
+     */
+    public void close() {
+        sendMessage(new CloseWebsocketMessage());
+    }
+
+    /**
+     * Sends the given message asynchronously to the client.
+     * If there is already a async sending in progress, then the message
+     * will be buffered and sent when possible.<br><br>
+     *
+     * This method can be called from multiple threads.
+     * @param msg
+     */
+    public void sendMessage(AbstractWebsocketMessage msg) {
+        synchronized (messagesToSend) {
+            if (!isClosing) {
+                // Check if we have a Close message
+                if (msg instanceof CloseWebsocketMessage) {
+                    isClosing = true;
+                }
+
+                if (isSendingMessage) {
+                    // Check if the buffered messages exceed
+                    // a specific amount - in that case, disconnect the client
+                    // to prevent DoS.
+                    // In this case we check if there are >= 1000 messages
+                    // or length(of all messages) >= 1000000 bytes.
+                    if (messagesToSend.size() >= 1000
+                            || messagesToSendLength >= 1000000) {
+                        isClosing = true;
+
+                        // Discard the new message and close the session immediately.
+                        CloseReason cr = new CloseReason(
+                                CloseCodes.VIOLATED_POLICY,
+                                "Send Buffer exceeded");
+                        try {
+                            session.close(cr);
+                        } catch (IOException e) {
+                            // Ignore
+                        }
+
+                    } else {
+
+                        // Check if the last message and the new message are
+                        // String messages - in that case we concatenate them
+                        // to reduce TCP overhead (using ";" as separator).
+                        if (msg instanceof StringWebsocketMessage
+                                && !messagesToSend.isEmpty()
+                                && messagesToSend.getLast()
+                                instanceof StringWebsocketMessage) {
+
+                            StringWebsocketMessage ms =
+                                    (StringWebsocketMessage) messagesToSend.removeLast();
+                            messagesToSendLength -= calculateMessageLength(ms);
+
+                            String concatenated = ms.getString() + ";" +
+                                    ((StringWebsocketMessage) msg).getString();
+                            msg = new StringWebsocketMessage(concatenated);
+                        }
+
+                        messagesToSend.add(msg);
+                        messagesToSendLength += calculateMessageLength(msg);
+                    }
+                } else {
+                    isSendingMessage = true;
+                    internalSendMessageAsync(msg);
+                }
+            }
+
+        }
+    }
+
+    private long calculateMessageLength(AbstractWebsocketMessage msg) {
+        if (msg instanceof BinaryWebsocketMessage) {
+            return ((BinaryWebsocketMessage) msg).getBytes().capacity();
+        } else if (msg instanceof StringWebsocketMessage) {
+            return ((StringWebsocketMessage) msg).getString().length() * 2;
+        }
+
+        return 0;
+    }
+
+    /**
+     * Internally sends the messages asynchronously.
+     * @param msg
+     */
+    private void internalSendMessageAsync(AbstractWebsocketMessage msg) {
+        try {
+            if (msg instanceof StringWebsocketMessage) {
+                StringWebsocketMessage sMsg = (StringWebsocketMessage) msg;
+                async.sendText(sMsg.getString(), sendHandler);
+
+            } else if (msg instanceof BinaryWebsocketMessage) {
+                BinaryWebsocketMessage bMsg = (BinaryWebsocketMessage) msg;
+                async.sendBinary(bMsg.getBytes(), sendHandler);
+
+            } else if (msg instanceof CloseWebsocketMessage) {
+                // Close the session.
+                session.close();
+            }
+        } catch (IllegalStateException ex) {
+            // Trying to write to the client when the session has
+            // already been closed.
+            // Ignore
+        } catch (IOException ex) {
+            // Trying to write to the client when the session has
+            // already been closed.
+            // Ignore
+        }
+    }
+
+
+
+    /**
+     * SendHandler that will continue to send buffered messages.
+     */
+    private final SendHandler sendHandler = new SendHandler() {
+        @Override
+        public void onResult(SendResult result) {
+            synchronized (messagesToSend) {
+
+                if (!messagesToSend.isEmpty()) {
+                    AbstractWebsocketMessage msg = messagesToSend.remove();
+                    messagesToSendLength -= calculateMessageLength(msg);
+
+                    internalSendMessageAsync(msg);
+
+                } else {
+                    isSendingMessage = false;
+                }
+
+            }
+        }
+    };
+
+}
diff --git a/webapps/examples/WEB-INF/classes/websocket/drawboard/DrawMessage.java b/webapps/examples/WEB-INF/classes/websocket/drawboard/DrawMessage.java
new file mode 100644
index 0000000..52d7971
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/websocket/drawboard/DrawMessage.java
@@ -0,0 +1,270 @@
+/*
+ *  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.drawboard;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.geom.Arc2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * A message that represents a drawing action.
+ * Note that we use primitive types instead of Point, Color etc.
+ * to reduce object allocation.<br><br>
+ *
+ * TODO: But a Color objects needs to be created anyway for drawing this
+ * onto a Graphics2D object, so this probably does not save much.
+ */
+public final class DrawMessage {
+
+    private int type;
+    private byte colorR, colorG, colorB, colorA;
+    private double thickness;
+    private double x1, y1, x2, y2;
+    private boolean lastInChain;
+
+    /**
+     * The type.<br>
+     * 1: Brush<br>
+     * 2: Line<br>
+     * 3: Rectangle<br>
+     * 4: Ellipse
+     */
+    public int getType() {
+        return type;
+    }
+    public void setType(int type) {
+        this.type = type;
+    }
+
+    public double getThickness() {
+        return thickness;
+    }
+    public void setThickness(double thickness) {
+        this.thickness = thickness;
+    }
+
+    public byte getColorR() {
+        return colorR;
+    }
+    public void setColorR(byte colorR) {
+        this.colorR = colorR;
+    }
+    public byte getColorG() {
+        return colorG;
+    }
+    public void setColorG(byte colorG) {
+        this.colorG = colorG;
+    }
+    public byte getColorB() {
+        return colorB;
+    }
+    public void setColorB(byte colorB) {
+        this.colorB = colorB;
+    }
+    public byte getColorA() {
+        return colorA;
+    }
+    public void setColorA(byte colorA) {
+        this.colorA = colorA;
+    }
+
+    public double getX1() {
+        return x1;
+    }
+    public void setX1(double x1) {
+        this.x1 = x1;
+    }
+    public double getX2() {
+        return x2;
+    }
+    public void setX2(double x2) {
+        this.x2 = x2;
+    }
+    public double getY1() {
+        return y1;
+    }
+    public void setY1(double y1) {
+        this.y1 = y1;
+    }
+    public double getY2() {
+        return y2;
+    }
+    public void setY2(double y2) {
+        this.y2 = y2;
+    }
+
+    /**
+     * Specifies if this DrawMessage is the last one in a chain
+     * (e.g. a chain of brush paths).<br>
+     * Currently it is unused.
+     */
+    public boolean isLastInChain() {
+        return lastInChain;
+    }
+    public void setLastInChain(boolean lastInChain) {
+        this.lastInChain = lastInChain;
+    }
+
+
+
+    public DrawMessage(int type, byte colorR, byte colorG, byte colorB,
+            byte colorA, double thickness, double x1, double x2, double y1,
+            double y2, boolean lastInChain) {
+
+        this.type = type;
+        this.colorR = colorR;
+        this.colorG = colorG;
+        this.colorB = colorB;
+        this.colorA = colorA;
+        this.thickness = thickness;
+        this.x1 = x1;
+        this.x2 = x2;
+        this.y1 = y1;
+        this.y2 = y2;
+        this.lastInChain = lastInChain;
+    }
+
+
+    /**
+     * Draws this DrawMessage onto the given Graphics2D.
+     * @param g
+     */
+    public void draw(Graphics2D g) {
+
+        g.setStroke(new BasicStroke((float) thickness,
+                BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
+        g.setColor(new Color(colorR & 0xFF, colorG & 0xFF, colorB & 0xFF,
+                colorA & 0xFF));
+
+        if (x1 == x2 && y1 == y2) {
+            // Always draw as arc to meet the behavior in the HTML5 Canvas.
+            Arc2D arc = new Arc2D.Double(x1, y1, 0, 0,
+                    0d, 360d, Arc2D.OPEN);
+            g.draw(arc);
+
+        } else if (type == 1 || type == 2) {
+            // Draw a line.
+            Line2D line = new Line2D.Double(x1, y1, x2, y2);
+            g.draw(line);
+
+        } else if (type == 3 || type == 4) {
+            double x1 = this.x1, x2 = this.x2,
+                    y1 = this.y1, y2 = this.y2;
+            if (x1 > x2) {
+                x1 = this.x2;
+                x2 = this.x1;
+            }
+            if (y1 > y2) {
+                y1 = this.y2;
+                y2 = this.y1;
+            }
+
+            // TODO: If (x1 == x2 || y1 == y2) draw as line.
+
+            if (type == 3) {
+                // Draw a rectangle.
+                Rectangle2D rect = new Rectangle2D.Double(x1, y1,
+                        x2 - x1, y2 - y1);
+                g.draw(rect);
+
+            } else if (type == 4) {
+                // Draw an ellipse.
+                Arc2D arc = new Arc2D.Double(x1, y1, x2 - x1, y2 - y1,
+                        0d, 360d, Arc2D.OPEN);
+                g.draw(arc);
+
+            }
+        }
+    }
+
+    /**
+     * Converts this message into a String representation that
+     * can be sent over WebSocket.<br>
+     * Since a DrawMessage consists only of numbers,
+     * we concatenate those numbers with a ",".
+     */
+    @Override
+    public String toString() {
+
+        return type + "," + (colorR & 0xFF) + "," + (colorG & 0xFF) + ","
+                + (colorB & 0xFF) + "," + (colorA & 0xFF) + "," + thickness
+                + "," + x1 + "," + y1 + "," + x2 + "," + y2 + ","
+                + (lastInChain ? "1" : "0");
+    }
+
+    public static DrawMessage parseFromString(String str)
+            throws ParseException {
+
+        int type;
+        byte[] colors = new byte[4];
+        double thickness;
+        double[] coords = new double[4];
+        boolean last;
+
+        try {
+            String[] elements = str.split(",");
+
+            type = Integer.parseInt(elements[0]);
+            if (!(type >= 1 && type <= 4))
+                throw new ParseException("Invalid type: " + type);
+
+            for (int i = 0; i < colors.length; i++) {
+                colors[i] = (byte) Integer.parseInt(elements[1 + i]);
+            }
+
+            thickness = Double.parseDouble(elements[5]);
+            if (Double.isNaN(thickness) || thickness < 0 || thickness > 100)
+                throw new ParseException("Invalid thickness: " + thickness);
+
+            for (int i = 0; i < coords.length; i++) {
+                coords[i] = Double.parseDouble(elements[6 + i]);
+                if (Double.isNaN(coords[i]))
+                    throw new ParseException("Invalid coordinate: "
+                            + coords[i]);
+            }
+
+            last = !"0".equals(elements[10]);
+
+        } catch (RuntimeException ex) {
+            throw new ParseException(ex);
+        }
+
+        DrawMessage m = new DrawMessage(type, colors[0], colors[1],
+                colors[2], colors[3], thickness, coords[0], coords[2],
+                coords[1], coords[3], last);
+
+        return m;
+    }
+
+    public static class ParseException extends Exception {
+        private static final long serialVersionUID = -6651972769789842960L;
+
+        public ParseException(Throwable root) {
+            super(root);
+        }
+
+        public ParseException(String message) {
+            super(message);
+        }
+    }
+
+
+
+}
diff --git a/webapps/examples/WEB-INF/classes/websocket/echo/WsConfigListener.java b/webapps/examples/WEB-INF/classes/websocket/drawboard/DrawboardContextListener.java
similarity index 60%
rename from webapps/examples/WEB-INF/classes/websocket/echo/WsConfigListener.java
rename to webapps/examples/WEB-INF/classes/websocket/drawboard/DrawboardContextListener.java
index 029881d..dd022ba 100644
--- a/webapps/examples/WEB-INF/classes/websocket/echo/WsConfigListener.java
+++ b/webapps/examples/WEB-INF/classes/websocket/drawboard/DrawboardContextListener.java
@@ -14,33 +14,24 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package websocket.echo;
+package websocket.drawboard;
 
 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 {
+public final class DrawboardContextListener 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);
-        }
+        // NO-OP
     }
 
     @Override
     public void contextDestroyed(ServletContextEvent sce) {
-        // NO-OP
+        // Shutdown our room.
+        Room room = DrawboardEndpoint.getRoom(false);
+        if (room != null) {
+            room.shutdown();
+        }
     }
 }
diff --git a/webapps/examples/WEB-INF/classes/websocket/drawboard/DrawboardEndpoint.java b/webapps/examples/WEB-INF/classes/websocket/drawboard/DrawboardEndpoint.java
new file mode 100644
index 0000000..1bd120b
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/websocket/drawboard/DrawboardEndpoint.java
@@ -0,0 +1,227 @@
+/*
+ *  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.drawboard;
+
+import java.io.EOFException;
+
+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 websocket.drawboard.DrawMessage.ParseException;
+import websocket.drawboard.wsmessages.StringWebsocketMessage;
+
+
+public final class DrawboardEndpoint extends Endpoint {
+
+    private static final Log log =
+            LogFactory.getLog(DrawboardEndpoint.class);
+
+
+    /**
+     * Our room where players can join.
+     */
+    private static volatile Room room = null;
+    private static final Object roomLock = new Object();
+
+    public static Room getRoom(boolean create) {
+        if (create) {
+            if (room == null) {
+                synchronized (roomLock) {
+                    if (room == null) {
+                        room = new Room();
+                    }
+                }
+            }
+            return room;
+        } else {
+            return room;
+        }
+    }
+
+    /**
+     * The player that is associated with this Endpoint and the current room.
+     * Note that this variable is only accessed from the Room Thread.<br><br>
+     *
+     * TODO: Currently, Tomcat uses an Endpoint instance once - however
+     * the java doc of endpoint says:
+     * "Each instance of a websocket endpoint is guaranteed not to be called by
+     * more than one thread at a time per active connection."
+     * This could mean that after calling onClose(), the instance
+     * could be reused for another connection so onOpen() will get called
+     * (possibly from another thread).<br>
+     * If this is the case, we would need a variable holder for the variables
+     * that are accessed by the Room thread, and read the reference to the holder
+     * at the beginning of onOpen, onMessage, onClose methods to ensure the room
+     * thread always gets the correct instance of the variable holder.
+     */
+    private Room.Player player;
+
+
+    @Override
+    public void onOpen(Session session, EndpointConfig config) {
+        // Set maximum messages size to 10.000 bytes.
+        session.setMaxTextMessageBufferSize(10000);
+        session.addMessageHandler(stringHandler);
+        final Client client = new Client(session);
+
+        final Room room = getRoom(true);
+        room.invokeAndWait(new Runnable() {
+            @Override
+            public void run() {
+                try {
+
+                    // Create a new Player and add it to the room.
+                    try {
+                        player = room.createAndAddPlayer(client);
+                    } catch (IllegalStateException ex) {
+                        // Probably the max. number of players has been
+                        // reached.
+                        client.sendMessage(new StringWebsocketMessage(
+                                "0" + ex.getLocalizedMessage()));
+                        // Close the connection.
+                        client.close();
+                    }
+
+                } catch (RuntimeException ex) {
+                    log.error("Unexpected exception: " + ex.toString(), ex);
+                }
+            }
+        });
+
+    }
+
+
+    @Override
+    public void onClose(Session session, CloseReason closeReason) {
+        Room room = getRoom(false);
+        if (room != null) {
+            room.invokeAndWait(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        // Player can be null if it couldn't enter the room
+                        if (player != null) {
+                            // Remove this player from the room.
+                            player.removeFromRoom();
+                        }
+                    } catch (RuntimeException ex) {
+                        log.error("Unexpected exception: " + ex.toString(), ex);
+                    }
+                }
+            });
+        }
+    }
+
+
+
+    @Override
+    public void onError(Session session, Throwable t) {
+        // Most likely cause is a user closing their browser. Check to see if
+        // the root cause is EOF and if it is ignore it.
+        // Protect against infinite loops.
+        int count = 0;
+        Throwable root = t;
+        while (root.getCause() != null && count < 20) {
+            root = root.getCause();
+            count ++;
+        }
+        if (root instanceof EOFException) {
+            // Assume this is triggered by the user closing their browser and
+            // ignore it.
+        } else {
+            log.error("onError: " + t.toString(), t);
+        }
+    }
+
+
+
+    private final MessageHandler.Whole<String> stringHandler =
+            new MessageHandler.Whole<String>() {
+
+        @Override
+        public void onMessage(final String message) {
+            // Invoke handling of the message in the room.
+            room.invokeAndWait(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+
+                        // Currently, the only types of messages the client will send
+                        // are draw messages prefixed by a Message ID
+                        // (starting with char '1'), and pong messages (starting
+                        // with char '0').
+                        // Draw messages should look like this:
+                        // ID|type,colR,colB,colG,colA,thickness,x1,y1,x2,y2,lastInChain
+
+                        boolean dontSwallowException = false;
+                        try {
+                            char messageType = message.charAt(0);
+                            String messageContent = message.substring(1);
+                            switch (messageType) {
+                            case '0':
+                                // Pong message.
+                                // Do nothing.
+                                break;
+
+                            case '1':
+                                // Draw message
+                                int indexOfChar = messageContent.indexOf('|');
+                                long msgId = Long.parseLong(
+                                        messageContent.substring(0, indexOfChar));
+
+                                DrawMessage msg = DrawMessage.parseFromString(
+                                        messageContent.substring(indexOfChar + 1));
+
+                                // Don't ingore RuntimeExceptions thrown by
+                                // this method
+                                // TODO: Find a better solution than this variable
+                                dontSwallowException = true;
+                                if (player != null) {
+                                    player.handleDrawMessage(msg, msgId);
+                                }
+                                dontSwallowException = false;
+
+                                break;
+                            }
+
+                        } catch (RuntimeException ex) {
+                            // Client sent invalid data.
+                            // Ignore, TODO: maybe close connection
+                            if (dontSwallowException) {
+                                throw ex;
+                            }
+                        } catch (ParseException ex) {
+                            // Client sent invalid data.
+                            // Ignore, TODO: maybe close connection
+                        }
+                    } catch (RuntimeException ex) {
+                        log.error("Unexpected exception: " + ex.toString(), ex);
+                    }
+                }
+            });
+
+        }
+    };
+
+
+}
diff --git a/webapps/examples/WEB-INF/classes/websocket/drawboard/Room.java b/webapps/examples/WEB-INF/classes/websocket/drawboard/Room.java
new file mode 100644
index 0000000..c45d5ab
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/websocket/drawboard/Room.java
@@ -0,0 +1,415 @@
+/*
+ *  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.drawboard;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.imageio.ImageIO;
+
+import websocket.drawboard.wsmessages.BinaryWebsocketMessage;
+import websocket.drawboard.wsmessages.StringWebsocketMessage;
+
+/**
+ * A Room represents a drawboard where a number of
+ * users participate.<br><br>
+ *
+ * Note: Instance methods should only be invoked by calling
+ * {@link #invokeAndWait(Runnable)} to ensure access is correctly synchronized.
+ */
+public final class Room {
+
+    /**
+     * Specifies the type of a room message that is sent to a client.<br>
+     * Note: Currently we are sending simple string messages - for production
+     * apps, a JSON lib should be used for object-level messages.<br><br>
+     *
+     * The number (single char) will be prefixed to the string when sending
+     * the message.
+     */
+    public static enum MessageType {
+        /**
+         * '0': Error: contains error message.
+         */
+        ERROR('0'),
+        /**
+         * '1': DrawMesssage: contains serialized DrawMessage(s) prefixed
+         *      with the current Player's {@link Player#lastReceivedMessageId}
+         *      and ",".<br>
+         *      Multiple draw messages are concatenated with "|" as separator.
+         */
+        DRAW_MESSAGE('1'),
+        /**
+         * '2': ImageMessage: Contains number of current players in this room.
+         *      After this message a Binary Websocket message will follow,
+         *      containing the current Room image as PNG.<br>
+         *      This is the first message that a Room sends to a new Player.
+         */
+        IMAGE_MESSAGE('2'),
+        /**
+         * '3': PlayerChanged: contains "+" or "-" which indicate a player
+         *      was added or removed to this Room.
+         */
+        PLAYER_CHANGED('3');
+
+        private final char flag;
+
+        private MessageType(char flag) {
+            this.flag = flag;
+        }
+
+    }
+
+
+    /**
+     * An object used to synchronize access to this Room.
+     */
+    private final Object syncObj = new Object();
+
+    /**
+     * Indicates if this room has already been shutdown.
+     */
+    private volatile boolean closed = false;
+
+    /**
+     * If <code>true</code>, outgoing DrawMessages will be buffered until the
+     * drawmessageBroadcastTimer ticks. Otherwise they will be sent
+     * immediately.
+     */
+    private static final boolean BUFFER_DRAW_MESSAGES = true;
+
+    /**
+     * A timer which sends buffered drawmessages to the client at once
+     * at a regular interval, to avoid sending a lot of very small
+     * messages which would cause TCP overhead and high CPU usage.
+     */
+    private final Timer drawmessageBroadcastTimer = new Timer();
+
+
+    /**
+     * The current image of the room drawboard. DrawMessages that are
+     * received from Players will be drawn onto this image.
+     */
+    private final BufferedImage roomImage =
+            new BufferedImage(900, 600, BufferedImage.TYPE_INT_RGB);
+    private final Graphics2D roomGraphics = roomImage.createGraphics();
+
+
+    /**
+     * The maximum number of players that can join this room.
+     */
+    private static final int MAX_PLAYER_COUNT = 100;
+
+    /**
+     * List of all currently joined players.
+     */
+    private final List<Player> players = new ArrayList<Player>();
+
+
+
+    public Room() {
+        roomGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                RenderingHints.VALUE_ANTIALIAS_ON);
+
+        // Clear the image with white background.
+        roomGraphics.setBackground(Color.WHITE);
+        roomGraphics.clearRect(0, 0, roomImage.getWidth(),
+                roomImage.getHeight());
+
+        // Schedule a TimerTask that broadcasts draw messages.
+        drawmessageBroadcastTimer.schedule(new TimerTask() {
+            @Override
+            public void run() {
+                invokeAndWait(new Runnable() {
+                    @Override
+                    public void run() {
+                        broadcastTimerTick();
+                    }
+                });
+            }
+        }, 30, 30);
+    }
+
+    /**
+     * Creates a Player from the given Client and adds it to this room.
+     * @param client the client
+     */
+    public Player createAndAddPlayer(Client client) {
+        if (players.size() >= MAX_PLAYER_COUNT) {
+            throw new IllegalStateException("Maximum player count ("
+                    + MAX_PLAYER_COUNT + ") has been reached.");
+        }
+
+        Player p = new Player(this, client);
+
+        // Broadcast to the other players that one player joined.
+        broadcastRoomMessage(MessageType.PLAYER_CHANGED, "+");
+
+        // Add the new player to the list.
+        players.add(p);
+
+        // Send him the current number of players and the current room image.
+        String content = String.valueOf(players.size());
+        p.sendRoomMessage(MessageType.IMAGE_MESSAGE, content);
+
+        // Store image as PNG
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        try {
+            ImageIO.write(roomImage, "PNG", bout);
+        } catch (IOException e) { /* Should never happen */ }
+
+
+        // Send the image as binary message.
+        BinaryWebsocketMessage msg = new BinaryWebsocketMessage(
+                ByteBuffer.wrap(bout.toByteArray()));
+        p.getClient().sendMessage(msg);
+
+        return p;
+
+    }
+
+    /**
+     * @see Player#removeFromRoom()
+     * @param p
+     */
+    private void internalRemovePlayer(Player p) {
+        players.remove(p);
+
+        // Broadcast that one player is removed.
+        broadcastRoomMessage(MessageType.PLAYER_CHANGED, "-");
+    }
+
+    /**
+     * @see Player#handleDrawMessage(DrawMessage, long)
+     * @param p
+     * @param msg
+     * @param msgId
+     */
+    private void internalHandleDrawMessage(Player p, DrawMessage msg,
+            long msgId) {
+        p.setLastReceivedMessageId(msgId);
+
+        // Draw the RoomMessage onto our Room Image.
+        msg.draw(roomGraphics);
+
+        // Broadcast the Draw Message.
+        broadcastDrawMessage(msg);
+    }
+
+
+    /**
+     * Broadcasts the given drawboard message to all connected players.<br>
+     * Note: For DrawMessages, please use
+     * {@link #broadcastDrawMessage(DrawMessage)}
+     * as this method will buffer them and prefix them with the correct
+     * last received Message ID.
+     * @param type
+     * @param content
+     */
+    private void broadcastRoomMessage(MessageType type, String content) {
+        for (Player p : players) {
+            p.sendRoomMessage(type, content);
+        }
+    }
+
+
+    /**
+     * Broadcast the given DrawMessage. This will buffer the message
+     * and the {@link #drawmessageBroadcastTimer} will broadcast them
+     * at a regular interval, prefixing them with the player's current
+     * {@link Player#lastReceivedMessageId}.
+     * @param msg
+     */
+    private void broadcastDrawMessage(DrawMessage msg) {
+        if (!BUFFER_DRAW_MESSAGES) {
+            String msgStr = msg.toString();
+
+            for (Player p : players) {
+                String s = String.valueOf(p.getLastReceivedMessageId())
+                        + "," + msgStr;
+                p.sendRoomMessage(MessageType.DRAW_MESSAGE, s);
+            }
+        } else {
+            for (Player p : players) {
+                p.getBufferedDrawMessages().add(msg);
+            }
+        }
+    }
+
+
+    /**
+     * Tick handler for the broadcastTimer.
+     */
+    private void broadcastTimerTick() {
+        // For each Player, send all per Player buffered
+        // DrawMessages, prefixing each DrawMessage with the player's
+        // lastReceivedMessageId.
+        // Multiple messages are concatenated with "|".
+
+        for (Player p : players) {
+
+            StringBuilder sb = new StringBuilder();
+            List<DrawMessage> drawMessages = p.getBufferedDrawMessages();
+
+            if (drawMessages.size() > 0) {
+                for (int i = 0; i < drawMessages.size(); i++) {
+                    DrawMessage msg = drawMessages.get(i);
+
+                    String s = String.valueOf(p.getLastReceivedMessageId())
+                            + "," + msg.toString();
+                    if (i > 0)
+                        sb.append("|");
+
+                    sb.append(s);
+                }
+                drawMessages.clear();
+
+                p.sendRoomMessage(MessageType.DRAW_MESSAGE, sb.toString());
+            }
+        }
+    }
+
+
+    /**
+     * Submits the given Runnable to the Room Executor and waits until it
+     * has been executed. Currently, this simply means that the Runnable
+     * will be run directly inside of a synchronized() block.
+     * @param task
+     */
+    public void invokeAndWait(Runnable task)  {
+        synchronized (syncObj) {
+            if (!closed) {
+                task.run();
+            }
+        }
+    }
+
+    /**
+     * Shuts down the roomExecutor and the drawmessageBroadcastTimer.
+     */
+    public void shutdown() {
+        invokeAndWait(new Runnable() {
+            @Override
+            public void run() {
+                closed = true;
+                drawmessageBroadcastTimer.cancel();
+                roomGraphics.dispose();
+            }
+        });
+    }
+
+
+    /**
+     * A Player participates in a Room. It is the interface between the
+     * {@link Room} and the {@link Client}.<br><br>
+     *
+     * Note: This means a player object is actually a join between Room and
+     * Client.
+     */
+    public final class Player {
+
+        /**
+         * The room to which this player belongs.
+         */
+        private Room room;
+
+        /**
+         * The room buffers the last draw message ID that was received from
+         * this player.
+         */
+        private long lastReceivedMessageId = 0;
+
+        private final Client client;
+
+        /**
+         * Buffered DrawMessages that will be sent by a Timer.
+         */
+        private final List<DrawMessage> bufferedDrawMessages =
+                new ArrayList<DrawMessage>();
+
+        private List<DrawMessage> getBufferedDrawMessages() {
+            return bufferedDrawMessages;
+        }
+
+        private Player(Room room, Client client) {
+            this.room = room;
+            this.client = client;
+        }
+
+        public Room getRoom() {
+            return room;
+        }
+
+        public Client getClient() {
+            return client;
+        }
+
+        /**
+         * Removes this player from its room, e.g. when
+         * the client disconnects.
+         */
+        public void removeFromRoom() {
+            if (room != null) {
+                room.internalRemovePlayer(this);
+                room = null;
+            }
+        }
+
+
+        private long getLastReceivedMessageId() {
+            return lastReceivedMessageId;
+        }
+        private void setLastReceivedMessageId(long value) {
+            lastReceivedMessageId = value;
+        }
+
+
+        /**
+         * Handles the given DrawMessage by drawing it onto this Room's
+         * image and by broadcasting it to the connected players.
+         * @param msg
+         * @param msgId
+         */
+        public void handleDrawMessage(DrawMessage msg, long msgId) {
+            room.internalHandleDrawMessage(this, msg, msgId);
+        }
+
+
+        /**
+         * Sends the given room message.
+         * @param type
+         * @param content
+         */
+        private void sendRoomMessage(MessageType type, String content) {
+            if (content == null || type == null)
+                throw null;
+
+            String completeMsg = String.valueOf(type.flag) + content;
+
+            client.sendMessage(new StringWebsocketMessage(completeMsg));
+        }
+    }
+}
diff --git a/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/AbstractWebsocketMessage.java b/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/AbstractWebsocketMessage.java
new file mode 100644
index 0000000..d425393
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/AbstractWebsocketMessage.java
@@ -0,0 +1,25 @@
+/*
+ *  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.drawboard.wsmessages;
+
+/**
+ * Abstract base class for Websocket Messages (binary or string)
+ * that can be buffered.
+ */
+public abstract class AbstractWebsocketMessage {
+
+}
diff --git a/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/BinaryWebsocketMessage.java b/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/BinaryWebsocketMessage.java
new file mode 100644
index 0000000..b16e1ae
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/BinaryWebsocketMessage.java
@@ -0,0 +1,34 @@
+/*
+ *  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.drawboard.wsmessages;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Represents a binary websocket message.
+ */
+public final class BinaryWebsocketMessage extends AbstractWebsocketMessage {
+    private final ByteBuffer bytes;
+
+    public BinaryWebsocketMessage(ByteBuffer bytes) {
+        this.bytes = bytes;
+    }
+
+    public ByteBuffer getBytes() {
+        return bytes;
+    }
+}
diff --git a/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/CloseWebsocketMessage.java b/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/CloseWebsocketMessage.java
new file mode 100644
index 0000000..44f48ad
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/CloseWebsocketMessage.java
@@ -0,0 +1,24 @@
+/*
+ *  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.drawboard.wsmessages;
+
+/**
+ * Represents a "close" message that closes the session.
+ */
+public class CloseWebsocketMessage extends AbstractWebsocketMessage {
+
+}
diff --git a/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/StringWebsocketMessage.java b/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/StringWebsocketMessage.java
new file mode 100644
index 0000000..49be369
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/StringWebsocketMessage.java
@@ -0,0 +1,34 @@
+/*
+ *  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.drawboard.wsmessages;
+
+/**
+ * Represents a string websocket message.
+ *
+ */
+public final class StringWebsocketMessage extends AbstractWebsocketMessage {
+    private final String string;
+
+    public StringWebsocketMessage(String string) {
+        this.string = string;
+    }
+
+    public String getString() {
+        return string;
+    }
+
+}
diff --git a/webapps/examples/WEB-INF/classes/websocket/echo/EchoEndpoint.java b/webapps/examples/WEB-INF/classes/websocket/echo/EchoEndpoint.java
index 374add1..68decef 100644
--- a/webapps/examples/WEB-INF/classes/websocket/echo/EchoEndpoint.java
+++ b/webapps/examples/WEB-INF/classes/websocket/echo/EchoEndpoint.java
@@ -24,7 +24,7 @@ import javax.websocket.MessageHandler;
 import javax.websocket.RemoteEndpoint;
 import javax.websocket.Session;
 
-public class EchoEndpoint extends Endpoint{
+public class EchoEndpoint extends Endpoint {
 
     @Override
     public void onOpen(Session session, EndpointConfig endpointConfig) {
diff --git a/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java b/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java
index 471a351..acfac59 100644
--- a/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java
+++ b/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java
@@ -21,6 +21,8 @@ import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
 
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
 import javax.websocket.Session;
 
 public class Snake {
@@ -65,7 +67,13 @@ public class Snake {
         try {
             session.getBasicRemote().sendText(msg);
         } catch (IOException ioe) {
-            // Ignore
+            CloseReason cr =
+                    new CloseReason(CloseCodes.CLOSED_ABNORMALLY, ioe.getMessage());
+            try {
+                session.close(cr);
+            } catch (IOException ioe2) {
+                // Ignore
+            }
         }
     }
 
diff --git a/webapps/examples/WEB-INF/classes/websocket/snake/SnakeAnnotation.java b/webapps/examples/WEB-INF/classes/websocket/snake/SnakeAnnotation.java
index ba63250..0edeeb3 100644
--- a/webapps/examples/WEB-INF/classes/websocket/snake/SnakeAnnotation.java
+++ b/webapps/examples/WEB-INF/classes/websocket/snake/SnakeAnnotation.java
@@ -17,11 +17,13 @@
 package websocket.snake;
 
 import java.awt.Color;
+import java.io.EOFException;
 import java.util.Iterator;
 import java.util.Random;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.websocket.OnClose;
+import javax.websocket.OnError;
 import javax.websocket.OnMessage;
 import javax.websocket.OnOpen;
 import javax.websocket.Session;
@@ -110,4 +112,24 @@ public class SnakeAnnotation {
         SnakeTimer.broadcast(String.format("{'type': 'leave', 'id': %d}",
                 Integer.valueOf(id)));
     }
+
+
+    @OnError
+    public void onError(Throwable t) throws Throwable {
+        // Most likely cause is a user closing their browser. Check to see if
+        // the root cause is EOF and if it is ignore it.
+        // Protect against infinite loops.
+        int count = 0;
+        Throwable root = t;
+        while (root.getCause() != null && count < 20) {
+            root = root.getCause();
+            count ++;
+        }
+        if (root instanceof EOFException) {
+            // Assume this is triggered by the user closing their browser and
+            // ignore it.
+        } else {
+            throw t;
+        }
+    }
 }
diff --git a/webapps/examples/WEB-INF/classes/websocket/snake/SnakeTimer.java b/webapps/examples/WEB-INF/classes/websocket/snake/SnakeTimer.java
index d831c18..1692b8e 100644
--- a/webapps/examples/WEB-INF/classes/websocket/snake/SnakeTimer.java
+++ b/webapps/examples/WEB-INF/classes/websocket/snake/SnakeTimer.java
@@ -79,7 +79,15 @@ public class SnakeTimer {
 
     protected static void broadcast(String message) {
         for (Snake snake : SnakeTimer.getSnakes()) {
-            snake.sendMessage(message);
+            try {
+                snake.sendMessage(message);
+            } catch (IllegalStateException ise) {
+                // An ISE can occur if an attempt is made to write to a
+                // WebSocket connection after it has been closed. The
+                // alternative to catching this exception is to synchronise
+                // the writes to the clients along with the addSnake() and
+                // removeSnake() methods that are already synchronised.
+            }
         }
     }
 
diff --git a/webapps/examples/WEB-INF/web.xml b/webapps/examples/WEB-INF/web.xml
index c75eb86..81b56ad 100644
--- a/webapps/examples/WEB-INF/web.xml
+++ b/webapps/examples/WEB-INF/web.xml
@@ -399,4 +399,9 @@
       <servlet-name>wsSnake</servlet-name>
       <url-pattern>/websocket/tc7/snake</url-pattern>
     </servlet-mapping>
+    <!-- Websocket examples -->
+    <listener>
+        <listener-class>websocket.drawboard.DrawboardContextListener</listener-class>
+    </listener>
+
 </web-app>
diff --git a/webapps/examples/index.html b/webapps/examples/index.html
index 9039457..f58bfef 100644
--- a/webapps/examples/index.html
+++ b/webapps/examples/index.html
@@ -14,19 +14,19 @@
   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>
+<!DOCTYPE HTML><html lang="en"><head>
+<meta charset="UTF-8">
+<title>Apache Tomcat Examples</title>
+</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 (JSR356) Examples</a></li>
+<li><a href="websocket/index.xhtml">WebSocket (JSR356) Examples</a></li>
 <li><a href="websocket-deprecated">WebSocket Examples using the deprecated
     Apache Tomcat proprietary API</a></li>
 </ul>
-</BODY></HTML>
+</body></html>
diff --git a/webapps/examples/jsp/jsptoserv/jts.html b/webapps/examples/jsp/jsptoserv/jts.html
index 0e7e3fa..55927e7 100644
--- a/webapps/examples/jsp/jsptoserv/jts.html
+++ b/webapps/examples/jsp/jsptoserv/jts.html
@@ -27,7 +27,7 @@
 <h3><a href="jsptoservlet.jsp.html">Source Code for JSP calling servlet <font color="#0000FF"></a>
   </font> </h3>
 
-<h3><a href="servletToJsp.java.html">Source Code for Servlet calling JSP
+<h3><a href="ServletToJsp.java.html">Source Code for Servlet calling JSP
 <font color="#0000FF"></a> </font> </h3>
 
 </body>
diff --git a/webapps/examples/websocket-deprecated/index.html b/webapps/examples/websocket-deprecated/index.html
index 3947fa8..91511aa 100644
--- a/webapps/examples/websocket-deprecated/index.html
+++ b/webapps/examples/websocket-deprecated/index.html
@@ -28,6 +28,6 @@
     <li><a href="snake.html">Multiplayer snake example</a></li>
 </ul>
 <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> 
+   356 <a href="../websocket/index.xhtml">Java WebSocket 1.0 API</a>.</p> 
 </body>
 </html>
\ No newline at end of file
diff --git a/webapps/examples/websocket/chat.html b/webapps/examples/websocket/chat.xhtml
similarity index 80%
rename from webapps/examples/websocket/chat.html
rename to webapps/examples/websocket/chat.xhtml
index 127eca4..b74cff3 100644
--- a/webapps/examples/websocket/chat.html
+++ b/webapps/examples/websocket/chat.xhtml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
@@ -14,11 +15,10 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<!DOCTYPE html>
-<html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <head>
     <title>Apache Tomcat WebSocket Examples: Chat</title>
-    <style type="text/css">
+    <style type="text/css"><![CDATA[
         input#chat {
             width: 410px
         }
@@ -41,8 +41,8 @@
             padding: 0;
             margin: 0;
         }
-    </style>
-    <script type="text/javascript">
+    ]]></style>
+    <script type="application/javascript"><![CDATA[
         var Chat = {};
 
         Chat.socket = null;
@@ -108,17 +108,26 @@
 
         Chat.initialize();
 
-    </script>
+
+        document.addEventListener("DOMContentLoaded", function() {
+            // Remove elements with "noscript" class - <noscript> is not allowed in XHTML
+            var noscripts = document.getElementsByClassName("noscript");
+            for (var i = 0; i < noscripts.length; i++) {
+                noscripts[i].parentNode.removeChild(noscripts[i]);
+            }
+        }, false);
+
+    ]]></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 class="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></div>
 <div>
     <p>
-        <input type="text" placeholder="type and press enter to chat" id="chat">
+        <input type="text" placeholder="type and press enter to chat" id="chat" />
     </p>
     <div id="console-container">
-        <div id="console"></div>
+        <div id="console"/>
     </div>
 </div>
 </body>
diff --git a/webapps/examples/websocket/drawboard.xhtml b/webapps/examples/websocket/drawboard.xhtml
new file mode 100644
index 0000000..8275dd7
--- /dev/null
+++ b/webapps/examples/websocket/drawboard.xhtml
@@ -0,0 +1,807 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+    <title>Apache Tomcat WebSocket Examples: Drawboard</title>
+    <style type="text/css"><![CDATA[
+
+        body {
+            font-family: Arial, sans-serif;
+            font-size: 11pt;
+            background-color: #eeeeea;
+            padding: 10px;
+        }
+
+        #console-container {
+            float: left;
+            background-color: #fff;
+            width: 250px;
+        }
+
+        #console {
+            font-size: 10pt;
+            height: 600px;
+            overflow-y: scroll;
+            padding-left: 5px;
+            padding-right: 5px;
+        }
+
+        #console p {
+            padding: 0;
+            margin: 0;
+        }
+
+        #drawContainer {
+            float: left;
+            display: none;
+            margin-right: 25px;
+        }
+
+        #drawContainer canvas {
+            display: block;
+            -ms-touch-action: none;
+            touch-action: none; /* Disable touch behaviors, like pan and zoom */
+            cursor: crosshair;
+        }
+
+        #labelContainer {
+            margin-bottom: 15px;
+        }
+
+        #drawContainer, #console-container {
+            box-shadow: 0px 0px 8px 3px #bbb;
+            border: 1px solid #CCCCCC;
+        }
+
+    ]]></style>
+    <script type="application/javascript"><![CDATA[
+    "use strict";
+
+    (function() {
+
+        document.addEventListener("DOMContentLoaded", function() {
+            // Remove elements with "noscript" class - <noscript> is not
+            // allowed in XHTML
+            var noscripts = document.getElementsByClassName("noscript");
+            for (var i = 0; i < noscripts.length; i++) {
+                noscripts[i].parentNode.removeChild(noscripts[i]);
+            }
+
+
+            var Console = {};
+
+            Console.log = (function() {
+                var consoleContainer =
+                    document.getElementById("console-container");
+                var console = document.createElement("div");
+                console.setAttribute("id", "console");
+                consoleContainer.appendChild(console);
+
+                return function(message) {
+                    var p = document.createElement('p');
+                    p.style.wordWrap = "break-word";
+                    p.appendChild(document.createTextNode(message));
+                    console.appendChild(p);
+                    while (console.childNodes.length > 25) {
+                        console.removeChild(console.firstChild);
+                    }
+                    console.scrollTop = console.scrollHeight;
+                }
+            })();
+
+
+            function Room(drawContainer) {
+
+                /* A pausable event forwarder that can be used to pause and
+                 * resume handling of events (e.g. when we need to wait
+                 * for a Image's load event before we can process further
+                 * WebSocket messages).
+                 * The object's callFunction(func) should be called from an
+                 * event handler and give the function to handle the event as
+                 * argument.
+                 * Call pauseProcessing() to suspend event forwarding and
+                 * resumeProcessing() to resume it.
+                 */
+                function PausableEventForwarder() {
+
+                    var pauseProcessing = false;
+                    // Queue for buffering functions to be called.
+                    var functionQueue = [];
+
+                    this.callFunction = function(func) {
+                        // If message processing is paused, we push it
+                        // into the queue - otherwise we process it directly.
+                        if (pauseProcessing) {
+                            functionQueue.push(func);
+                        } else {
+                            func();
+                        }
+                    };
+
+                    this.pauseProcessing = function() {
+                        pauseProcessing = true;
+                    };
+
+                    this.resumeProcessing = function() {
+                        pauseProcessing = false;
+
+                        // Process all queued functions until some handler calls
+                        // pauseProcessing() again.
+                        while (functionQueue.length > 0 && !pauseProcessing) {
+                            var func = functionQueue.pop();
+                            func();
+                        }
+                    };
+                }
+
+                // The WebSocket object.
+                var socket;
+                // ID of the timer which sends ping messages.
+                var pingTimerId;
+
+                var isStarted = false;
+                var playerCount = 0;
+
+                // An array of PathIdContainer objects that the server
+                // did not yet handle.
+                // They are ordered by id (ascending).
+                var pathsNotHandled = [];
+
+                var nextMsgId = 1;
+
+                var canvasDisplay = document.createElement("canvas");
+                var canvasBackground = document.createElement("canvas");
+                var canvasServerImage = document.createElement("canvas");
+                var canvasArray = [canvasDisplay, canvasBackground,
+                    canvasServerImage];
+                canvasDisplay.addEventListener("mousedown", function(e) {
+                    // Prevent default mouse event to prevent browsers from marking text
+                    // (and Chrome from displaying the "text" cursor).
+                    e.preventDefault();
+                }, false);
+
+                var labelPlayerCount = document.createTextNode("0");
+                var optionContainer = document.createElement("div");
+
+
+                var canvasDisplayCtx = canvasDisplay.getContext("2d");
+                var canvasBackgroundCtx = canvasBackground.getContext("2d");
+                var canvasServerImageCtx = canvasServerImage.getContext("2d");
+                var canvasMouseMoveHandler;
+                var canvasMouseDownHandler;
+
+                var mouseInWindow = false;
+                var mouseDown = false;
+                var currentMouseX = 0, currentMouseY = 0;
+                var currentPreviewPath = null;
+
+                var availableColors = [];
+                var currentColorIndex;
+                var colorContainers;
+                var previewTransparency = 0.65;
+
+                var availableThicknesses = [2, 3, 6, 10, 16, 28, 50];
+                var currentThicknessIndex;
+                var thicknessContainers;
+
+                var availableDrawTypes = [
+                           { name: "Brush", id: 1, continuous: true },
+                           { name: "Line", id: 2, continuous: false },
+                           { name: "Rectangle", id: 3, continuous: false },
+                           { name: "Ellipse", id: 4, continuous: false }
+                ];
+                var currentDrawTypeIndex;
+                var drawTypeContainers;
+
+
+                var labelContainer = document.getElementById("labelContainer");
+                var placeholder = document.createElement("div");
+                placeholder.appendChild(document.createTextNode("Loading... "));
+                var progressElem = document.createElement("progress");
+                placeholder.appendChild(progressElem);
+
+                labelContainer.appendChild(placeholder);
+
+                function rgb(color) {
+                       return "rgba(" + color[0] + "," + color[1] + ","
+                               + color[2] + "," + color[3] + ")";
+                   }
+
+                function PathIdContainer(path, id) {
+                    this.path = path;
+                    this.id = id;
+                }
+
+                function Path(type, color, thickness, x1, y1, x2, y2, lastInChain) {
+                    this.type = type;
+                    this.color = color;
+                    this.thickness = thickness;
+                    this.x1 = x1;
+                    this.y1 = y1;
+                    this.x2 = x2;
+                    this.y2 = y2;
+                    this.lastInChain = lastInChain;
+
+                    function ellipse(ctx, x, y, w, h) {
+                        /* Drawing a ellipse cannot be done directly in a
+                         * CanvasRenderingContext2D - we need to use drawArc()
+                         * in conjunction with scaling the context so that we
+                         * get the needed proportion.
+                         */
+                        ctx.save();
+
+                        // Translate and scale the context so that we can draw
+                        // an arc at (0, 0) with a radius of 1.
+                        ctx.translate(x + w / 2, y + h / 2);
+                        ctx.scale(w / 2, h / 2);
+
+                        ctx.beginPath();
+                        ctx.arc(0, 0, 1, 0, Math.PI * 2, false);
+
+                        ctx.restore();
+                    }
+
+                    this.draw = function(ctx) {
+                        ctx.beginPath();
+                        ctx.lineCap = "round";
+                        ctx.lineWidth = thickness;
+                        var style = rgb(color);
+                        ctx.strokeStyle = style;
+
+                        if (x1 == x2 && y1 == y2) {
+                            // Always draw as arc to meet the behavior
+                            // in Java2D.
+                            ctx.fillStyle = style;
+                            ctx.arc(x1, y1, thickness / 2.0, 0,
+                                    Math.PI * 2.0, false);
+                            ctx.fill();
+                        } else {
+                            if (type == 1 || type == 2) {
+                                // Draw a line.
+                                ctx.moveTo(x1, y1);
+                                ctx.lineTo(x2, y2);
+                                ctx.stroke();
+                            } else if (type == 3) {
+                                // Draw a rectangle.
+                                if (x1 == x2 || y1 == y2) {
+                                    // Draw as line
+                                    ctx.moveTo(x1, y1);
+                                    ctx.lineTo(x2, y2);
+                                    ctx.stroke();
+                                } else {
+                                    ctx.strokeRect(x1, y1, x2 - x1, y2 - y1);
+                                }
+                            } else if (type == 4) {
+                                // Draw a ellipse.
+                                ellipse(ctx, x1, y1, x2 - x1, y2 - y1);
+                                ctx.closePath();
+                                ctx.stroke();
+                            }
+                        }
+                    };
+                }
+
+
+                function connect() {
+                    var host = (window.location.protocol == "https:"
+                            ? "wss://" : "ws://") + window.location.host
+                            + "/examples/websocket/drawboard";
+                    socket = new WebSocket(host);
+
+                    /* Use a pausable event forwarder.
+                     * This is needed when we load an Image object with data
+                     * from a previous message, because we must wait until the
+                     * Image's load event it raised before we can use it (and
+                     * in the meantime the socket.message event could be
+                     * raised).
+                     * Therefore we need this pausable event handler to handle
+                     * e.g. socket.onmessage and socket.onclose.
+                     */
+                    var eventForwarder = new PausableEventForwarder();
+
+                    socket.onopen = function () {
+                        // Socket has opened. Now wait for the server to
+                        // send us the initial packet.
+                        Console.log("WebSocket connection opened.");
+
+                        // Set up a timer for pong messages.
+                        pingTimerId = window.setInterval(function() {
+                            socket.send("0");
+                        }, 30000);
+                    };
+
+                    socket.onclose = function () {
+                        eventForwarder.callFunction(function() {
+                            Console.log("WebSocket connection closed.");
+                            disableControls();
+
+                            // Disable pong timer.
+                            window.clearInterval(pingTimerId);
+                        });
+                    };
+
+                    // Handles an incoming Websocket message.
+                    var handleOnMessage = function(message) {
+
+                        // Split joined message and process them
+                        // invidividually.
+                        var messages = message.data.split(";");
+                        for (var msgArrIdx = 0; msgArrIdx < messages.length;
+                                msgArrIdx++) {
+                            var msg = messages[msgArrIdx];
+                            var type = msg.substring(0, 1);
+
+                            if (type == "0") {
+                                // Error message.
+                                var error = msg.substring(1);
+                                // Log it to the console and show an alert.
+                                Console.log("Error: " + error);
+                                alert(error);
+
+                            } else {
+                                if (!isStarted) {
+                                    if (type == "2") {
+                                        // Initial message. It contains the
+                                        // number of players.
+                                        // After this message we will receive
+                                        // a binary message containing the current
+                                        // room image as PNG.
+                                        playerCount = parseInt(msg.substring(1));
+
+                                        refreshPlayerCount();
+
+                                        // The next message will be a binary
+                                        // message containing the room images
+                                        // as PNG. Therefore we temporarily swap
+                                        // the message handler.
+                                        var originalHandler = handleOnMessage;
+                                        handleOnMessage = function(message) {
+                                            // First, we restore the original handler.
+                                            handleOnMessage = originalHandler;
+
+                                            // Read the image.
+                                            var blob = message.data;
+                                            // Create new blob with correct MIME type.
+                                            blob = new Blob([blob], {type : "image/png"});
+
+                                            var url = URL.createObjectURL(blob);
+
+                                            var img = new Image();
+
+                                            // We must wait until the onload event is
+                                            // raised until we can draw the image onto
+                                            // the canvas.
+                                            // Therefore we need to pause the event
+                                            // forwarder until the image is loaded.
+                                            eventForwarder.pauseProcessing();
+
+                                            img.onload = function() {
+
+                                                // Release the object URL.
+                                                URL.revokeObjectURL(url);
+
+                                                // Set the canvases to the correct size.
+                                                for (var i = 0; i < canvasArray.length; i++) {
+                                                    canvasArray[i].width = img.width;
+                                                    canvasArray[i].height = img.height;
+                                                }
+
+                                                // Now draw the image on the last canvas.
+                                                canvasServerImageCtx.clearRect(0, 0,
+                                                        canvasServerImage.width,
+                                                        canvasServerImage.height);
+                                                canvasServerImageCtx.drawImage(img, 0, 0);
+
+                                                // Draw it on the background canvas.
+                                                canvasBackgroundCtx.drawImage(canvasServerImage,
+                                                        0, 0);
+
+                                                // Refresh the display canvas.
+                                                refreshDisplayCanvas();
+
+                                                isStarted = true;
+                                                startControls();
+
+
+                                                // Finally, resume the event forwarder.
+                                                eventForwarder.resumeProcessing();
+                                            };
+
+                                            img.src = url;
+                                        };
+                                    }
+                                } else {
+                                    if (type == "3") {
+                                        // The number of players in this room changed.
+                                        var playerAdded = msg.substring(1) == "+";
+                                        playerCount += playerAdded ? 1 : -1;
+                                        refreshPlayerCount();
+
+                                        Console.log("Player " + (playerAdded
+                                                ? "joined." : "left."));
+
+                                    } else if (type == "1") {
+                                        // We received a new DrawMessage.
+                                        var maxLastHandledId = -1;
+                                        var drawMessages = msg.substring(1).split("|");
+                                        for (var i = 0; i < drawMessages.length; i++) {
+                                            var elements = drawMessages[i].split(",");
+                                            var lastHandledId = parseInt(elements[0]);
+                                               maxLastHandledId = Math.max(maxLastHandledId,
+                                                       lastHandledId);
+
+                                            var path = new Path(
+                                                    parseInt(elements[1]),
+                                                    [parseInt(elements[2]),
+                                                    parseInt(elements[3]),
+                                                    parseInt(elements[4]),
+                                                    parseInt(elements[5]) / 255.0],
+                                                    parseFloat(elements[6]),
+                                                    parseFloat(elements[7]),
+                                                    parseFloat(elements[8]),
+                                                    parseFloat(elements[9]),
+                                                    parseFloat(elements[10]),
+                                                    elements[11] != "0");
+
+                                            // Draw the path onto the last canvas.
+                                            path.draw(canvasServerImageCtx);
+                                        }
+
+                                        // Draw the last canvas onto the background one.
+                                        canvasBackgroundCtx.drawImage(canvasServerImage,
+                                                0, 0);
+
+                                        // Now go through the pathsNotHandled array and
+                                        // remove the paths that were already handled by
+                                        // the server.
+                                        while (pathsNotHandled.length > 0
+                                                && pathsNotHandled[0].id <= maxLastHandledId)
+                                            pathsNotHandled.shift();
+
+                                        // Now me must draw the remaining paths onto
+                                        // the background canvas.
+                                        for (var i = 0; i < pathsNotHandled.length; i++) {
+                                            pathsNotHandled[i].path.draw(canvasBackgroundCtx);
+                                        }
+
+                                        refreshDisplayCanvas();
+                                    }
+                                }
+                            }
+                        }
+                    };
+
+                    socket.onmessage = function(message) {
+                        eventForwarder.callFunction(function() {
+                            handleOnMessage(message);
+                        });
+                    };
+
+                }
+
+
+                function refreshPlayerCount() {
+                    labelPlayerCount.nodeValue = String(playerCount);
+                }
+
+                function refreshDisplayCanvas() {
+                    canvasDisplayCtx.drawImage(canvasBackground, 0, 0);
+                    if (currentPreviewPath != null) {
+                        // Draw the preview path.
+                        currentPreviewPath.draw(canvasDisplayCtx);
+
+                    } else if (mouseInWindow && !mouseDown) {
+                        canvasDisplayCtx.beginPath();
+                        var color = availableColors[currentColorIndex].slice(0);
+                        color[3] = previewTransparency;
+                        canvasDisplayCtx.fillStyle = rgb(color);
+
+                        canvasDisplayCtx.arc(currentMouseX, currentMouseY,
+                                availableThicknesses[currentThicknessIndex] / 2,
+                                0, Math.PI * 2.0, true);
+                        canvasDisplayCtx.fill();
+                    }
+
+                }
+
+                function startControls() {
+                    labelContainer.removeChild(placeholder);
+                    placeholder = undefined;
+
+                    labelContainer.appendChild(
+                            document.createTextNode("Number of Players: "));
+                    labelContainer.appendChild(labelPlayerCount);
+
+
+                    drawContainer.style.display = "block";
+                    drawContainer.appendChild(canvasDisplay);
+
+                    drawContainer.appendChild(optionContainer);
+                    
+                    canvasMouseDownHandler = function(e) {
+                        if (e.button == 0) {
+                            currentMouseX = e.pageX - canvasDisplay.offsetLeft;
+                            currentMouseY = e.pageY - canvasDisplay.offsetTop;
+
+                            mouseDown = true;
+                            canvasMouseMoveHandler(e);
+
+                        } else if (mouseDown) {
+                            // Cancel drawing.
+                            mouseDown = false;
+                            currentPreviewPath = null;
+
+                            currentMouseX = e.pageX - canvasDisplay.offsetLeft;
+                            currentMouseY = e.pageY - canvasDisplay.offsetTop;
+
+                            refreshDisplayCanvas();
+                        }
+                    };
+                    canvasDisplay.addEventListener("mousedown", canvasMouseDownHandler, false);
+
+                    canvasMouseMoveHandler = function(e) {
+                        mouseInWindow = true;
+                        var mouseX = e.pageX - canvasDisplay.offsetLeft;
+                        var mouseY = e.pageY - canvasDisplay.offsetTop;
+
+                        if (mouseDown) {
+                            var drawType = availableDrawTypes[currentDrawTypeIndex];
+
+                            if (drawType.continuous) {
+
+                                var path = new Path(drawType.id,
+                                        availableColors[currentColorIndex],
+                                        availableThicknesses[currentThicknessIndex],
+                                        currentMouseX, currentMouseY, mouseX,
+                                        mouseY, false);
+                                // Draw it on the background canvas.
+                                path.draw(canvasBackgroundCtx);
+
+                                // Send it to the sever.
+                                pushPath(path);
+
+                                // Refresh old coordinates
+                                currentMouseX = mouseX;
+                                currentMouseY = mouseY;
+
+                            } else {
+                                // Create a new preview path.
+                                var color = availableColors[currentColorIndex].slice(0);
+                                color[3] = previewTransparency;
+                                currentPreviewPath = new Path(drawType.id,
+                                        color,
+                                        availableThicknesses[currentThicknessIndex],
+                                        currentMouseX, currentMouseY, mouseX,
+                                        mouseY, false);
+                            }
+                        } else {
+                            currentMouseX = mouseX;
+                            currentMouseY = mouseY;
+                        }
+
+                        refreshDisplayCanvas();
+                    };
+                    canvasDisplay.addEventListener("mousemove", canvasMouseMoveHandler, false);
+
+                    canvasDisplay.addEventListener("mouseup", function(e) {
+                        if (e.button == 0) {
+                            if (mouseDown) {
+                                mouseDown = false;
+                                currentPreviewPath = null;
+
+                                var mouseX = e.pageX - canvasDisplay.offsetLeft;
+                                var mouseY = e.pageY - canvasDisplay.offsetTop;
+                                var drawType = availableDrawTypes[currentDrawTypeIndex];
+
+                                var path = new Path(drawType.id, availableColors[currentColorIndex],
+                                        availableThicknesses[currentThicknessIndex],
+                                        currentMouseX, currentMouseY, mouseX,
+                                        mouseY, true);
+                                // Draw it on the background canvas.
+                                path.draw(canvasBackgroundCtx);
+
+                                // Send it to the sever.
+                                pushPath(path);
+
+                                // Refresh old coordinates
+                                currentMouseX = mouseX;
+                                currentMouseY = mouseY;
+
+                                refreshDisplayCanvas();
+                            }
+                        }
+                    }, false);
+
+                    canvasDisplay.addEventListener("mouseout", function(e) {
+                        mouseInWindow = false;
+                        refreshDisplayCanvas();
+                    }, false);
+
+
+                    // Create color and thickness controls.
+                    var colorContainersBox = document.createElement("div");
+                    colorContainersBox.setAttribute("style",
+                            "margin: 4px; border: 1px solid #bbb; border-radius: 3px;");
+                    optionContainer.appendChild(colorContainersBox);
+
+                    colorContainers = new Array(3 * 3 * 3);
+                    for (var i = 0; i < colorContainers.length; i++) {
+                        var colorContainer = colorContainers[i] =
+                            document.createElement("div");
+                        var color = availableColors[i] =
+                            [
+                                Math.floor((i % 3) * 255 / 2),
+                                Math.floor((Math.floor(i / 3) % 3) * 255 / 2),
+                                Math.floor((Math.floor(i / (3 * 3)) % 3) * 255 / 2),
+                                1.0
+                            ];
+                        colorContainer.setAttribute("style",
+                                "margin: 3px; width: 18px; height: 18px; "
+                                + "float: left; background-color: " + rgb(color));
+                        colorContainer.style.border = '2px solid #000';
+                        colorContainer.addEventListener("mousedown", (function(ix) {
+                            return function() {
+                                setColor(ix);
+                            };
+                        })(i), false);
+
+                        colorContainersBox.appendChild(colorContainer);
+                    }
+
+                    var divClearLeft = document.createElement("div");
+                    divClearLeft.setAttribute("style", "clear: left;");
+                    colorContainersBox.appendChild(divClearLeft);
+
+
+                    var drawTypeContainersBox = document.createElement("div");
+                    drawTypeContainersBox.setAttribute("style",
+                           "float: right; margin-right: 3px; margin-top: 1px;");
+                    optionContainer.appendChild(drawTypeContainersBox);
+
+                    drawTypeContainers = new Array(availableDrawTypes.length);
+                    for (var i = 0; i < drawTypeContainers.length; i++) {
+                        var drawTypeContainer = drawTypeContainers[i] =
+                            document.createElement("div");
+                        drawTypeContainer.setAttribute("style",
+                                "text-align: center; margin: 3px; padding: 0 3px;"
+                                + "height: 18px; float: left;");
+                        drawTypeContainer.style.border = "2px solid #000";
+                        drawTypeContainer.appendChild(document.createTextNode(
+                                String(availableDrawTypes[i].name)));
+                        drawTypeContainer.addEventListener("mousedown", (function(ix) {
+                            return function() {
+                                setDrawType(ix);
+                            };
+                        })(i), false);
+
+                        drawTypeContainersBox.appendChild(drawTypeContainer);
+                    }
+
+
+                    var thicknessContainersBox = document.createElement("div");
+                    thicknessContainersBox.setAttribute("style",
+                            "margin: 3px; border: 1px solid #bbb; border-radius: 3px;");
+                    optionContainer.appendChild(thicknessContainersBox);
+
+                    thicknessContainers = new Array(availableThicknesses.length);
+                    for (var i = 0; i < thicknessContainers.length; i++) {
+                        var thicknessContainer = thicknessContainers[i] =
+                            document.createElement("div");
+                        thicknessContainer.setAttribute("style",
+                                "text-align: center; margin: 3px; width: 18px; "
+                                + "height: 18px; float: left;");
+                        thicknessContainer.style.border = "2px solid #000";
+                        thicknessContainer.appendChild(document.createTextNode(
+                                String(availableThicknesses[i])));
+                        thicknessContainer.addEventListener("mousedown", (function(ix) {
+                            return function() {
+                                setThickness(ix);
+                            };
+                        })(i), false);
+
+                        thicknessContainersBox.appendChild(thicknessContainer);
+                    }
+
+
+                    divClearLeft = document.createElement("div");
+                    divClearLeft.setAttribute("style", "clear: left;");
+                    thicknessContainersBox.appendChild(divClearLeft);
+
+
+                    setColor(0);
+                    setThickness(0);
+                    setDrawType(0);
+
+                }
+
+                function disableControls() {
+                    canvasDisplay.removeEventListener("mousedown", canvasMouseDownHandler);
+                    canvasDisplay.removeEventListener("mousemove", canvasMouseMoveHandler);
+                    mouseInWindow = false;
+                    refreshDisplayCanvas();
+                }
+
+                function pushPath(path) {
+
+                    // Push it into the pathsNotHandled array.
+                    var container = new PathIdContainer(path, nextMsgId++);
+                    pathsNotHandled.push(container);
+
+                    // Send the path to the server.
+                    var message = container.id + "|" + path.type + ","
+                            + path.color[0] + "," + path.color[1] + ","
+                            + path.color[2] + ","
+                            + Math.round(path.color[3] * 255.0) + ","
+                            + path.thickness + "," + path.x1 + ","
+                            + path.y1 + "," + path.x2 + "," + path.y2 + ","
+                            + (path.lastInChain ? "1" : "0");
+
+                    socket.send("1" + message);
+                }
+
+                function setThickness(thicknessIndex) {
+                    if (typeof currentThicknessIndex !== "undefined")
+                        thicknessContainers[currentThicknessIndex]
+                            .style.borderColor = "#000";
+                    currentThicknessIndex = thicknessIndex;
+                    thicknessContainers[currentThicknessIndex]
+                        .style.borderColor = "#d08";
+                }
+
+                function setColor(colorIndex) {
+                    if (typeof currentColorIndex !== "undefined")
+                        colorContainers[currentColorIndex]
+                            .style.borderColor = "#000";
+                    currentColorIndex = colorIndex;
+                    colorContainers[currentColorIndex]
+                        .style.borderColor = "#d08";
+                }
+
+                function setDrawType(drawTypeIndex) {
+                    if (typeof currentDrawTypeIndex !== "undefined")
+                        drawTypeContainers[currentDrawTypeIndex]
+                            .style.borderColor = "#000";
+                    currentDrawTypeIndex = drawTypeIndex;
+                    drawTypeContainers[currentDrawTypeIndex]
+                        .style.borderColor = "#d08";
+                }
+
+
+                connect();
+
+            }
+
+
+            // Initialize the room
+            var room = new Room(document.getElementById("drawContainer"));
+
+
+        }, false);
+
+    })();
+    ]]></script>
+</head>
+<body>
+    <div class="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></div>
+    <div id="labelContainer"/>
+    <div id="drawContainer"/>
+    <div id="console-container"/>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/webapps/examples/websocket/echo.html b/webapps/examples/websocket/echo.xhtml
similarity index 83%
rename from webapps/examples/websocket/echo.html
rename to webapps/examples/websocket/echo.xhtml
index 67d33ab..d3585cc 100644
--- a/webapps/examples/websocket/echo.html
+++ b/webapps/examples/websocket/echo.xhtml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
@@ -14,11 +15,10 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<!DOCTYPE html>
-<html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <head>
     <title>Apache Tomcat WebSocket Examples: Echo</title>
-    <style type="text/css">
+    <style type="text/css"><![CDATA[
         #connect-container {
             float: left;
             width: 400px
@@ -48,8 +48,8 @@
             padding: 0;
             margin: 0;
         }
-    </style>
-    <script type="text/javascript">
+    ]]></style>
+    <script type="application/javascript"><![CDATA[
         var ws = null;
 
         function setConnected(connected) {
@@ -122,21 +122,30 @@
             }
             console.scrollTop = console.scrollHeight;
         }
-    </script>
+
+
+        document.addEventListener("DOMContentLoaded", function() {
+            // Remove elements with "noscript" class - <noscript> is not allowed in XHTML
+            var noscripts = document.getElementsByClassName("noscript");
+            for (var i = 0; i < noscripts.length; i++) {
+                noscripts[i].parentNode.removeChild(noscripts[i]);
+            }
+        }, false);
+    ]]></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 class="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></div>
 <div>
     <div id="connect-container">
         <div>
             <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>
+                   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>
+                   onclick="updateTarget(this.value);"/> <label for="radio2">annotation API</label>
         </div>
         <div>
             <input id="target" type="text" size="40" style="width: 350px"/>
@@ -153,7 +162,7 @@
         </div>
     </div>
     <div id="console-container">
-        <div id="console"></div>
+        <div id="console"/>
     </div>
 </div>
 </body>
diff --git a/webapps/examples/websocket/index.html b/webapps/examples/websocket/index.xhtml
similarity index 69%
rename from webapps/examples/websocket/index.html
rename to webapps/examples/websocket/index.xhtml
index e90dc65..97ee945 100644
--- a/webapps/examples/websocket/index.html
+++ b/webapps/examples/websocket/index.xhtml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
@@ -14,18 +15,18 @@
   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>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <head>
-    <meta http-equiv=Content-Type content="text/html">
     <title>Apache Tomcat WebSocket Examples</title>
 </head>
 <body>
-<h3>Apache Tomcat WebSocket Examples</h3>
+<h1>Apache Tomcat WebSocket Examples</h1>
 <ul>
-    <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>
+    <li><a href="echo.xhtml">Echo example</a></li>
+    <li><a href="chat.xhtml">Chat example</a></li>
+    <li><a href="snake.xhtml">Multiplayer snake example</a></li>
+    <li><a href="drawboard.xhtml">Multiplayer drawboard example</a></li>
+
 </ul>
 </body>
 </html>
\ No newline at end of file
diff --git a/webapps/examples/websocket/snake.html b/webapps/examples/websocket/snake.xhtml
similarity index 90%
rename from webapps/examples/websocket/snake.html
rename to webapps/examples/websocket/snake.xhtml
index 1363f38..fdc3e97 100644
--- a/webapps/examples/websocket/snake.html
+++ b/webapps/examples/websocket/snake.xhtml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
@@ -14,13 +15,10 @@
   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">
+<html xmlns="http://www.w3.org/1999/xhtml" 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">
+    <style type="text/css"><![CDATA[
         #playground {
             width: 640px;
             height: 480px;
@@ -48,18 +46,18 @@
             padding: 0;
             margin: 0;
         }
-    </style>
+    ]]></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 class="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></div>
     <div style="float: left">
-        <canvas id="playground" width="640" height="480"></canvas>
+        <canvas id="playground" width="640" height="480"/>
     </div>
     <div id="console-container">
-        <div id="console"></div>
+        <div id="console"/>
     </div>
-    <script type="text/javascript">
+    <script type="application/javascript"><![CDATA[
 
         var Game = {};
 
@@ -253,6 +251,16 @@
         });
 
         Game.initialize();
-    </script>
+
+
+        document.addEventListener("DOMContentLoaded", function() {
+            // Remove elements with "noscript" class - <noscript> is not allowed in XHTML
+            var noscripts = document.getElementsByClassName("noscript");
+            for (var i = 0; i < noscripts.length; i++) {
+                noscripts[i].parentNode.removeChild(noscripts[i]);
+            }
+        }, false);
+
+        ]]></script>
 </body>
-</html>
+</html>
\ No newline at end of file

-- 
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