Merge branch 'master' into release-9
diff --git a/VERSION.txt b/VERSION.txt
index 11204d4..4da151f 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1,3 +1,6 @@
+jetty-9.0.2-SNAPSHOT
+
+
 jetty-9.0.1.v20130408 - 08 April 2013
  + 384552 add comment to jetty-https.xml describing keymanager password
  + 385488 non existing resources in collection are just warnings
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java
index c8b5f94..92f5ddb 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java
@@ -67,6 +67,7 @@
             new SslConnectionFactory(sslContextFactory,"http/1.1"),
             new HttpConnectionFactory(https_config));
         https.setPort(8443);
+        https.setIdleTimeout(500000);
 
         // Set the connectors
         server.setConnectors(new Connector[] { http, https });
diff --git a/examples/embedded/src/main/resources/jetty-logging.properties b/examples/embedded/src/main/resources/jetty-logging.properties
index f912160..73456ad 100644
--- a/examples/embedded/src/main/resources/jetty-logging.properties
+++ b/examples/embedded/src/main/resources/jetty-logging.properties
@@ -5,5 +5,6 @@
 #org.eclipse.jetty.STACKS=false
 #org.eclipse.jetty.spdy.LEVEL=DEBUG
 #org.eclipse.jetty.server.LEVEL=DEBUG
-#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
+org.eclipse.jetty.io.LEVEL=DEBUG
+org.eclipse.jetty.io.ssl.LEVEL=DEBUG
 #org.eclipse.jetty.spdy.server.LEVEL=DEBUG
diff --git a/examples/embedded/src/main/resources/jetty-otherserver.xml b/examples/embedded/src/main/resources/jetty-otherserver.xml
new file mode 100644
index 0000000..04e3358
--- /dev/null
+++ b/examples/embedded/src/main/resources/jetty-otherserver.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id="OtherServer" class="org.eclipse.jetty.server.Server">
+    <Set name="handler">
+      <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
+        <Set name="handlers">
+         <Array type="org.eclipse.jetty.server.Handler">
+           <Item>
+             <New id="OtherContexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
+           </Item>
+           <Item>
+             <New class="org.eclipse.jetty.server.handler.DefaultHandler"/>
+           </Item>
+         </Array>
+        </Set>
+      </New>
+    </Set>
+
+  <Call name="addConnector">
+    <Arg>
+      <New class="org.eclipse.jetty.server.ServerConnector">
+        <Arg name="server"><Ref refid="OtherServer" /></Arg>
+        <Set name="port">8888</Set>
+      </New>
+    </Arg>
+  </Call>
+
+  <Call name="addBean">
+    <Arg>
+      <New id="DeploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
+        <Set name="contexts">
+          <Ref refid="OtherContexts" />
+        </Set>
+
+        <Call id="webappprovider" name="addAppProvider">
+          <Arg>
+            <New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
+              <Set name="monitoredDirName"><Property name="jetty.home" default="." />/other-webapps</Set>
+              <Set name="defaultsDescriptor"><Property name="jetty.home" default="." />/etc/webdefault.xml</Set>
+              <Set name="configurationManager">
+                <New class="org.eclipse.jetty.deploy.PropertiesConfigurationManager"/>
+              </Set>
+            </New>
+          </Arg>
+        </Call>
+      </New>
+    </Arg>
+  </Call>
+</Configure>
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java
index f8c4e2b..f76eadf 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java
@@ -167,7 +167,7 @@
             //if not, add it
             for (WebInitParam ip:annotation.initParams())
             {
-                if (metaData.getOrigin(servletName+".servlet.init-param"+ip.name())==Origin.NotSet)
+                if (metaData.getOrigin(servletName+".servlet.init-param."+ip.name())==Origin.NotSet)
                 {
                     holder.setInitParameter(ip.name(), ip.value());
                     metaData.setOrigin(servletName+".servlet.init-param."+ip.name());
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java
index 644326a..6ac55e6 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java
@@ -130,28 +130,23 @@
     }
 
     @Override
-    public void onFailure(Response response, Throwable failure)
-    {
-        LOG.debug("Queuing failure {} {}", FAILURE, failure);
-        queue.offer(FAILURE);
-        responseLatch.countDown();
-        resultLatch.countDown();
-        this.failure = failure;
-        signal();
-    }
-
-    @Override
-    public void onSuccess(Response response)
-    {
-        LOG.debug("Queuing end of content {}{}", EOF, "");
-        queue.offer(EOF);
-    }
-
-    @Override
     public void onComplete(Result result)
     {
         this.result = result;
+        if (result.isSucceeded())
+        {
+            LOG.debug("Queuing end of content {}{}", EOF, "");
+            queue.offer(EOF);
+        }
+        else
+        {
+            LOG.debug("Queuing failure {} {}", FAILURE, failure);
+            queue.offer(FAILURE);
+            this.failure = result.getFailure();
+            responseLatch.countDown();
+        }
         resultLatch.countDown();
+        signal();
     }
 
     protected boolean await()
@@ -176,7 +171,7 @@
     {
         synchronized (this)
         {
-            notify();
+            notifyAll();
         }
     }
 
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java
index 218f0e0..5a800af 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java
@@ -445,6 +445,20 @@
         Assert.assertNull(failure.get());
     }
 
+    @Test
+    public void testInputStreamResponseListenerFailedBeforeResponse() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        InputStreamResponseListener listener = new InputStreamResponseListener();
+        // Connect to the wrong port
+        client.newRequest("localhost", 0)
+                .scheme(scheme)
+                .send(listener);
+        Result result = listener.await(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(result);
+    }
+
     @Test(expected = ExecutionException.class)
     public void testInputStreamContentProviderThrowingWhileReading() throws Exception
     {
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java
index 67c9931..c7a685c 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java
@@ -357,12 +357,7 @@
         proxy.flushToClient(record);
 
         record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
-        proxy.flushToServer(record);
-
-        record = proxy.readFromClient();
         Assert.assertNull(record);
-        proxy.flushToServer(record);
 
         server.close();
     }
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
index 1faca02..e0130dd 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
@@ -72,7 +72,6 @@
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class SslBytesServerTest extends SslBytesTest
@@ -87,6 +86,7 @@
     private SslContextFactory sslContextFactory;
     private SSLContext sslContext;
     private SimpleProxy proxy;
+    private Runnable idleHook;
 
     @Before
     public void init() throws Exception
@@ -119,6 +119,15 @@
                             }
                         };
                     }
+
+                    @Override
+                    protected boolean onReadTimeout()
+                    {
+                        final Runnable idleHook = SslBytesServerTest.this.idleHook;
+                        if (idleHook != null)
+                            idleHook.run();
+                        return super.onReadTimeout();
+                    }
                 }, connector, endPoint);
             }
         };
@@ -165,7 +174,6 @@
             }
         };
         connector.setIdleTimeout(idleTimeout);
-//        connector.setPort(5870);
         connector.setPort(0);
 
         server.addConnector(connector);
@@ -209,7 +217,7 @@
 
         proxy = new SimpleProxy(threadPool, "localhost", serverPort);
         proxy.start();
-        logger.debug(":{} <==> :{}", proxy.getPort(), serverPort);
+        logger.info("proxy:{} <==> server:{}", proxy.getPort(), serverPort);
     }
 
     @After
@@ -377,13 +385,9 @@
         Assert.assertNull(String.valueOf(record), record);
         proxy.flushToServer(record);
 
-        // Close Alert
-        record = proxy.readFromServer();
-        proxy.flushToClient(record);
         // Socket close
         record = proxy.readFromServer();
         Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToClient(record);
     }
 
     @Test
@@ -669,13 +673,9 @@
         Assert.assertNull(String.valueOf(record), record);
         proxy.flushToServer(record);
 
-        // Close Alert
-        record = proxy.readFromServer();
-        proxy.flushToClient(record);
         // Socket close
         record = proxy.readFromServer();
         Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToClient(record);
     }
 
     @Test
@@ -728,22 +728,15 @@
         Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
         proxy.flushToClient(record);
 
-        // Close Alert
+        // Socket close
         record = proxy.readFromServer();
-        Assert.assertNotNull(record);
-        Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
-        // We can't forward to the client, its socket is already closed
+        Assert.assertNull(record);
 
         // Check that we did not spin
         TimeUnit.MILLISECONDS.sleep(500);
         Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
         Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
         Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
-
-        // Socket close
-        record = proxy.readFromServer();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToClient(record);
     }
 
     @Test
@@ -803,11 +796,9 @@
         Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
         proxy.flushToClient(record);
 
-        // Close Alert
+        // Socket close
         record = proxy.readFromServer();
-        Assert.assertNotNull(record);
-        Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
-        // We can't forward to the client, its socket is already closed
+        Assert.assertNull(String.valueOf(record), record);
 
         // Check that we did not spin
         TimeUnit.MILLISECONDS.sleep(500);
@@ -819,11 +810,6 @@
         record = proxy.readFromClient();
         Assert.assertNull(String.valueOf(record), record);
         proxy.flushToServer(record);
-
-        // Socket close
-        record = proxy.readFromServer();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToClient(record);
     }
 
     @Test
@@ -864,12 +850,9 @@
         // Close the raw socket, this generates a truncation attack
         proxy.flushToServer(null);
 
-        // Expect alert + raw close from server
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
+        // Expect raw close from server
         record = proxy.readFromServer();
         Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToClient(record);
 
         // Check that we did not spin
         TimeUnit.MILLISECONDS.sleep(500);
@@ -917,12 +900,9 @@
         Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
         proxy.flushToClient(record);
 
-        // Expect alert + raw close from server
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
+        // Expect raw close from server
         record = proxy.readFromServer();
         Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToClient(record);
 
         // Check that we did not spin
         TimeUnit.MILLISECONDS.sleep(500);
@@ -1099,6 +1079,7 @@
         System.arraycopy(closeBytes, 0, bytes, dataBytes.length, closeBytes.length / 2);
         proxy.flushToServer(100, bytes);
 
+        // Send the other half of the close alert bytes
         bytes = new byte[closeBytes.length - closeBytes.length / 2];
         System.arraycopy(closeBytes, closeBytes.length / 2, bytes, 0, bytes.length);
         proxy.flushToServer(100, bytes);
@@ -1113,27 +1094,15 @@
         Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
         proxy.flushToClient(record);
 
-        // Close Alert
+        // Socket close
         record = proxy.readFromServer();
-        Assert.assertNotNull(record);
-        Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
-        // We can't forward to the client, its socket is already closed
+        Assert.assertNull(record);
 
         // Check that we did not spin
         TimeUnit.MILLISECONDS.sleep(500);
         Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
         Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
         Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
-
-        // Socket close
-        record = proxy.readFromClient();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToServer(record);
-
-        // Socket close
-        record = proxy.readFromServer();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToClient(record);
     }
 
     @Test
@@ -1749,13 +1718,37 @@
         client.close();
     }
 
-    @Ignore
     @Test
     public void testRequestConcurrentWithIdleExpiration() throws Exception
     {
         final SSLSocket client = newClient();
+        final OutputStream clientOutput = client.getOutputStream();
         final CountDownLatch latch = new CountDownLatch(1);
 
+        idleHook = new Runnable()
+        {
+            public void run()
+            {
+                if (latch.getCount()==0)
+                    return;
+                try
+                {
+                    // Send request
+                    clientOutput.write(("" +
+                            "GET / HTTP/1.1\r\n" +
+                            "Host: localhost\r\n" +
+                            "\r\n").getBytes("UTF-8"));
+                    clientOutput.flush();
+                    latch.countDown();
+                }
+                catch (Exception x)
+                {
+                    // Latch won't trigger and test will fail
+                    x.printStackTrace();
+                }
+            }
+        };
+
         SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
         client.startHandshake();
         Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
@@ -1779,86 +1772,13 @@
         Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
         Assert.assertThat(httpParses.get(), Matchers.lessThan(50));
 
-        completeClose(client);
+        record = proxy.readFromServer();
+        Assert.assertNull(record);
 
         TimeUnit.MILLISECONDS.sleep(200);
-        //System.err.println(((Dumpable)server.getConnectors()[0]).dump());
         Assert.assertThat(((Dumpable)server.getConnectors()[0]).dump(), Matchers.not(Matchers.containsString("SCEP@")));
-
     }
 
-    /*
-        @Test
-        public void testRequestWriteBlockedWithPipelinedRequest() throws Exception
-        {
-            final SSLSocket client = newClient();
-            final OutputStream clientOutput = client.getOutputStream();
-
-            SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-            client.startHandshake();
-            Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-            byte[] data = new byte[128 * 1024];
-            Arrays.fill(data, (byte)'X');
-            final String content = new String(data, "UTF-8");
-            Future<Object> request = threadPool.submit(new Callable<Object>()
-            {
-                public Object call() throws Exception
-                {
-                    clientOutput.write(("" +
-                            "POST /echo HTTP/1.1\r\n" +
-                            "Host: localhost\r\n" +
-                            "Content-Length: " + content.length() + "\r\n" +
-                            "\r\n" +
-                            content).getBytes("UTF-8"));
-                    clientOutput.flush();
-                    return null;
-                }
-            });
-
-            // Nine TLSRecords will be generated for the request
-            for (int i = 0; i < 9; ++i)
-            {
-                // Application data
-                TLSRecord record = proxy.readFromClient();
-                Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-                proxy.flushToServer(record, 0);
-            }
-            Assert.assertNull(request.get(5, TimeUnit.SECONDS));
-
-            // We do not read the big request to cause a write blocked on the server
-            TimeUnit.MILLISECONDS.sleep(500);
-
-            // Now send the pipelined request
-            Future<Object> pipelined = threadPool.submit(new Callable<Object>()
-            {
-                public Object call() throws Exception
-                {
-                    clientOutput.write(("" +
-                            "GET /pipelined HTTP/1.1\r\n" +
-                            "Host: localhost\r\n" +
-                            "\r\n").getBytes("UTF-8"));
-                    clientOutput.flush();
-                    return null;
-                }
-            });
-
-            TLSRecord record = proxy.readFromClient();
-            Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-            proxy.flushToServer(record, 0);
-            Assert.assertNull(pipelined.get(5, TimeUnit.SECONDS));
-
-            // Check that we did not spin
-            TimeUnit.MILLISECONDS.sleep(500);
-            Assert.assertThat(sslFills.get(), lessThan(20));
-            Assert.assertThat(sslFlushes.get(), lessThan(20));
-            Assert.assertThat(httpParses.get(), lessThan(50));
-
-            Thread.sleep(5000);
-
-    //        closeClient(client);
-        }
-    */
     private void assumeJavaVersionSupportsTLSRenegotiations()
     {
         // Due to a security bug, TLS renegotiations were disabled in JDK 1.6.0_19-21
@@ -1893,31 +1813,8 @@
         Assert.assertNull(String.valueOf(record), record);
         proxy.flushToServer(record);
 
-        // Close Alert
-        record = proxy.readFromServer();
-        proxy.flushToClient(record);
-
         // Socket close
         record = proxy.readFromServer();
         Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToClient(record);
-    }
-
-    private void completeClose(SSLSocket client) throws Exception
-    {
-        client.close();
-
-        // Close Alert
-        TLSRecord record = proxy.readFromClient();
-        proxy.flushToServer(record);
-        // Socket close
-        record = proxy.readFromClient();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToServer(record);
-
-        // Close Alert
-        record = proxy.readFromServer();
-        proxy.flushToClient(record);
-
     }
 }
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java
index c7fe744..b0f5769 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java
@@ -34,12 +34,17 @@
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 
+import org.eclipse.jetty.toolchain.test.TestTracker;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.junit.Assert;
+import org.junit.Rule;
 
 public abstract class SslBytesTest
 {
+    @Rule
+    public TestTracker tracker = new TestTracker();
+
     protected final Logger logger = Log.getLogger(getClass());
 
     public static class TLSRecord
@@ -115,8 +120,7 @@
 
         public void start() throws Exception
         {
-            serverSocket = new ServerSocket(47009);
-//            serverSocket = new ServerSocket(0);
+            serverSocket = new ServerSocket(0);
             Thread acceptor = new Thread(this);
             acceptor.start();
             server = new Socket(serverHost, serverPort);
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
index 4fca18f..19b6548 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
@@ -190,6 +190,10 @@
         if (DEBUG)
             LOG.debug("onFillable enter {}", getEndPoint());
 
+        // We have received a close handshake, close the end point to send FIN.
+        if (_decryptedEndPoint.isInputShutdown())
+            getEndPoint().close();
+            
         // wake up whoever is doing the fill or the flush so they can
         // do all the filling, unwrapping, wrapping and flushing
         _decryptedEndPoint.getFillInterest().fillable();
@@ -612,8 +616,11 @@
                                         // maybe we will fill some more on a retry
                                         continue;
                                     }
-                                    // we need to wait for more net data
-                                    return 0;
+                                    else
+                                    {
+                                        // we need to wait for more net data
+                                        return 0;
+                                    }
 
                                 case FINISHED:
                                     throw new IllegalStateException();
@@ -726,6 +733,7 @@
                             {
                                 _cannotAcceptMoreAppDataToFlush = true;
                                 getEndPoint().flush(_encryptedOutput);
+                                getEndPoint().shutdownOutput();
                                 // If we failed to flush the close handshake then we will just pretend that
                                 // the write has progressed normally and let a subsequent call to flush
                                 // (or WriteFlusher#onIncompleteFlushed) to finish writing the close handshake.
@@ -733,8 +741,11 @@
                                 if (BufferUtil.hasContent(_encryptedOutput))
                                     return false;
                             }
-
                             // otherwise we have written, and the caller will close the underlying connection
+                            else
+                            {
+                                getEndPoint().shutdownOutput();
+                            }
                             return allConsumed;
 
                         case BUFFER_UNDERFLOW:
@@ -823,24 +834,37 @@
             {
                 _bufferPool.release(_encryptedOutput);
                 _encryptedOutput = null;
-                if (_sslEngine.isOutboundDone())
-                    getEndPoint().shutdownOutput();
             }
         }
 
         @Override
         public void shutdownOutput()
         {
-            _sslEngine.closeOutbound();
-            try
+            boolean ishut = isInputShutdown();
+            if (DEBUG)
+                LOG.debug("{} shutdownOutput: oshut={}, ishut={}", SslConnection.this, isOutputShutdown(), ishut);
+            if (ishut)
             {
-                flush(BufferUtil.EMPTY_BUFFER);
-            }
-            catch (IOException e)
-            {
-                LOG.ignore(e);
+                // Aggressively close, since inbound close alert has already been processed
+                // and the TLS specification allows to close the connection directly, which
+                // is what most other implementations expect: a FIN rather than a TLS close
+                // reply. If a TLS close reply is sent, most implementation send a RST.
                 getEndPoint().close();
             }
+            else
+            {
+                try
+                {
+                    _sslEngine.closeOutbound();
+                    flush(BufferUtil.EMPTY_BUFFER); // Send close handshake
+                    SslConnection.this.fillInterested(); // seek reply FIN or RST or close handshake
+                }
+                catch (Exception e)
+                {
+                    LOG.ignore(e);
+                    getEndPoint().close();
+                }
+            }
         }
 
         @Override
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java
index 77aea8b..7bd0596 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java
@@ -202,16 +202,21 @@
 
         filled=client.read(sslIn);
         if (debug) System.err.println("in="+filled);
-        sslIn.flip();
-        try
+        
+        if (filled>=0)
         {
-            // Since the client closed abruptly, the server is sending a close alert with a failure
-            engine.unwrap(sslIn, appIn);
-            Assert.fail();
-        }
-        catch (SSLException x)
-        {
-            // Expected
+            // this is the old behaviour. 
+            sslIn.flip();
+            try
+            {
+                // Since the client closed abruptly, the server is sending a close alert with a failure
+                engine.unwrap(sslIn, appIn);
+                Assert.fail();
+            }
+            catch (SSLException x)
+            {
+                // Expected
+            }
         }
 
         sslIn.clear();
diff --git a/jetty-npn/pom.xml b/jetty-npn/pom.xml
deleted file mode 100644
index 28d3ad1..0000000
--- a/jetty-npn/pom.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <groupId>org.eclipse.jetty</groupId>
-        <artifactId>jetty-project</artifactId>
-        <version>9.0.0-SNAPSHOT</version>
-    </parent>
-
-    <modelVersion>4.0.0</modelVersion>
-    <groupId>org.eclipse.jetty.npn</groupId>
-    <artifactId>npn-api</artifactId>
-    <version>1.1.1-SNAPSHOT</version>
-    <name>Jetty :: Next Protocol Negotiation :: API</name>
-
-    <properties>
-      <!-- for now we do make it an OSGi bundle...
-      but it needs to be in the bootstrap classes at runtime. -->
-        <bundle-symbolic-name>org.eclipse.jetty.npn</bundle-symbolic-name>
-    </properties>
-
-    <scm>
-      <connection>scm:git:http://git.eclipse.org/gitroot/jetty/org.eclipse.jetty.project.git</connection>
-      <developerConnection>scm:git:ssh://git.eclipse.org/gitroot/jetty/org.eclipse.jetty.project.git</developerConnection>
-       <url>http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/jetty-npn</url>
-    </scm>
-
-    <build>
-      <plugins>
-        <plugin>
-          <groupId>org.apache.felix</groupId>
-          <artifactId>maven-bundle-plugin</artifactId>
-          <extensions>true</extensions>
-          <executions>
-            <execution>
-              <goals>
-                <goal>manifest</goal>
-              </goals>
-              <configuration>
-                <instructions>
-                  <Export-Package>org.eclipse.jetty.npn.*;version="9.0"</Export-Package>
-                  <Import-Package>*</Import-Package>
-                  <Bundle-Description>Next Protocol Negotiation API. must be in the bootstrap packages at runtime.</Bundle-Description>
-                </instructions>
-              </configuration>
-             </execution>
-          </executions>
-        </plugin>
-      <plugin>
-        <!--
-        Required for OSGI
-        -->
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <configuration>
-          <archive>
-            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
-          </archive>
-        </configuration>
-      </plugin>
-      <plugin>
-          <groupId>org.apache.maven.plugins</groupId>
-          <artifactId>maven-release-plugin</artifactId>
-          <version>2.2.1</version>
-          <configuration>
-            <useReleaseProfile>false</useReleaseProfile>
-            <goals>deploy</goals>
-            <arguments>-Peclipse-release</arguments>
-            <preparationGoals>clean install</preparationGoals>
-          </configuration>
-        </plugin>
-      </plugins>
-    </build>
-</project>
diff --git a/jetty-npn/src/main/java/org/eclipse/jetty/npn/NextProtoNego.java b/jetty-npn/src/main/java/org/eclipse/jetty/npn/NextProtoNego.java
deleted file mode 100644
index 6dbea16..0000000
--- a/jetty-npn/src/main/java/org/eclipse/jetty/npn/NextProtoNego.java
+++ /dev/null
@@ -1,248 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.npn;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.WeakHashMap;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLSocket;
-
-/**
- * <p>{@link NextProtoNego} provides an API to applications that want to make use of the
- * <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next Protocol Negotiation</a>.</p>
- * <p>The NPN extension is only available when using the TLS protocol, therefore applications must
- * ensure that the TLS protocol is used:</p>
- * <pre>
- * SSLContext context = SSLContext.getInstance("TLSv1");
- * </pre>
- * <p>Refer to the
- * <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext">list
- * of standard SSLContext protocol names</a> for further information on TLS protocol versions supported.</p>
- * <p>Applications must register instances of either {@link SSLSocket} or {@link SSLEngine} with a
- * {@link ClientProvider} or with a {@link ServerProvider}, depending whether they are on client or
- * server side.</p>
- * <p>The NPN implementation will invoke the provider callbacks to allow applications to interact
- * with the negotiation of the next protocol.</p>
- * <p>Client side typical usage:</p>
- * <pre>
- * SSLSocket sslSocket = ...;
- * NextProtoNego.put(sslSocket, new NextProtoNego.ClientProvider()
- * {
- *     &#64;Override
- *     public boolean supports()
- *     {
- *         return true;
- *     }
- *
- *     &#64;Override
- *     public void unsupported()
- *     {
- *     }
- *
- *     &#64;Override
- *     public String selectProtocol(List&lt;String&gt; protocols)
- *     {
- *         return protocols.get(0);
- *     }
- *  });
- * </pre>
- * <p>Server side typical usage:</p>
- * <pre>
- * SSLSocket sslSocket = ...;
- * NextProtoNego.put(sslSocket, new NextProtoNego.ServerProvider()
- * {
- *     &#64;Override
- *     public void unsupported()
- *     {
- *     }
- *
- *     &#64;Override
- *     public List<String> protocols()
- *     {
- *         return Arrays.asList("http/1.1");
- *     }
- *
- *     &#64;Override
- *     public void protocolSelected(String protocol)
- *     {
- *         System.out.println("Protocol Selected is: " + protocol);
- *     }
- *  });
- * </pre>
- * <p>There is no need to unregister {@link SSLSocket} or {@link SSLEngine} instances, as they
- * are kept in a {@link WeakHashMap} and will be garbage collected when the application does not
- * hard reference them anymore. However, methods to explicitly unregister {@link SSLSocket} or
- * {@link SSLEngine} instances are provided.</p>
- * <p>In order to help application development, you can set the {@link NextProtoNego#debug} field
- * to {@code true} to have debug code printed to {@link System#err}.</p>
- */
-public class NextProtoNego
-{
-    /**
-     * <p>Enables debug logging on {@link System#err}.</p>
-     */
-    public static boolean debug = false;
-
-    private static Map<Object, Provider> objects = Collections.synchronizedMap(new WeakHashMap<Object, Provider>());
-
-    private NextProtoNego()
-    {
-    }
-
-    /**
-     * <p>Registers a SSLSocket with a provider.</p>
-     *
-     * @param socket the socket to register with the provider
-     * @param provider the provider to register with the socket
-     * @see #remove(SSLSocket)
-     */
-    public static void put(SSLSocket socket, Provider provider)
-    {
-        objects.put(socket, provider);
-    }
-
-    /**
-     * @param socket a socket registered with {@link #put(SSLSocket, Provider)}
-     * @return the provider registered with the given socket
-     */
-    public static Provider get(SSLSocket socket)
-    {
-        return objects.get(socket);
-    }
-
-    /**
-     * <p>Unregisters the given SSLSocket.</p>
-     *
-     * @param socket the socket to unregister
-     * @return the provider registered with the socket
-     * @see #put(SSLSocket, Provider)
-     */
-    public static Provider remove(SSLSocket socket)
-    {
-        return objects.remove(socket);
-    }
-
-    /**
-     * <p>Registers a SSLEngine with a provider.</p>
-     *
-     * @param engine the engine to register with the provider
-     * @param provider the provider to register with the engine
-     * @see #remove(SSLEngine)
-     */
-    public static void put(SSLEngine engine, Provider provider)
-    {
-        objects.put(engine, provider);
-    }
-
-    /**
-     *
-     * @param engine an engine registered with {@link #put(SSLEngine, Provider)}
-     * @return the provider registered with the given engine
-     */
-    public static Provider get(SSLEngine engine)
-    {
-        return objects.get(engine);
-    }
-
-    /**
-     * <p>Unregisters the given SSLEngine.</p>
-     *
-     * @param engine the engine to unregister
-     * @return the provider registered with the engine
-     * @see #put(SSLEngine, Provider)
-     */
-    public static Provider remove(SSLEngine engine)
-    {
-        return objects.remove(engine);
-    }
-
-    /**
-     * <p>Base, empty, interface for providers.</p>
-     */
-    public interface Provider
-    {
-    }
-
-    /**
-     * <p>The client-side provider interface that applications must implement to interact
-     * with the negotiation of the next protocol.</p>
-     */
-    public interface ClientProvider extends Provider
-    {
-        /**
-         * <p>Callback invoked to let the implementation know whether an
-         * empty NPN extension should be added to a ClientHello SSL message.</p>
-         *
-         * @return true to add the NPN extension, false otherwise
-         */
-        public boolean supports();
-
-        /**
-         * <p>Callback invoked to let the application know that the server does
-         * not support NPN.</p>
-         */
-        public void unsupported();
-
-        /**
-         * <p>Callback invoked to let the application select a protocol
-         * among the ones sent by the server.</p>
-         *
-         * @param protocols the protocols sent by the server
-         * @return the protocol selected by the application, or null if the
-         * NextProtocol SSL message should not be sent to the server
-         */
-        public String selectProtocol(List<String> protocols);
-    }
-
-    /**
-     * <p>The server-side provider interface that applications must implement to interact
-     * with the negotiation of the next protocol.</p>
-     */
-    public interface ServerProvider extends Provider
-    {
-        /**
-         * <p>Callback invoked to let the application know that the client does not
-         * support NPN.</p>
-         */
-        public void unsupported();
-
-        /**
-         * <p>Callback invoked to let the implementation know the list
-         * of protocols that should be added to an NPN extension in a
-         * ServerHello SSL message.</p>
-         * <p>This callback is invoked only if the client sent a NPN extension.</p>
-         *
-         * @return the list of protocols, or null if no NPN extension
-         * should be sent to the client
-         */
-        public List<String> protocols();
-
-        /**
-         * <p>Callback invoked to let the application know the protocol selected
-         * by the client.</p>
-         * <p>This callback is invoked only if the client sent a NextProtocol SSL message.</p>
-         *
-         * @param protocol the selected protocol
-         */
-        public void protocolSelected(String protocol);
-    }
-}
diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/ContainerTldBundleDiscoverer.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/ContainerTldBundleDiscoverer.java
new file mode 100644
index 0000000..f9c212a
--- /dev/null
+++ b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/ContainerTldBundleDiscoverer.java
@@ -0,0 +1,153 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.boot.jasper;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.deploy.DeploymentManager;
+import org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration;
+import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
+import org.eclipse.jetty.osgi.boot.utils.TldBundleDiscoverer;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+
+
+
+/**
+ * ContainerTldBundleDiscoverer
+ * 
+ * 
+ * Use a System property to define bundles that contain tlds that need to
+ * be treated by jasper as if they were on the jetty container's classpath.
+ * 
+ * The value of the property is evaluated against the DeploymentManager 
+ * context attribute "org.eclipse.jetty.server.webapp.containerIncludeBundlePattern", 
+ * which defines a pattern of matching bundle names.
+ * 
+ * The bundle locations are converted to URLs for jasper's use.
+ * 
+ * Eg:
+ * -Dorg.eclipse.jetty.osgi.tldbundles=org.springframework.web.servlet,com.opensymphony.module.sitemesh
+ * 
+ */
+public class ContainerTldBundleDiscoverer implements TldBundleDiscoverer
+{
+    /**
+     * Comma separated list of names of bundles that contain tld files that should be
+     * discoved by jasper as if they were on the container's classpath.
+     * Eg:
+     * -Djetty.osgi.tldbundles=org.springframework.web.servlet,com.opensymphony.module.sitemesh
+     */
+    public static final String SYS_PROP_TLD_BUNDLES = "org.eclipse.jetty.osgi.tldbundles";
+
+
+
+    /**
+     * Check the System property "org.eclipse.jetty.osgi.tldbundles" for names of
+     * bundles that contain tlds and convert to URLs.
+     * 
+     * @return The location of the jars that contain tld files as URLs.
+     */
+    public URL[] getUrlsForBundlesWithTlds(DeploymentManager deploymentManager, BundleFileLocatorHelper locatorHelper) throws Exception
+    {
+        // naive way of finding those bundles.
+        // lots of assumptions: for example we assume a single version of each
+        // bundle that would contain tld files.
+        // this is probably good enough as those tlds are loaded system-wide on
+        // jetty.
+        // to do better than this we need to do it on a per webapp basis.
+        // probably using custom properties in the ContextHandler service
+        // and mirroring those in the MANIFEST.MF
+
+        Bundle[] bundles = FrameworkUtil.getBundle(ContainerTldBundleDiscoverer.class).getBundleContext().getBundles();
+        HashSet<URL> urls = new HashSet<URL>();
+        String tmp = System.getProperty(SYS_PROP_TLD_BUNDLES); //comma separated exact names
+        List<String> sysNames =   new ArrayList<String>();
+        if (tmp != null)
+        {
+            StringTokenizer tokenizer = new StringTokenizer(tmp, ", \n\r\t", false);
+            while (tokenizer.hasMoreTokens())
+                sysNames.add(tokenizer.nextToken());
+        }
+        tmp = (String) deploymentManager.getContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN); //bundle name patterns
+        Pattern pattern = (tmp==null? null : Pattern.compile(tmp));
+        for (Bundle bundle : bundles)
+        {
+            if (sysNames.contains(bundle.getSymbolicName()))
+                convertBundleLocationToURL(locatorHelper, bundle, urls);
+           
+            if (pattern != null && pattern.matcher(bundle.getSymbolicName()).matches())
+                convertBundleLocationToURL(locatorHelper, bundle, urls);
+        }
+
+        return urls.toArray(new URL[urls.size()]);
+
+    }
+
+    /**
+     * Resolves a bundle that contains tld files as a URL. The URLs are
+     * used by jasper to discover the tld files.
+     * 
+     * Support only 2 types of packaging for the bundle: - the bundle is a jar
+     * (recommended for runtime.) - the bundle is a folder and contain jars in
+     * the root and/or in the lib folder (nice for PDE developement situations)
+     * Unsupported: the bundle is a jar that embeds more jars.
+     * 
+     * @param locatorHelper
+     * @param bundle
+     * @param urls
+     * @throws Exception
+     */
+    private void convertBundleLocationToURL(BundleFileLocatorHelper locatorHelper, Bundle bundle, Set<URL> urls) throws Exception
+    {
+        File jasperLocation = locatorHelper.getBundleInstallLocation(bundle);
+        if (jasperLocation.isDirectory())
+        {
+            for (File f : jasperLocation.listFiles())
+            {
+                if (f.getName().endsWith(".jar") && f.isFile())
+                {
+                    urls.add(f.toURI().toURL());
+                }
+                else if (f.isDirectory() && f.getName().equals("lib"))
+                {
+                    for (File f2 : jasperLocation.listFiles())
+                    {
+                        if (f2.getName().endsWith(".jar") && f2.isFile())
+                        {
+                            urls.add(f2.toURI().toURL());
+                        }
+                    }
+                }
+            }
+            urls.add(jasperLocation.toURI().toURL());
+        }
+        else
+        {
+            urls.add(jasperLocation.toURI().toURL());
+        }
+    }
+}
\ No newline at end of file
diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/WebappRegistrationCustomizerImpl.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/JSTLBundleDiscoverer.java
similarity index 94%
rename from jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/WebappRegistrationCustomizerImpl.java
rename to jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/JSTLBundleDiscoverer.java
index 7b744b9..23628c3 100644
--- a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/WebappRegistrationCustomizerImpl.java
+++ b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/JSTLBundleDiscoverer.java
@@ -34,7 +34,7 @@
 import org.eclipse.jetty.deploy.DeploymentManager;
 import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
 import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
-import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer;
+import org.eclipse.jetty.osgi.boot.utils.TldBundleDiscoverer;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.osgi.framework.Bundle;
@@ -44,17 +44,20 @@
 import org.xml.sax.SAXException;
 
 /**
+ * 
+ * JSTLBundleDiscoverer
+ * 
  * Fix various shortcomings with the way jasper parses the tld files. Plugs the
  * JSTL tlds assuming that they are packaged with the bundle that contains the
  * JSTL classes.
  * <p>
  * Pluggable tlds at the server level are handled by
- * {@link PluggableWebAppRegistrationCustomizerImpl}.
+ * {@link ContainerTldBundleDiscoverer}.
  * </p>
  */
-public class WebappRegistrationCustomizerImpl implements WebappRegistrationCustomizer
+public class JSTLBundleDiscoverer implements TldBundleDiscoverer
 {
-    private static final Logger LOG = Log.getLogger(WebappRegistrationCustomizerImpl.class);
+    private static final Logger LOG = Log.getLogger(JSTLBundleDiscoverer.class);
     
 
     /**
@@ -83,7 +86,7 @@
      */
     private static String DEFAULT_JSP_FACTORY_IMPL_CLASS = "org.apache.jasper.runtime.JspFactoryImpl";
 
-    public WebappRegistrationCustomizerImpl()
+    public JSTLBundleDiscoverer()
     {
         fixupDtdResolution();
 
@@ -136,7 +139,7 @@
      * @return array of URLs
      * @throws Exception
      */
-    public URL[] getJarsWithTlds(DeploymentManager deployer, BundleFileLocatorHelper locatorHelper) throws Exception
+    public URL[] getUrlsForBundlesWithTlds(DeploymentManager deployer, BundleFileLocatorHelper locatorHelper) throws Exception
     {
 
         ArrayList<URL> urls = new ArrayList<URL>();
@@ -148,7 +151,7 @@
         // So we can look for this class using this bundle's classloader:
         try
         {
-            Class<?> jstlClass = WebappRegistrationCustomizerImpl.class.getClassLoader().loadClass(DEFAULT_JSTL_BUNDLE_CLASS);
+            Class<?> jstlClass = JSTLBundleDiscoverer.class.getClassLoader().loadClass(DEFAULT_JSTL_BUNDLE_CLASS);
 
             classesToAddToTheTldBundles.add(jstlClass);
         }
diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/PluggableWebAppRegistrationCustomizerImpl.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/PluggableWebAppRegistrationCustomizerImpl.java
deleted file mode 100644
index 2521ed1..0000000
--- a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/PluggableWebAppRegistrationCustomizerImpl.java
+++ /dev/null
@@ -1,187 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.jasper;
-
-import java.io.File;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.StringTokenizer;
-import java.util.regex.Pattern;
-
-import org.eclipse.jetty.deploy.DeploymentManager;
-import org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration;
-import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
-import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.FrameworkUtil;
-
-/**
- * Plug bundles that contains tld files so that jasper will discover them and
- * set them up in jetty.
- * 
- * For example:
- * -Dorg.eclipse.jetty.osgi.tldbundles=org.springframework.web.servlet
- * ,com.opensymphony.module.sitemesh Otherwise use an attribute to the
- * WebAppDeployer &lt;New
- * class="org.eclipse.jetty.deploy.providers.WebAppProvider"&gt; .... &lt;Set
- * name="tldBundles"&gt;&ltProperty name="org.eclipse.jetty.osgi.tldsbundles"
- * default="" /&gt;&lt;/Set&gt; &lt;New&gt;
- */
-public class PluggableWebAppRegistrationCustomizerImpl implements WebappRegistrationCustomizer
-{
-    /**
-     * To plug into jasper bundles that contain tld files please use a list of
-     * bundle's symbolic names:
-     * -Djetty.osgi.tldbundles=org.springframework.web.servlet
-     * ,com.opensymphony.module.sitemesh
-     */
-    public static final String SYS_PROP_TLD_BUNDLES = "org.eclipse.jetty.osgi.tldbundles";
-
-    /**
-     * Union of the tld bundles defined system wide and the one defines as an
-     * attribute of the AppProvider.
-     * 
-     * @param provider
-     * @return
-     */
-    private static Collection<String> getTldBundles(DeploymentManager deploymentManager)
-    {
-        String sysprop = System.getProperty(SYS_PROP_TLD_BUNDLES);
-        String att = (String) deploymentManager.getContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN);
-        if (sysprop == null && att == null) { return Collections.emptySet(); }
-        if (att == null)
-        {
-            att = sysprop;
-        }
-        else if (sysprop != null)
-        {
-            att = att + "," + sysprop;
-        }
-
-        Collection<String> tldbundles = new HashSet<String>();
-        StringTokenizer tokenizer = new StringTokenizer(att, ", \n\r\t", false);
-        while (tokenizer.hasMoreTokens())
-        {
-            tldbundles.add(tokenizer.nextToken());
-        }
-        return tldbundles;
-    }
-
-    /**
-     * @return The location of the jars that contain tld files. Jasper will
-     *         discover them.
-     */
-    public URL[] getJarsWithTlds(DeploymentManager deploymentManager, BundleFileLocatorHelper locatorHelper) throws Exception
-    {
-        // naive way of finding those bundles.
-        // lots of assumptions: for example we assume a single version of each
-        // bundle that would contain tld files.
-        // this is probably good enough as those tlds are loaded system-wide on
-        // jetty.
-        // to do better than this we need to do it on a per webapp basis.
-        // probably using custom properties in the ContextHandler service
-        // and mirroring those in the MANIFEST.MF
-
-        Bundle[] bundles = FrameworkUtil.getBundle(PluggableWebAppRegistrationCustomizerImpl.class).getBundleContext().getBundles();
-        HashSet<URL> urls = new HashSet<URL>();
-        String tmp = System.getProperty(SYS_PROP_TLD_BUNDLES); //comma separated exact names
-        List<String> sysNames =   new ArrayList<String>();
-        if (tmp != null)
-        {
-            StringTokenizer tokenizer = new StringTokenizer(tmp, ", \n\r\t", false);
-            while (tokenizer.hasMoreTokens())
-                sysNames.add(tokenizer.nextToken());
-        }
-        tmp = (String) deploymentManager.getContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN); //bundle name patterns
-        Pattern pattern = (tmp==null? null : Pattern.compile(tmp));
-        for (Bundle bundle : bundles)
-        {
-            if (sysNames.contains(bundle.getSymbolicName()))
-                registerTldBundle(locatorHelper, bundle, urls);
-           
-            if (pattern != null && pattern.matcher(bundle.getSymbolicName()).matches())
-                registerTldBundle(locatorHelper, bundle, urls);
-        }
-
-        return urls.toArray(new URL[urls.size()]);
-
-    }
-
-    /**
-     * Resolves the bundle that contains tld files as a set of URLs that will be
-     * passed to jasper as a URLClassLoader later on. Usually that would be a
-     * single URL per bundle. But we do some more work if there are jars
-     * embedded in the bundle.
-     * 
-     * The jasper TldScanner expects a URLClassloader to parse a jar for the
-     * /META-INF/*.tld it may contain. We place the bundles that we know contain
-     * such tag-libraries. Please note that it will work if and only if the
-     * bundle is a jar (!) Currently we just hardcode the bundle that contains
-     * the jstl implemenation.
-     * 
-     * A workaround when the tld cannot be parsed with this method is to copy
-     * and paste it inside the WEB-INF of the webapplication where it is used.
-     * 
-     * Support only 2 types of packaging for the bundle: - the bundle is a jar
-     * (recommended for runtime.) - the bundle is a folder and contain jars in
-     * the root and/or in the lib folder (nice for PDE developement situations)
-     * Unsupported: the bundle is a jar that embeds more jars.
-     * 
-     * @param locatorHelper
-     * @param bundle
-     * @param urls
-     * @throws Exception
-     */
-    private void registerTldBundle(BundleFileLocatorHelper locatorHelper, Bundle bundle, Set<URL> urls) throws Exception
-    {
-        File jasperLocation = locatorHelper.getBundleInstallLocation(bundle);
-        if (jasperLocation.isDirectory())
-        {
-            for (File f : jasperLocation.listFiles())
-            {
-                if (f.getName().endsWith(".jar") && f.isFile())
-                {
-                    urls.add(f.toURI().toURL());
-                }
-                else if (f.isDirectory() && f.getName().equals("lib"))
-                {
-                    for (File f2 : jasperLocation.listFiles())
-                    {
-                        if (f2.getName().endsWith(".jar") && f2.isFile())
-                        {
-                            urls.add(f2.toURI().toURL());
-                        }
-                    }
-                }
-            }
-            urls.add(jasperLocation.toURI().toURL());
-        }
-        else
-        {
-            urls.add(jasperLocation.toURI().toURL());
-        }
-
-    }
-
-}
\ No newline at end of file
diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java
index 9741d21..b172702 100644
--- a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java
+++ b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java
@@ -19,21 +19,26 @@
 package org.eclipse.jetty.osgi.boot.jsp;
 
 import org.eclipse.jetty.osgi.boot.BundleWebAppProvider;
-import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleTrackerCustomizer;
-import org.eclipse.jetty.osgi.boot.jasper.PluggableWebAppRegistrationCustomizerImpl;
-import org.eclipse.jetty.osgi.boot.jasper.WebappRegistrationCustomizerImpl;
+import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
+import org.eclipse.jetty.osgi.boot.jasper.ContainerTldBundleDiscoverer;
+import org.eclipse.jetty.osgi.boot.jasper.JSTLBundleDiscoverer;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 
 /**
- * Pseudo fragment activator. Called by the main org.eclipse.jetty.osgi.boot
- * bundle. Please note: this is not a real BundleActivator. Simply something
- * called back by the host bundle.
- * <p>
- * It must be placed in the org.eclipse.jetty.osgi.boot.jsp package: this is
- * because org.eclipse.jetty.osgi.boot.jsp is the symbolic-name of this
- * fragment. From that name, the PackageadminTracker will call this class. IN a
- * different package it won't be called.
+ * FragmentActivator
+ * 
+ * Sets up support for jsp. All relevant jsp jars must also be installed
+ * into the osgi environment.
+ *  <p>
+ * Note that as this is part of a bundle fragment, this activator is NOT
+ * called by the OSGi environment. Instead, the org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminTracker
+ * simulates fragment activation and causes this class's start() method to
+ * be called.
+ * </p>
+ *  <p>
+ * The package of this class MUST match the Bundle-SymbolicName of this fragment
+ * in order for the PackageAdminTracker to find it.
  * </p>
  */
 public class FragmentActivator implements BundleActivator
@@ -43,12 +48,14 @@
      */
     public void start(BundleContext context) throws Exception
     {
+        //jsr199 compilation does not work in osgi
         System.setProperty("org.apache.jasper.compiler.disablejsr199", Boolean.TRUE.toString());
-        WebBundleTrackerCustomizer.JSP_REGISTRATION_HELPERS.add(new WebappRegistrationCustomizerImpl());
-        WebBundleTrackerCustomizer.JSP_REGISTRATION_HELPERS.add(new PluggableWebAppRegistrationCustomizerImpl());
-        //Put in the support for the tag libs
-        addTagLibSupport();
-      
+        
+        //set up some classes that will look for bundles with tlds that must be converted
+        //to urls and treated as if they are on the Jetty container's classpath so that 
+        //jasper can deal with them
+        ServerInstanceWrapper.addContainerTldBundleDiscoverer(new JSTLBundleDiscoverer());
+        ServerInstanceWrapper.addContainerTldBundleDiscoverer(new ContainerTldBundleDiscoverer());      
     }
 
     /**
@@ -58,12 +65,4 @@
     {
 
     }
-    
-    public void addTagLibSupport ()
-    {
-        String[] defaultConfigurations = new String[BundleWebAppProvider.getDefaultConfigurations().length+1];
-        System.arraycopy(BundleWebAppProvider.getDefaultConfigurations(), 0, defaultConfigurations, 0, BundleWebAppProvider.getDefaultConfigurations().length);
-        defaultConfigurations[defaultConfigurations.length-1] = "org.eclipse.jetty.osgi.boot.jsp.TagLibOSGiConfiguration";
-        BundleWebAppProvider.setDefaultConfigurations(defaultConfigurations);
-    }
 }
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractContextProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractContextProvider.java
index 6f07480..5d801c6 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractContextProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractContextProvider.java
@@ -22,14 +22,12 @@
 import java.net.URL;
 import java.util.Dictionary;
 import java.util.HashMap;
-import java.util.Hashtable;
 
 import org.eclipse.jetty.deploy.App;
 import org.eclipse.jetty.deploy.AppProvider;
 import org.eclipse.jetty.deploy.DeploymentManager;
 import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
 import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory;
-import org.eclipse.jetty.osgi.boot.utils.EventSender;
 import org.eclipse.jetty.osgi.boot.utils.OSGiClassLoader;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.component.AbstractLifeCycle;
@@ -37,11 +35,8 @@
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.resource.JarResource;
 import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.webapp.WebAppContext;
 import org.eclipse.jetty.xml.XmlConfiguration;
 import org.osgi.framework.Bundle;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceRegistration;
 
 
 
@@ -49,14 +44,15 @@
 /**
  * AbstractContextProvider
  *
- *
+ * Base class for DeploymentManager Providers that can deploy ContextHandlers into 
+ * Jetty that have been discovered via OSGI either as bundles or services.
+ * 
  */
 public abstract class AbstractContextProvider extends AbstractLifeCycle implements AppProvider
 {
     private static final Logger LOG = Log.getLogger(AbstractContextProvider.class);
     
-    private DeploymentManager _deploymentManager;
-    
+    private DeploymentManager _deploymentManager;    
     
     private ServerInstanceWrapper _serverWrapper;
     
@@ -65,7 +61,7 @@
     
     /* ------------------------------------------------------------ */
     /**
-     * BundleApp
+     * OSGiApp
      *
      *
      */
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractOSGiApp.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractOSGiApp.java
index be09d2c..b9e040b 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractOSGiApp.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractOSGiApp.java
@@ -32,9 +32,10 @@
 
 
 /**
- * AbstractBundleApp
+ * AbstractOSGiApp
  *
- *
+ * Base class representing info about a webapp/ContextHandler that is deployed into Jetty.
+ * 
  */
 public abstract class AbstractOSGiApp extends App
 {      
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java
index 73da20f..cbae1c9 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java
@@ -20,14 +20,9 @@
 
 import java.io.File;
 import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Map;
-
 
 import org.eclipse.jetty.deploy.App;
 import org.eclipse.jetty.deploy.AppProvider;
@@ -35,7 +30,6 @@
 import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
 import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory;
 import org.eclipse.jetty.osgi.boot.internal.webapp.OSGiWebappClassLoader;
-import org.eclipse.jetty.osgi.boot.utils.EventSender;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.component.AbstractLifeCycle;
 import org.eclipse.jetty.util.log.Log;
@@ -44,9 +38,7 @@
 import org.eclipse.jetty.xml.XmlConfiguration;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceReference;
-import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.packageadmin.PackageAdmin;
 
 
@@ -55,7 +47,9 @@
 /**
  * AbstractWebAppProvider
  *
- *
+ * Base class for Jetty DeploymentManager Providers that are capable of deploying a webapp,
+ * either from a bundle or an OSGi service.
+ * 
  */
 public abstract class AbstractWebAppProvider extends AbstractLifeCycle implements AppProvider
 {
@@ -64,10 +58,9 @@
     public static String __defaultConfigurations[] = {
                                                             "org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration",
                                                             "org.eclipse.jetty.webapp.WebXmlConfiguration",
-                                                            "org.eclipse.jetty.osgi.boot.OSGiMetaInfConfiguration",
+                                                            "org.eclipse.jetty.webapp.MetaInfConfiguration",
                                                             "org.eclipse.jetty.webapp.FragmentConfiguration",
-                                                            "org.eclipse.jetty.webapp.JettyWebXmlConfiguration"//,
-                                                            //"org.eclipse.jetty.osgi.boot.jsp.TagLibOSGiConfiguration"                            
+                                                            "org.eclipse.jetty.webapp.JettyWebXmlConfiguration"                          
                                                      };
     
     public static void setDefaultConfigurations (String[] defaultConfigs)
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleContextProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleContextProvider.java
index 149aa99..92dcbd4 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleContextProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleContextProvider.java
@@ -26,16 +26,11 @@
 import java.util.Map;
 
 import org.eclipse.jetty.deploy.App;
-import org.eclipse.jetty.deploy.DeploymentManager;
 import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
-import org.eclipse.jetty.osgi.boot.utils.EventSender;
-import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.webapp.WebAppContext;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 
 
@@ -43,7 +38,7 @@
 /**
  * BundleContextProvider
  *
- * Handles deploying bundles that define a context xml file for configuring them.
+ * Handles deploying OSGi bundles that define a context xml file for configuring them.
  * 
  *
  */
@@ -136,6 +131,7 @@
             }
             apps.add(app);
             getDeploymentManager().addApp(app);
+            added = true;
         }
 
         return added; //true if even 1 context from this bundle was added
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleProvider.java
index c87c071..e137269 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleProvider.java
@@ -20,6 +20,11 @@
 
 import org.osgi.framework.Bundle;
 
+/**
+ * BundleProvider
+ *
+ * Jetty DeploymentManager Provider api for webapps or ContextHandlers that are discovered as osgi bundles.
+ */
 public interface BundleProvider
 {
     public boolean bundleAdded (Bundle bundle) throws Exception;
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleWebAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleWebAppProvider.java
index 2a5e6e3..ab38f29 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleWebAppProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleWebAppProvider.java
@@ -25,7 +25,6 @@
 
 import org.eclipse.jetty.deploy.App;
 import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
-import org.eclipse.jetty.osgi.boot.utils.EventSender;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.osgi.framework.Bundle;
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java
index 2ed6e7b..8763ece 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java
@@ -18,45 +18,35 @@
 
 package org.eclipse.jetty.osgi.boot;
 
-import java.util.Dictionary;
-import java.util.Hashtable;
-
 import org.eclipse.jetty.osgi.boot.internal.serverfactory.DefaultJettyAtJettyHomeHelper;
 import org.eclipse.jetty.osgi.boot.internal.serverfactory.JettyServerServiceTracker;
-import org.eclipse.jetty.osgi.boot.internal.webapp.IWebBundleDeployerHelper;
-import org.eclipse.jetty.osgi.boot.internal.webapp.JettyContextHandlerServiceTracker;
-import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleTrackerCustomizer;
+import org.eclipse.jetty.osgi.boot.internal.webapp.BundleWatcher;
+import org.eclipse.jetty.osgi.boot.internal.webapp.ServiceWatcher;
 import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.util.tracker.BundleTracker;
 
 /**
+ * JettyBootstrapActivator
+ * 
  * Bootstrap jetty and publish a default Server instance as an OSGi service.
  * 
  * Listen for other Server instances to be published as services and support them as deployment targets.
  * 
- * Listen for Bundles to be activated, and deploy those that represent webapps to one of the known Server instances.
+ * Listen for Bundles to be activated, and deploy those that represent webapps/ContextHandlers to one of the known Server instances.
  * 
- * <ol>
- * <li>basic servlet [ok]</li>
- * <li>basic jetty.xml [ok]</li>
- * <li>basic jetty.xml and jetty-plus.xml [ok]</li>
- * <li>basic jsp [ok]</li>
- * <li>jsp with tag-libs [ok]</li>
- * <li>test-jndi with atomikos and derby inside ${jetty.home}/lib/ext [ok]</li>
- * </ul>
  */
 public class JettyBootstrapActivator implements BundleActivator
 {
-
+    private static final Logger LOG = Log.getLogger(JettyBootstrapActivator.class);
+    
     private static JettyBootstrapActivator INSTANCE = null;
 
     public static JettyBootstrapActivator getInstance()
@@ -66,7 +56,7 @@
 
     private ServiceRegistration _registeredServer;
 
-    private JettyContextHandlerServiceTracker _jettyContextHandlerTracker;
+    private ServiceWatcher _jettyContextHandlerTracker;
 
     private PackageAdminServiceTracker _packageAdminServiceTracker;
 
@@ -75,7 +65,10 @@
     private BundleContext _bundleContext;
 
     private JettyServerServiceTracker _jettyServerServiceTracker;
-
+    
+    
+    
+    /* ------------------------------------------------------------ */
     /**
      * Setup a new jetty Server, registers it as a service. Setup the Service
      * tracker for the jetty ContextHandlers that are in charge of deploying the
@@ -84,7 +77,7 @@
      * 
      * @param context
      */
-    public void start(BundleContext context) throws Exception
+    public void start(final BundleContext context) throws Exception
     {
         INSTANCE = this;
         _bundleContext = context;
@@ -98,18 +91,23 @@
         context.addServiceListener(_jettyServerServiceTracker, "(objectclass=" + Server.class.getName() + ")");
 
         // track ContextHandler class instances and deploy them to one of the known Servers
-        _jettyContextHandlerTracker = new JettyContextHandlerServiceTracker();
+        _jettyContextHandlerTracker = new ServiceWatcher();
         context.addServiceListener(_jettyContextHandlerTracker, "(objectclass=" + ContextHandler.class.getName() + ")");
 
         // Create a default jetty instance right now.
-        DefaultJettyAtJettyHomeHelper.startJettyAtJettyHome(context);
+        Server defaultServer = DefaultJettyAtJettyHomeHelper.startJettyAtJettyHome(context);
 
-        // track Bundles and deploy those that represent webapps to one of the known Servers
-        WebBundleTrackerCustomizer customizer = new WebBundleTrackerCustomizer();
-        _webBundleTracker = new BundleTracker(context, Bundle.ACTIVE | Bundle.STOPPING, customizer);
-        customizer.setAndOpenWebBundleTracker(_webBundleTracker);
+        //Create a bundle tracker to help deploy webapps and ContextHandlers
+        BundleWatcher bundleTrackerCustomizer = new BundleWatcher();
+        bundleTrackerCustomizer.setWaitForDefaultServer(defaultServer != null);
+        _webBundleTracker =  new BundleTracker(context, Bundle.ACTIVE | Bundle.STOPPING, bundleTrackerCustomizer);
+        bundleTrackerCustomizer.setBundleTracker(_webBundleTracker);
+        bundleTrackerCustomizer.open();
     }
-
+    
+    
+    
+    /* ------------------------------------------------------------ */
     /**
      * Stop the activator.
      * 
@@ -120,7 +118,6 @@
     {
         try
         {
-
             if (_webBundleTracker != null)
             {
                 _webBundleTracker.close();
@@ -164,122 +161,4 @@
             INSTANCE = null;
         }
     }
-
-    /**
-     * Helper method that creates a new org.jetty.webapp.WebAppContext and
-     * registers it as an OSGi service. The tracker
-     * {@link JettyContextHandlerServiceTracker} will do the actual deployment.
-     * 
-     * @param contributor The bundle
-     * @param webappFolderPath The path to the root of the webapp. Must be a
-     *            path relative to bundle; either an absolute path.
-     * @param contextPath The context path. Must start with "/"
-     * @throws Exception
-     */
-    public static void registerWebapplication(Bundle contributor, String webappFolderPath, String contextPath) throws Exception
-    {
-        checkBundleActivated();
-        WebAppContext contextHandler = new WebAppContext();
-        Dictionary<String,String> dic = new Hashtable<String,String>();
-        dic.put(OSGiWebappConstants.SERVICE_PROP_WAR, webappFolderPath);
-        dic.put(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH, contextPath);
-        String requireTldBundle = (String) contributor.getHeaders().get(OSGiWebappConstants.REQUIRE_TLD_BUNDLE);
-        if (requireTldBundle != null)
-        {
-            dic.put(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE, requireTldBundle);
-        }
-        contributor.getBundleContext().registerService(ContextHandler.class.getName(), contextHandler, dic);
-    }
-
-    /**
-     * Helper method that creates a new org.jetty.webapp.WebAppContext and
-     * registers it as an OSGi service. The tracker
-     * {@link JettyContextHandlerServiceTracker} will do the actual deployment.
-     * 
-     * @param contributor The bundle
-     * @param webappFolderPath The path to the root of the webapp. Must be a
-     *            path relative to bundle; either an absolute path.
-     * @param contextPath The context path. Must start with "/"
-     * @param dic TODO: parameter description
-     * @throws Exception
-     */
-    public static void registerWebapplication(Bundle contributor, String webappFolderPath, String contextPath, Dictionary<String, String> dic) throws Exception
-    {
-        checkBundleActivated();
-        WebAppContext contextHandler = new WebAppContext();
-        dic.put(OSGiWebappConstants.SERVICE_PROP_WAR, webappFolderPath);
-        dic.put(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH, contextPath);
-        contributor.getBundleContext().registerService(ContextHandler.class.getName(), contextHandler, dic);
-    }
-
-    /**
-     * Helper method that creates a new skeleton of a ContextHandler and
-     * registers it as an OSGi service. The tracker
-     * {@link JettyContextHandlerServiceTracker} will do the actual deployment.
-     * 
-     * @param contributor The bundle that registers a new context
-     * @param contextFilePath The path to the file inside the bundle that
-     *            defines the context.
-     * @throws Exception
-     */
-    public static void registerContext(Bundle contributor, String contextFilePath) throws Exception
-    {
-        registerContext(contributor, contextFilePath, new Hashtable<String, String>());
-    }
-
-    /**
-     * Helper method that creates a new skeleton of a ContextHandler and
-     * registers it as an OSGi service. The tracker
-     * {@link JettyContextHandlerServiceTracker} will do the actual deployment.
-     * 
-     * @param contributor The bundle that registers a new context
-     * @param contextFilePath The path to the file inside the bundle that
-     *            defines the context.
-     * @param dic TODO: parameter description
-     * @throws Exception
-     */
-    public static void registerContext(Bundle contributor, String contextFilePath, Dictionary<String, String> dic) throws Exception
-    {
-        checkBundleActivated();
-        ContextHandler contextHandler = new ContextHandler();
-        dic.put(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH, contextFilePath);
-        dic.put(IWebBundleDeployerHelper.INTERNAL_SERVICE_PROP_UNKNOWN_CONTEXT_HANDLER_TYPE, Boolean.TRUE.toString());
-        contributor.getBundleContext().registerService(ContextHandler.class.getName(), contextHandler, dic);
-    }
-
-    public static void unregister(String contextPath)
-    {
-        // todo
-    }
-
-    /**
-     * Since org.eclipse.jetty.osgi.boot does not have a lazy activation policy
-     * when one of the static methods to register a webapp is called we should
-     * make sure that the bundle is started.
-     */
-    private static void checkBundleActivated()
-    {
-        if (INSTANCE == null)
-        {
-            Bundle thisBundle = FrameworkUtil.getBundle(JettyBootstrapActivator.class);
-            try
-            {
-                thisBundle.start();
-            }
-            catch (BundleException e)
-            {
-                // nevermind.
-            }
-        }
-    }
-
-    /**
-     * @return The bundle context for this bundle.
-     */
-    public static BundleContext getBundleContext()
-    {
-        checkBundleActivated();
-        return INSTANCE._bundleContext;
-    }
-
 }
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiDeployer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiDeployer.java
index 5f9321e..1b80e04 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiDeployer.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiDeployer.java
@@ -27,7 +27,9 @@
 /**
  * OSGiDeployer
  *
- *
+ * Extension of standard Jetty deployer that emits OSGi EventAdmin 
+ * events whenever a webapp is deployed into OSGi via Jetty.
+ * 
  */
 public class OSGiDeployer extends StandardDeployer
 {
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java
index bacc8ea..9767bd3 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java
@@ -32,6 +32,14 @@
 import org.eclipse.jetty.webapp.WebAppContext;
 import org.osgi.framework.Bundle;
 
+/**
+ * OSGiMetaInfConfiguration
+ *
+ * Extension of standard Jetty MetaInfConfiguration class to handle OSGi bundle
+ * fragments that may also need to be scanned for META-INF info.
+ * 
+ * @deprecated
+ */
 public class OSGiMetaInfConfiguration extends MetaInfConfiguration
 {
     private static final Logger LOG = Log.getLogger(OSGiMetaInfConfiguration.class);
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java
index 2f9df55..67ef323 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java
@@ -19,6 +19,8 @@
 package org.eclipse.jetty.osgi.boot;
 
 /**
+ * OSGiServerConstants
+ * 
  * Name of the properties that configure a jetty Server OSGi service.
  */
 public class OSGiServerConstants
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiUndeployer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiUndeployer.java
index ac06874..674f960 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiUndeployer.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiUndeployer.java
@@ -29,7 +29,9 @@
 /**
  * OSGiUndeployer
  *
- *
+ * Extension of the Jetty Undeployer which emits OSGi EventAdmin events
+ * whenever a webapp is undeployed from Jetty.
+ * 
  */
 public class OSGiUndeployer extends StandardUndeployer
 {
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java
index 988ebb2..76eb8ad 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java
@@ -52,7 +52,7 @@
     
     public static final String CONTAINER_BUNDLE_PATTERN = "org.eclipse.jetty.server.webapp.containerIncludeBundlePattern";
     
-    
+    /* ------------------------------------------------------------ */
     /** 
      * Check to see if there have been any bundle symbolic names added of bundles that should be
      * regarded as being on the container classpath, and scanned for fragments, tlds etc etc.
@@ -120,7 +120,7 @@
     }
     
     
-    
+    /* ------------------------------------------------------------ */
     /** 
      * Consider the fragment bundles associated with the bundle of the webapp being deployed.
      * 
@@ -148,7 +148,7 @@
         return mergedResources;
     }
     
-    
+    /* ------------------------------------------------------------ */
     /** 
      * Allow fragments to supply some resources that are added to the baseResource of the webapp.
      * 
@@ -227,11 +227,10 @@
             resources[resources.length-1] = context.getBaseResource();
             context.setBaseResource(new ResourceCollection(resources));
         }
-        
     }
 
     
-    
+    /* ------------------------------------------------------------ */
     /**
     * Resolves the bundle. Usually that would be a single URL per bundle. But we do some more work if there are jars
     * embedded in the bundle.
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java
index 1908eb2..e97457a 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java
@@ -19,7 +19,12 @@
 package org.eclipse.jetty.osgi.boot;
 
 /**
- * Name of the service properties for a ContextHandler that configure a webapp deployed on jetty OSGi.
+ * OSGiWebappConstants
+ * 
+ * 
+ * Constants (MANIFEST headers, service properties etc) associated with deploying
+ * webapps into OSGi via Jetty.
+ * 
  */
 public class OSGiWebappConstants
 {
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceContextProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceContextProvider.java
index 2e36904..a228a62 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceContextProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceContextProvider.java
@@ -38,7 +38,9 @@
 /**
  * ServiceContextProvider
  *
- *
+ * Jetty DeploymentManager Provider that is able to deploy ContextHandlers discovered via OSGi as services.
+ * 
+ * 
  */
 public class ServiceContextProvider extends AbstractContextProvider implements ServiceProvider
 { 
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceProvider.java
index f2304c6..34335cf 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceProvider.java
@@ -21,6 +21,11 @@
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.osgi.framework.ServiceReference;
 
+/**
+ * ServiceProvider
+ *
+ * Jetty DeploymentManager Provider api for webapps or ContextHandlers that are discovered as OSGi services.
+ */
 public interface ServiceProvider
 {
     public boolean serviceAdded (ServiceReference ref, ContextHandler handler) throws Exception;
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceWebAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceWebAppProvider.java
index e3f97f0..6008aa0 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceWebAppProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceWebAppProvider.java
@@ -27,7 +27,6 @@
 import org.eclipse.jetty.deploy.AppProvider;
 import org.eclipse.jetty.deploy.DeploymentManager;
 import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
-import org.eclipse.jetty.osgi.boot.utils.EventSender;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloader.java
deleted file mode 100644
index 4b48aef..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloader.java
+++ /dev/null
@@ -1,65 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.internal.jsp;
-
-import java.net.URL;
-import java.net.URLClassLoader;
-
-/**
- * Tricky url classloader. In fact we don't want a real URLClassLoader: we want
- * OSGi to provide its classloader and let it does. But to let
- * {@link org.apache.jasper.compiler.TldLocationsCache} find the core tlds
- * inside the jars we must be a URLClassLoader that returns an array of jars
- * where tlds are stored when the method getURLs is called.
- */
-public class TldLocatableURLClassloader extends URLClassLoader
-{
-
-    private URL[] _jarsWithTldsInside;
-
-    public TldLocatableURLClassloader(ClassLoader osgiClassLoader, URL[] jarsWithTldsInside)
-    {
-        super(new URL[] {},osgiClassLoader);
-        _jarsWithTldsInside = jarsWithTldsInside;
-    }
-
-    /**
-     * @return the jars that contains tlds so that TldLocationsCache or
-     *         TldScanner can find them.
-     */
-    @Override
-    public URL[] getURLs()
-    {
-        return _jarsWithTldsInside;
-    }
-
-    public String toString()
-    {
-        StringBuilder builder = new StringBuilder();
-
-        if (_jarsWithTldsInside != null)
-        {
-            for (URL u:_jarsWithTldsInside)
-                builder.append(" "+u.toString());
-            return builder.toString();
-        }
-        else
-            return super.toString();
-    }
-}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloaderWithInsertedJettyClassloader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloaderWithInsertedJettyClassloader.java
deleted file mode 100644
index e4cf805..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloaderWithInsertedJettyClassloader.java
+++ /dev/null
@@ -1,69 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.internal.jsp;
-
-import java.net.URL;
-
-/**
- * Add a classloader to the
- * org.apache.jasper.compiler.TldLocatableURLClassloader. Hopefuly not
- * necessary: still experimenting.
- * 
- * @see TldLocatableURLClassloader
- */
-public class TldLocatableURLClassloaderWithInsertedJettyClassloader extends TldLocatableURLClassloader
-{
-
-    private ClassLoader _internalClassLoader;
-
-    /**
-     * 
-     * @param osgiClassLoaderParent
-     *            The parent classloader
-     * @param internalClassLoader
-     *            The classloader that will be at the same level than the
-     *            jarsWithTldsInside
-     * @param jarsWithTldsInside
-     *            jars that are scanned for tld files.
-     */
-    public TldLocatableURLClassloaderWithInsertedJettyClassloader(ClassLoader osgiClassLoaderParent, ClassLoader internalClassLoader, URL[] jarsWithTldsInside)
-    {
-        super(osgiClassLoaderParent,jarsWithTldsInside);
-        _internalClassLoader = internalClassLoader;
-    }
-
-    protected Class<?> findClass(String name) throws ClassNotFoundException
-    {
-        try
-        {
-            return super.findClass(name);
-        }
-        catch (ClassNotFoundException cne)
-        {
-            if (_internalClassLoader != null)
-            {
-                return _internalClassLoader.loadClass(name);
-            }
-            else
-            {
-                throw cne;
-            }
-        }
-    }
-}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java
index bef4dcf..a89ff5b 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java
@@ -29,7 +29,6 @@
 import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
 import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
 import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory;
-import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -40,9 +39,12 @@
  * DefaultJettyAtJettyHomeHelper
  * 
  * 
+ * Creates a default instance of Jetty, based on the values of the
+ * System properties "jetty.home" or "jetty.home.bundle", one of which
+ * must be specified in order to create the default instance.
+ * 
  * Called by the {@link JettyBootstrapActivator} during the starting of the
- * bundle. If the system property 'jetty.home' is defined and points to a
- * folder, then setup the corresponding jetty server.
+ * bundle. 
  */
 public class DefaultJettyAtJettyHomeHelper
 {
@@ -64,6 +66,7 @@
     public static final String DEFAULT_JETTYHOME = "/jettyhome/";
     
     
+    
     /* ------------------------------------------------------------ */
     /**
      * Called by the JettyBootStrapActivator. If the system property jetty.home
@@ -87,7 +90,7 @@
      * as part of their properties.
      * </p>
      */
-    public static void startJettyAtJettyHome(BundleContext bundleContext) throws Exception
+    public static Server startJettyAtJettyHome(BundleContext bundleContext) throws Exception
     {
         String jettyHomeSysProp = System.getProperty(OSGiServerConstants.JETTY_HOME);
         String jettyHomeBundleSysProp = System.getProperty(OSGiServerConstants.JETTY_HOME_BUNDLE);
@@ -109,7 +112,7 @@
             if (!jettyHome.exists() || !jettyHome.isDirectory())
             {
                 LOG.warn("Unable to locate the jetty.home folder " + jettyHomeSysProp);
-                return;
+                return null;
             }
         }
         else if (jettyHomeBundleSysProp != null)
@@ -126,14 +129,14 @@
             if (jettyHomeBundle == null)
             {
                 LOG.warn("Unable to find the jetty.home.bundle named " + jettyHomeSysProp);
-                return;
+                return null;
             }
 
         }
         if (jettyHome == null && jettyHomeBundle == null)
         {
             LOG.warn("No default jetty created.");
-            return;
+            return null;
         }
 
         Server server = new Server();
@@ -152,8 +155,11 @@
         setProperty(properties, OSGiServerConstants.JETTY_PORT, System.getProperty(OSGiServerConstants.JETTY_PORT));
         setProperty(properties, OSGiServerConstants.JETTY_PORT_SSL, System.getProperty(OSGiServerConstants.JETTY_PORT_SSL));
 
-        //register the Server instance as an OSGi service.
+        //Register the default Server instance as an OSGi service.
+        //The JettyServerServiceTracker will notice it and configure it.
         bundleContext.registerService(Server.class.getName(), server, properties);
+        
+        return server;
     }
     
     
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServerServiceTracker.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServerServiceTracker.java
index e172c75..b946cd8 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServerServiceTracker.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServerServiceTracker.java
@@ -32,8 +32,11 @@
 import org.osgi.framework.ServiceReference;
 
 /**
- * Deploy the jetty server instances when they are registered as an OSGi
- * service.
+ * JettyServerServiceTracker
+ * 
+ * Tracks instances of Jetty Servers, and configures them so that they can deploy 
+ * webapps or ContextHandlers discovered from the OSGi environment.
+ * 
  */
 public class JettyServerServiceTracker implements ServiceListener, IManagedJettyServerRegistry
 {
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java
index a7e9daa..209fee6 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java
@@ -19,7 +19,6 @@
 package org.eclipse.jetty.osgi.boot.internal.serverfactory;
 
 import java.io.File;
-import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
 import java.util.ArrayList;
@@ -27,8 +26,10 @@
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.StringTokenizer;
 
 import org.eclipse.jetty.deploy.AppLifeCycle;
@@ -44,11 +45,10 @@
 import org.eclipse.jetty.osgi.boot.OSGiUndeployer;
 import org.eclipse.jetty.osgi.boot.ServiceContextProvider;
 import org.eclipse.jetty.osgi.boot.ServiceWebAppProvider;
-import org.eclipse.jetty.osgi.boot.internal.jsp.TldLocatableURLClassloader;
 import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory;
 import org.eclipse.jetty.osgi.boot.internal.webapp.LibExtClassLoaderHelper;
-import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleTrackerCustomizer;
-import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer;
+import org.eclipse.jetty.osgi.boot.utils.FakeURLClassLoader;
+import org.eclipse.jetty.osgi.boot.utils.TldBundleDiscoverer;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.util.IO;
@@ -72,6 +72,9 @@
      * support the case where the bundle is zipped.
      */
     public static final String PROPERTY_THIS_JETTY_XML_FOLDER_URL = "this.jetty.xml.parent.folder.url";
+    
+    
+    private static Collection<TldBundleDiscoverer> __containerTldBundleDiscoverers = new ArrayList<TldBundleDiscoverer>();
 
     private static Logger LOG = Log.getLogger(ServerInstanceWrapper.class.getName());
     
@@ -96,6 +99,21 @@
     private DeploymentManager _deploymentManager;
     
     
+    
+    /* ------------------------------------------------------------ */
+    public static void addContainerTldBundleDiscoverer (TldBundleDiscoverer tldBundleDiscoverer)
+    {
+        __containerTldBundleDiscoverers.add(tldBundleDiscoverer);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static Collection<TldBundleDiscoverer> getContainerTldBundleDiscoverers()
+    {
+        return __containerTldBundleDiscoverers;
+    }
+    
+    
+    
     /* ------------------------------------------------------------ */
     public ServerInstanceWrapper(String managedServerName)
     {
@@ -173,9 +191,29 @@
             configure(server, props);
 
             init();
+            
+            //if support for jsp is enabled, we need to convert locations of bundles that contain tlds into urls.
+            //these are tlds that we want jasper to treat as if they are on the container's classpath. Web bundles
+            //can use the Require-TldBundle MANIFEST header to name other tld-containing bundles that should be regarded
+            //as on the webapp classpath.
+            if (!__containerTldBundleDiscoverers.isEmpty())
+            {
+                Set<URL> urls = new HashSet<URL>();
+                //discover bundles with tlds that need to be on the container's classpath as URLs
+                for (TldBundleDiscoverer d:__containerTldBundleDiscoverers)
+                {
+                    URL[] list = d.getUrlsForBundlesWithTlds(_deploymentManager, BundleFileLocatorHelperFactory.getFactory().getHelper());
+                    if (list != null)
+                    {
+                        for (URL u:list)
+                            urls.add(u);
+                    }
+                }
+                _commonParentClassLoaderForWebapps =  new FakeURLClassLoader(libExtClassLoader, urls.toArray(new URL[urls.size()]));
+            }
+            else
+                _commonParentClassLoaderForWebapps = libExtClassLoader;
 
-            URL[] jarsWithTlds = getJarsWithTlds();
-            _commonParentClassLoaderForWebapps = jarsWithTlds == null ? libExtClassLoader : new TldLocatableURLClassloader(libExtClassLoader, jarsWithTlds);
             
             if (LOG.isDebugEnabled()) LOG.debug("common classloader = "+_commonParentClassLoaderForWebapps);
 
@@ -219,54 +257,7 @@
     }
     
     
-    /* ------------------------------------------------------------ */
-    /**
-     * TODO: right now only the jetty-jsp bundle is scanned for common taglibs.
-     * Should support a way to plug more bundles that contain taglibs.
-     * 
-     * The jasper TldScanner expects a URLClassloader to parse a jar for the
-     * /META-INF/*.tld it may contain. We place the bundles that we know contain
-     * such tag-libraries. Please note that it will work if and only if the
-     * bundle is a jar (!) Currently we just hardcode the bundle that contains
-     * the jstl implementation.
-     * 
-     * A workaround when the tld cannot be parsed with this method is to copy
-     * and paste it inside the WEB-INF of the webapplication where it is used.
-     * 
-     * Support only 2 types of packaging for the bundle: - the bundle is a jar
-     * (recommended for runtime.) - the bundle is a folder and contain jars in
-     * the root and/or in the lib folder (nice for PDE development situations)
-     * Unsupported: the bundle is a jar that embeds more jars.
-     * 
-     * @return
-     * @throws Exception
-     */
-    private URL[] getJarsWithTlds() throws Exception
-    {
-        
-        //Jars that are added onto the equivalent of the container classpath are:
-        // jstl jars: identified by the class WhenTag (and the boot-bundle manifest imports the jstl packages
-        // bundles identified by System property org.eclipse.jetty.osgi.tldbundles
-        // bundle symbolic name patterns defined in the DeploymentManager
-        //
-        // Any bundles mentioned in the Require-TldBundle manifest header of the webapp bundle MUST ALSO HAVE Import-Bundle
-        // in order to get them onto the classpath of the webapp.
-        
-        ArrayList<URL> res = new ArrayList<URL>();
-        for (WebappRegistrationCustomizer regCustomizer : WebBundleTrackerCustomizer.JSP_REGISTRATION_HELPERS)
-        {
-            URL[] urls = regCustomizer.getJarsWithTlds(_deploymentManager, BundleFileLocatorHelperFactory.getFactory().getHelper());
-            for (URL url : urls)
-            {
-                if (!res.contains(url)) res.add(url);
-            }
-        }
-        if (!res.isEmpty())
-            return res.toArray(new URL[res.size()]);
-        else
-            return null;
-    }
-    
+   
     
     /* ------------------------------------------------------------ */
     private void configure(Server server, Dictionary props) throws Exception
@@ -340,7 +331,9 @@
         }
 
     }
-
+    
+    
+    /* ------------------------------------------------------------ */
     /**
      * Must be called after the server is configured. 
      * 
@@ -438,7 +431,9 @@
             }
         }
     }
-
+    
+    
+    /* ------------------------------------------------------------ */
     /**
      * @return The default folder in which the context files of the osgi bundles
      *         are located and watched. Or null when the system property
@@ -463,7 +458,7 @@
         return new File(jettyHome, "/contexts");
     }
 
-
+    /* ------------------------------------------------------------ */
     /**
      * @return the urls in this string.
      */
@@ -485,7 +480,9 @@
         }
         return urls;
     }
-
+    
+    
+    /* ------------------------------------------------------------ */
     /**
      * Get the folders that might contain jars for the legacy J2EE shared
      * libraries
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/BundleWatcher.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/BundleWatcher.java
new file mode 100644
index 0000000..74622c3
--- /dev/null
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/BundleWatcher.java
@@ -0,0 +1,317 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.boot.internal.webapp;
+
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.eclipse.jetty.osgi.boot.BundleProvider;
+import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
+import org.eclipse.jetty.osgi.boot.utils.TldBundleDiscoverer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.BundleTracker;
+import org.osgi.util.tracker.BundleTrackerCustomizer;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * BundleWatcher
+ * 
+ * 
+ * Tracks the installation and removal of Bundles in the OSGi environment. Any bundles
+ * that are added are passed to the set of Jetty DeploymentManager providers to see if
+ * the bundle should be deployed as a webapp or ContextHandler into Jetty.
+ * 
+ * @author hmalphettes
+ */
+public class BundleWatcher implements BundleTrackerCustomizer
+{
+    private static final Logger LOG = Log.getLogger(BundleWatcher.class);
+    
+    public static Collection<TldBundleDiscoverer> JSP_REGISTRATION_HELPERS = new ArrayList<TldBundleDiscoverer>();
+
+
+    public static final String FILTER = "(objectclass=" + BundleProvider.class.getName() + ")";
+    private ServiceTracker _serviceTracker;
+    private BundleTracker _bundleTracker;
+    private boolean _waitForDefaultServer = true;
+    private boolean _defaultServerReady = false;
+    private Bundle _bundle = null;
+    
+ 
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @throws Exception
+     */
+    public BundleWatcher() throws Exception
+    {
+        _bundle = FrameworkUtil.getBundle(this.getClass());
+        //Track all BundleProviders (Jetty DeploymentManager Providers that can deploy bundles)
+        _serviceTracker = new ServiceTracker(_bundle.getBundleContext(), FrameworkUtil.createFilter(FILTER),null);
+        _serviceTracker.open();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public boolean isWaitForDefaultServer()
+    {
+        return _waitForDefaultServer;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public void setWaitForDefaultServer(boolean waitForDefaultServer)
+    {
+        _waitForDefaultServer = waitForDefaultServer;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    public void setBundleTracker (BundleTracker bundleTracker)
+    {
+        _bundleTracker = bundleTracker;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    public void open () throws Exception
+    {
+        if (_waitForDefaultServer && !_defaultServerReady)
+        {
+            String filter = "(&(objectclass=" + BundleProvider.class.getName() + ")"+
+                    "("+OSGiServerConstants.MANAGED_JETTY_SERVER_NAME+"="+OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME+"))";
+            
+            ServiceTracker defaultServerTracker = new ServiceTracker(_bundle.getBundleContext(), 
+                                                                     FrameworkUtil.createFilter(filter),null)
+            {
+                public Object addingService(ServiceReference reference)
+                {
+                    try
+                    {
+                        Object object = super.addingService(reference);
+                        LOG.debug("Default Jetty Server registered {}", reference);
+                        _defaultServerReady = true;
+                        openBundleTracker();
+                        return object;
+                    }
+                    catch (Exception e)
+                    {
+                        throw new IllegalStateException(e);
+                    }
+                }
+            };
+            defaultServerTracker.open();
+        }
+        else
+            openBundleTracker();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param managedServerName
+     * @return
+     */
+    public Map<ServiceReference, BundleProvider> getDeployers(String managedServerName)
+    {
+        if (managedServerName == null)
+            managedServerName = OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME;
+        
+        Map<ServiceReference, BundleProvider> candidates = new HashMap<ServiceReference, BundleProvider>();
+        
+        ServiceReference[] references = _serviceTracker.getServiceReferences();
+        if (references != null)
+        {
+            for (ServiceReference ref:references)
+            {
+                String name = (String)ref.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);                
+                if (managedServerName.equalsIgnoreCase(name))
+                {
+                    BundleProvider candidate = (BundleProvider)_serviceTracker.getService(ref);
+                    if (candidate != null)
+                        candidates.put(ref, candidate);
+                }
+            }
+        }
+       return candidates;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * A bundle is being added to the <code>BundleTracker</code>.
+     * 
+     * <p>
+     * This method is called before a bundle which matched the search parameters
+     * of the <code>BundleTracker</code> is added to the
+     * <code>BundleTracker</code>. This method should return the object to be
+     * tracked for the specified <code>Bundle</code>. The returned object is
+     * stored in the <code>BundleTracker</code> and is available from the
+     * {@link BundleTracker#getObject(Bundle) getObject} method.
+     * 
+     * @param bundle The <code>Bundle</code> being added to the
+     *            <code>BundleTracker</code>.
+     * @param event The bundle event which caused this customizer method to be
+     *            called or <code>null</code> if there is no bundle event
+     *            associated with the call to this method.
+     * @return The object to be tracked for the specified <code>Bundle</code>
+     *         object or <code>null</code> if the specified <code>Bundle</code>
+     *         object should not be tracked.
+     */
+    public Object addingBundle(Bundle bundle, BundleEvent event)
+    {
+        if (bundle.getState() == Bundle.ACTIVE)
+        {
+            register(bundle);          
+        }
+        else if (bundle.getState() == Bundle.STOPPING)
+        {
+            unregister(bundle);
+        }
+        else
+        {
+            // we should not be called in that state as
+            // we are registered only for ACTIVE and STOPPING
+        }
+        return null;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * A bundle tracked by the <code>BundleTracker</code> has been modified.
+     * 
+     * <p>
+     * This method is called when a bundle being tracked by the
+     * <code>BundleTracker</code> has had its state modified.
+     * 
+     * @param bundle The <code>Bundle</code> whose state has been modified.
+     * @param event The bundle event which caused this customizer method to be
+     *            called or <code>null</code> if there is no bundle event
+     *            associated with the call to this method.
+     * @param object The tracked object for the specified bundle.
+     */
+    public void modifiedBundle(Bundle bundle, BundleEvent event, Object object)
+    {
+        if (bundle.getState() == Bundle.STOPPING || bundle.getState() == Bundle.ACTIVE)
+        {
+            unregister(bundle);
+        }
+        if (bundle.getState() == Bundle.ACTIVE)
+        {
+            register(bundle);
+        }
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * A bundle tracked by the <code>BundleTracker</code> has been removed.
+     * 
+     * <p>
+     * This method is called after a bundle is no longer being tracked by the
+     * <code>BundleTracker</code>.
+     * 
+     * @param bundle The <code>Bundle</code> that has been removed.
+     * @param event The bundle event which caused this customizer method to be
+     *            called or <code>null</code> if there is no bundle event
+     *            associated with the call to this method.
+     * @param object The tracked object for the specified bundle.
+     */
+    public void removedBundle(Bundle bundle, BundleEvent event, Object object)
+    {
+        unregister(bundle);
+    }
+
+    
+    protected void openBundleTracker()
+    {
+        _bundleTracker.open();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param bundle
+     * @return true if this bundle can be deployed into Jetty
+     */
+    private boolean register(Bundle bundle)
+    {
+        if (bundle == null)
+            return false;
+
+        //It might be a bundle that is deployable by Jetty.
+        //Use any named Server instance provided, defaulting to the default Server instance if none supplied
+        boolean deployed = false;
+        String serverName = (String)bundle.getHeaders().get(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);
+        Map<ServiceReference, BundleProvider> candidates = getDeployers(serverName);
+        if (candidates != null)
+        {
+            Iterator<Entry<ServiceReference, BundleProvider>> itor = candidates.entrySet().iterator();
+            while (!deployed && itor.hasNext())
+            {
+                Entry<ServiceReference, BundleProvider> e = itor.next();
+                try
+                {           
+                    deployed = e.getValue().bundleAdded(bundle);
+                }
+                catch (Exception x)
+                {
+                    LOG.warn("Error deploying bundle for jetty context", x);
+                }
+            }
+        }
+
+        return deployed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param bundle
+     */
+    private void unregister(Bundle bundle)
+    { 
+        boolean undeployed = false;
+        String serverName = (String)bundle.getHeaders().get(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);    
+        Map<ServiceReference, BundleProvider> candidates = getDeployers(serverName);
+        if (candidates != null)
+        {
+            Iterator<Entry<ServiceReference, BundleProvider>> itor = candidates.entrySet().iterator();
+            while (!undeployed && itor.hasNext())
+            {
+                Entry<ServiceReference, BundleProvider> e = itor.next();
+                try
+                {
+                    undeployed = e.getValue().bundleRemoved(bundle);
+                }
+                catch (Exception x)
+                {
+                    LOG.warn("Error undeploying Bundle representing jetty deployable ", x);
+                }
+            }
+        }
+    }
+}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java
deleted file mode 100644
index 1c5cd3e..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java
+++ /dev/null
@@ -1,88 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.internal.webapp;
-
-
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.osgi.framework.Bundle;
-
-/**
- * Internal interface for the class that deploys a webapp on a server. Used as
- * we migrate from the single instance of the jety server to multiple jetty
- * servers.
- */
-public interface IWebBundleDeployerHelper
-{
-
-    /**
-     * when this property is present, the type of context handler registered is
-     * not known in advance.
-     */
-    public static final String INTERNAL_SERVICE_PROP_UNKNOWN_CONTEXT_HANDLER_TYPE = "unknownContextHandlerType";
-
-    /**
-     * Deploy a new web application on the jetty server.
-     * 
-     * @param bundle The bundle
-     * @param webappFolderPath The path to the root of the webapp. Must be a
-     *            path relative to bundle; either an absolute path.
-     * @param contextPath The context path. Must start with "/"
-     * @param extraClasspath
-     * @param overrideBundleInstallLocation
-     * @param requireTldBundle The list of bundles's symbolic names that contain
-     *            tld files that are required by this WAB.
-     * @param webXmlPath
-     * @param defaultWebXmlPath TODO: parameter description
-     * @return The contexthandler created and started
-     * @throws Exception
-     */
-    public abstract WebAppContext registerWebapplication(Bundle bundle, String webappFolderPath, String contextPath, String extraClasspath,
-                                                         String overrideBundleInstallLocation, String requireTldBundle, String webXmlPath,
-                                                         String defaultWebXmlPath, WebAppContext webAppContext) throws Exception;
-
-    /**
-     * Stop a ContextHandler and remove it from the collection.
-     * 
-     * @see ContextDeployer#undeploy
-     * @param contextHandler
-     * @throws Exception
-     */
-    public abstract void unregister(ContextHandler contextHandler) throws Exception;
-
-    /**
-     * This type of registration relies on jetty's complete context xml file.
-     * Context encompasses jndi and all other things. This makes the definition
-     * of the webapp a lot more self-contained.
-     * 
-     * @param contributor
-     * @param contextFileRelativePath
-     * @param extraClasspath
-     * @param overrideBundleInstallLocation
-     * @param requireTldBundle The list of bundles'symbolic name that contain
-     *            tld files for this webapp.
-     * @param handler the context handler passed in the server reference that
-     *            will be configured, deployed and started.
-     * @return The contexthandler created and started
-     * @throws Exception
-     */
-    public abstract ContextHandler registerContext(Bundle contributor, String contextFileRelativePath, String extraClasspath,
-                                                   String overrideBundleInstallLocation, String requireTldBundle, ContextHandler handler) throws Exception;
-
-}
\ No newline at end of file
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java
index c66fa26..7673834 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java
@@ -33,6 +33,9 @@
 import org.eclipse.jetty.server.Server;
 
 /**
+ * LibExtClassLoaderHelper
+ * 
+ * 
  * Helper to create a URL class-loader with the jars inside
  * ${jetty.home}/lib/ext and ${jetty.home}/resources. In an ideal world, every
  * library is an OSGi bundle that does loads nicely. To support standard jars or
@@ -40,57 +43,40 @@
  * inserting the jars in the usual jetty/lib/ext folders in the proper classpath
  * for the webapps.
  * <p>
- * Also the folder resources typically contains central configuration files for
- * things like: log config and others. We enable fragments to register classes
- * that are called back and passed those resources to do what they need to do.
+ * The drawback is that those jars will not be available in the OSGi
+ * classloader.
  * </p>
  * <p>
- * For example the test-jndi webapplication depends on derby, derbytools,
- * atomikos none of them are osgi bundles. we can either re-package them or we
- * can place them in the usual lib/ext. <br/>
- * In fact jasper's jsp libraries should maybe place in lib/ext too.
- * </p>
- * <p>
- * The drawback is that those libraries will not be available in the OSGi
- * classloader. Note that we could have setup those jars as embedded jars of the
- * current bundle. However, we would need to know in advance what are those jars
- * which was not acceptable. Also having those jars in a URLClassLoader seem to
- * be required for some cases. For example jaspers' TldLocationsCache (replaced
- * by TldScanner for servlet-3.0). <br/>
- * Also all the dependencies of those libraries must be resolvable directly from
- * the JettyBootstrapActivator bundle as it is set as the parent classloader. For
- * example: if atomikos is placed in lib/ext it will work if and only if
- * JettyBootstrapActivator import the necessary packages from javax.naming*,
- * javax.transaction*, javax.mail* etc Most of the common cases of javax are
- * added as optional import packages into jetty bootstrapper plugin. When there
- * are not covered: please make a request or create a fragment or register a
- * bundle with a buddy-policy onto the jetty bootstrapper..
- * </p>
- * <p>
- * Alternatives to placing jars in lib/ext
+ * Alternatives to placing jars in lib/ext:
  * <ol>
- * <li>Bundle the jars in an osgi bundle. Have the webapp(s) that context
- * depends on them depend on that bundle. Things will go well for jetty.</li>
+ * <li>Bundle the jars in an osgi bundle. Have the webapp(s) that need these jars
+ * depend on that bundle.</li>
  * <li>Bundle those jars in an osgi bundle-fragment that targets the
  * jetty-bootstrap bundle</li>
  * <li>Use equinox Buddy-Policy: register a buddy of the jetty bootstrapper
- * bundle. (least favorite: it will work only on equinox)</li>
+ * bundle. (Note: it will work only on equinox)</li>
  * </ol>
  * </p>
  */
 public class LibExtClassLoaderHelper
 {
-
+    /* ------------------------------------------------------------ */
     /**
-     * Class called back
+     * IFilesInJettyHomeResourcesProcessor
+     * 
+     * Interface for callback impls
      */
     public interface IFilesInJettyHomeResourcesProcessor
     {
         void processFilesInResourcesFolder(File jettyHome, Map<String, File> filesInResourcesFolder);
     }
 
+    
+    
     public static Set<IFilesInJettyHomeResourcesProcessor> registeredFilesInJettyHomeResourcesProcessors = new HashSet<IFilesInJettyHomeResourcesProcessor>();
 
+    
+    /* ------------------------------------------------------------ */
     /**
      * @param server
      * @return a url classloader with the jars of resources, lib/ext and the
@@ -145,6 +131,8 @@
         return new URLClassLoader(urls.toArray(new URL[urls.size()]), parentClassLoader);
     }
 
+    
+    /* ------------------------------------------------------------ */
     /**
      * @param server
      * @return a url classloader with the jars of resources, lib/ext and the
@@ -188,6 +176,7 @@
         return new URLClassLoader(urls.toArray(new URL[urls.size()]), parentClassLoader);
     }
 
+    /* ------------------------------------------------------------ */
     /**
      * When we find files typically used for central logging configuration we do
      * what it takes in this method to do what the user expects. Without
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java
index 5f7e644..e963b91 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java
@@ -33,7 +33,6 @@
 
 import javax.servlet.http.HttpServlet;
 
-import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper;
 import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelperFactory;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -44,8 +43,10 @@
 import org.osgi.framework.BundleReference;
 
 /**
- * Extends the webappclassloader to insert the classloader provided by the osgi
- * bundle at the same level than any other jars palced in the webappclassloader.
+ * OSGiWebappClassLoader
+ * 
+ * 
+ * Extends the webapp classloader to also use the classloader of the Bundle defining the webapp.
  */
 public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleReference
 {
@@ -79,10 +80,9 @@
 
     private boolean _lookInOsgiFirst = true;
 
-    private Set<String> _libsAlreadyInManifest = new HashSet<String>();
-
+    /* ------------------------------------------------------------ */
     /**
-     * @param parent The parent classloader. In this case
+     * @param parent The parent classloader.
      * @param context The WebAppContext
      * @param contributor The bundle that defines this web-application.
      * @throws IOException
@@ -94,7 +94,10 @@
         _contributor = contributor;
         _osgiBundleClassLoader = BundleClassLoaderHelperFactory.getFactory().getHelper().getBundleClassLoader(contributor);
     }
-
+    
+    
+    
+    /* ------------------------------------------------------------ */
     /**
      * Returns the <code>Bundle</code> that defined this web-application.
      * 
@@ -106,17 +109,7 @@
         return _contributor;
     }
 
-    /**
-     * Reads the manifest. If the manifest is already configured to loads a few
-     * libs we should not add them to the classpath of the webapp. Not really
-     * important as we resolve classes through the osgi classloader first and
-     * then default on the libs of the webapp.
-     */
-    private void computeLibsAlreadyInOSGiClassLoader()
-    {
-        // TODO
-    }
-
+    /* ------------------------------------------------------------ */
     @Override
     public Enumeration<URL> getResources(String name) throws IOException
     {
@@ -131,7 +124,10 @@
             return Collections.enumeration(toList(urls, osgiUrls));
         }
     }
-
+    
+    
+    
+    /* ------------------------------------------------------------ */
     @Override
     public URL getResource(String name)
     {
@@ -146,7 +142,10 @@
             return url != null ? url : _osgiBundleClassLoader.getResource(name);
         }
     }
-
+    
+    
+    
+    /* ------------------------------------------------------------ */
     private List<URL> toList(Enumeration<URL> e, Enumeration<URL> e2)
     {
         List<URL> list = new ArrayList<URL>();
@@ -157,9 +156,8 @@
         return list;
     }
 
-    /**
-     * 
-     */
+    
+    /* ------------------------------------------------------------ */
     protected Class<?> findClass(String name) throws ClassNotFoundException
     {
         try
@@ -178,7 +176,10 @@
             }
         }
     }
-
+    
+    
+    
+    /* ------------------------------------------------------------ */
     /**
      * Parse the classpath ourselves to be able to filter things. This is a
      * derivative work of the super class
@@ -207,6 +208,8 @@
 
     }
 
+    
+    /* ------------------------------------------------------------ */
     /**
      * @param lib
      * @return true if the lib should be included in the webapp classloader.
@@ -255,6 +258,8 @@
 
     private static Field _contextField;
 
+    
+    /* ------------------------------------------------------------ */
     /**
      * In the case of the generation of a webapp via a jetty context file we
      * need a proper classloader to setup the app before we have the
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/ServiceWatcher.java
similarity index 91%
rename from jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java
rename to jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/ServiceWatcher.java
index 6bd352d..08dbd4a 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/ServiceWatcher.java
@@ -18,28 +18,18 @@
 
 package org.eclipse.jetty.osgi.boot.internal.webapp;
 
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
-import org.eclipse.jetty.osgi.boot.BundleWebAppProvider;
 import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
 import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
 import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
 import org.eclipse.jetty.osgi.boot.ServiceProvider;
-import org.eclipse.jetty.osgi.boot.internal.serverfactory.DefaultJettyAtJettyHomeHelper;
-import org.eclipse.jetty.osgi.boot.internal.serverfactory.IManagedJettyServerRegistry;
-import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.Scanner;
-import org.eclipse.jetty.webapp.WebAppContext;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
@@ -49,7 +39,7 @@
 import org.osgi.util.tracker.ServiceTracker;
 
 /**
- * JettyContextHandlerServiceTracker
+ * ServiceWatcher
  * 
  * When a {@link ContextHandler} is activated as an osgi service we find a jetty deployer
  * for it. The ContextHandler could be either a WebAppContext or any other derivative of 
@@ -59,9 +49,9 @@
  * osgi services. Instead, they can be deployed via manifest headers inside bundles. See
  * {@link WebBundleTrackerCustomizer}.
  */
-public class JettyContextHandlerServiceTracker implements ServiceListener
+public class ServiceWatcher implements ServiceListener
 {
-    private static Logger LOG = Log.getLogger(JettyContextHandlerServiceTracker.class);
+    private static Logger LOG = Log.getLogger(ServiceWatcher.class);
     
     public static final String FILTER = "(objectclass=" + ServiceProvider.class.getName() + ")";
 
@@ -75,7 +65,7 @@
     /**
      * @param registry
      */
-    public JettyContextHandlerServiceTracker() throws Exception
+    public ServiceWatcher() throws Exception
     {
         //track all instances of deployers of webapps
         Bundle myBundle = FrameworkUtil.getBundle(this.getClass());
@@ -195,6 +185,7 @@
                         try
                         {
                             added = e.getValue().serviceAdded(sr, contextHandler);
+                            System.err.println(serverName+" deployed "+contextHandler+": "+added);
                             if (added && LOG.isDebugEnabled())
                                 LOG.debug("Provider "+e.getValue()+" deployed "+contextHandler);
                         }
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java
deleted file mode 100644
index b8adc65..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java
+++ /dev/null
@@ -1,250 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.internal.webapp;
-
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-import org.eclipse.jetty.osgi.boot.BundleProvider;
-import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
-import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleEvent;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.BundleTracker;
-import org.osgi.util.tracker.BundleTrackerCustomizer;
-import org.osgi.util.tracker.ServiceTracker;
-
-/**
- * WebBundleTrackerCustomizer
- * 
- * 
- * Support bundles that declare a webpp or context directly through headers in their
- * manifest. They will be deployed to the default jetty Server instance.
- * 
- * If you wish to deploy a context or webapp to a different jetty Server instance,
- * register your context/webapp as an osgi service, and set the property OSGiServerConstants.MANAGED_JETTY_SERVER_NAME
- * with the name of the Server instance you wish to depoy to.
- * 
- * @author hmalphettes
- */
-public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer
-{
-    private static final Logger LOG = Log.getLogger(WebBundleTrackerCustomizer.class);
-    
-    public static Collection<WebappRegistrationCustomizer> JSP_REGISTRATION_HELPERS = new ArrayList<WebappRegistrationCustomizer>();
-    public static final String FILTER = "(&(objectclass=" + BundleProvider.class.getName() + ")"+
-                                          "("+OSGiServerConstants.MANAGED_JETTY_SERVER_NAME+"="+OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME+"))";
-
-    private ServiceTracker _serviceTracker;
-    private BundleTracker _bundleTracker;
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @throws Exception
-     */
-    public WebBundleTrackerCustomizer ()
-    throws Exception
-    {
-        Bundle myBundle = FrameworkUtil.getBundle(this.getClass());
-        
-        //track all instances of deployers of webapps/contexts as bundles       
-        _serviceTracker = new ServiceTracker(myBundle.getBundleContext(), FrameworkUtil.createFilter(FILTER),null) {
-            public Object addingService(ServiceReference reference) {
-                Object object = super.addingService(reference);
-                LOG.debug("Deployer registered {}", reference);
-                openBundleTracker();
-                return object;
-            }
-        };
-        _serviceTracker.open();
-
-    }
-    
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * A bundle is being added to the <code>BundleTracker</code>.
-     * 
-     * <p>
-     * This method is called before a bundle which matched the search parameters
-     * of the <code>BundleTracker</code> is added to the
-     * <code>BundleTracker</code>. This method should return the object to be
-     * tracked for the specified <code>Bundle</code>. The returned object is
-     * stored in the <code>BundleTracker</code> and is available from the
-     * {@link BundleTracker#getObject(Bundle) getObject} method.
-     * 
-     * @param bundle The <code>Bundle</code> being added to the
-     *            <code>BundleTracker</code>.
-     * @param event The bundle event which caused this customizer method to be
-     *            called or <code>null</code> if there is no bundle event
-     *            associated with the call to this method.
-     * @return The object to be tracked for the specified <code>Bundle</code>
-     *         object or <code>null</code> if the specified <code>Bundle</code>
-     *         object should not be tracked.
-     */
-    public Object addingBundle(Bundle bundle, BundleEvent event)
-    {
-        if (bundle.getState() == Bundle.ACTIVE)
-        {
-            register(bundle);          
-        }
-        else if (bundle.getState() == Bundle.STOPPING)
-        {
-            unregister(bundle);
-        }
-        else
-        {
-            // we should not be called in that state as
-            // we are registered only for ACTIVE and STOPPING
-        }
-        return null;
-    }
-
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * A bundle tracked by the <code>BundleTracker</code> has been modified.
-     * 
-     * <p>
-     * This method is called when a bundle being tracked by the
-     * <code>BundleTracker</code> has had its state modified.
-     * 
-     * @param bundle The <code>Bundle</code> whose state has been modified.
-     * @param event The bundle event which caused this customizer method to be
-     *            called or <code>null</code> if there is no bundle event
-     *            associated with the call to this method.
-     * @param object The tracked object for the specified bundle.
-     */
-    public void modifiedBundle(Bundle bundle, BundleEvent event, Object object)
-    {
-        // nothing the web-bundle was already track. something changed.
-        // we only reload the webapps if the bundle is stopped and restarted.
-        if (bundle.getState() == Bundle.STOPPING || bundle.getState() == Bundle.ACTIVE)
-        {
-            unregister(bundle);
-        }
-        if (bundle.getState() == Bundle.ACTIVE)
-        {
-            register(bundle);
-        }
-    }
-
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * A bundle tracked by the <code>BundleTracker</code> has been removed.
-     * 
-     * <p>
-     * This method is called after a bundle is no longer being tracked by the
-     * <code>BundleTracker</code>.
-     * 
-     * @param bundle The <code>Bundle</code> that has been removed.
-     * @param event The bundle event which caused this customizer method to be
-     *            called or <code>null</code> if there is no bundle event
-     *            associated with the call to this method.
-     * @param object The tracked object for the specified bundle.
-     */
-    public void removedBundle(Bundle bundle, BundleEvent event, Object object)
-    {
-        unregister(bundle);
-    }
-
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @param bundle
-     * @return true if this bundle in indeed a web-bundle.
-     */
-    private boolean register(Bundle bundle)
-    {
-        if (bundle == null)
-            return false;
-
-        //It might be a bundle that we can deploy to our default jetty server instance
-        boolean deployed = false;
-        Object[] deployers = _serviceTracker.getServices();
-        if (deployers != null)
-        {
-            int i=0;
-            while (!deployed && i<deployers.length)
-            {
-
-                BundleProvider p = (BundleProvider)deployers[i];
-                try
-                {
-                    deployed = p.bundleAdded(bundle);
-                }
-                catch (Exception x)
-                {
-                    LOG.warn("Error deploying bundle for jetty context", x);
-                }
-                i++;
-            }
-        }
-
-        return deployed;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param bundle
-     */
-    private void unregister(Bundle bundle)
-    { 
-        Object[] deployers = _serviceTracker.getServices();
-        boolean undeployed = false;
-        if (deployers != null)
-        {
-            int i=0;
-            while (!undeployed && i<deployers.length)
-            {
-                try
-                {
-                    undeployed = ((BundleProvider)deployers[i++]).bundleRemoved(bundle);
-                }
-                catch (Exception x)
-                {
-                    LOG.warn("Error undeploying bundle for jetty context", x);
-                }
-            }
-        }
-    }
-
-    public void setAndOpenWebBundleTracker(BundleTracker bundleTracker) {
-        if(_bundleTracker == null) {
-            _bundleTracker = bundleTracker;
-            LOG.debug("Bundle tracker is set");
-            openBundleTracker();
-        }
-    }
-
-    private void openBundleTracker() {
-        if(_bundleTracker != null && _serviceTracker.getServices() != null &&
-                _serviceTracker.getServices().length > 0) {
-            _bundleTracker.open();
-            LOG.debug("Bundle tracker has been opened");
-        }
-    }
-
-}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelper.java
index 4c4382f..ba99ac2 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelper.java
@@ -22,6 +22,10 @@
 import org.osgi.framework.Bundle;
 
 /**
+ * 
+ * BundleClassLoaderHelper
+ * 
+ * 
  * Is there a clean OSGi way to go from the Bundle object to the classloader of
  * the Bundle ? You can certainly take a class inside the bundle and get the
  * bundle's classloader that way. Getting the classloader directly from the
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelperFactory.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelperFactory.java
index edcfde0..297f0f3 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelperFactory.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelperFactory.java
@@ -32,15 +32,19 @@
     
     private static BundleClassLoaderHelperFactory _instance = new BundleClassLoaderHelperFactory();
     
+    
+    /* ------------------------------------------------------------ */
     public static BundleClassLoaderHelperFactory getFactory()
     {
         return _instance;
     }
     
+    /* ------------------------------------------------------------ */
     private BundleClassLoaderHelperFactory()
     {
     }
     
+    /* ------------------------------------------------------------ */
     public BundleClassLoaderHelper getHelper()
     {
         //use the default
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java
index 0809b72..2abd7a0 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java
@@ -26,6 +26,9 @@
 import org.osgi.framework.Bundle;
 
 /**
+ * BundleFileLocatorHelper
+ * 
+ * 
  * From a bundle to its location on the filesystem. Assumes the bundle is not a
  * jar.
  * 
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/EventSender.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/EventSender.java
index 13703fa..c4e06a2 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/EventSender.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/EventSender.java
@@ -21,14 +21,18 @@
 import java.util.Dictionary;
 import java.util.Hashtable;
 
-import javax.security.auth.login.FailedLoginException;
-
 import org.osgi.framework.Bundle;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.event.Event;
 import org.osgi.service.event.EventAdmin;
 
+/**
+ * EventSender
+ *
+ * Utility class for emiting OSGi EventAdmin events
+ * 
+ */
 public class EventSender
 {    
     //OSGi Event Admin events for webapps
@@ -43,6 +47,13 @@
     private Bundle _myBundle;
     private EventAdmin _eventAdmin;
     
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * 
+     */
     private EventSender ()
     {
         _myBundle = FrameworkUtil.getBundle(EventSender.class);
@@ -50,13 +61,27 @@
         if (ref != null)
             _eventAdmin = (EventAdmin)_myBundle.getBundleContext().getService(ref);
     }
-
     
+    
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return
+     */
     public static EventSender getInstance()
     {
         return __instance;
     }
 
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param topic
+     * @param wab
+     * @param contextPath
+     */
     public  void send (String topic, Bundle wab, String contextPath)
     {
         if (topic==null || wab==null || contextPath==null)
@@ -66,6 +91,14 @@
     }
     
     
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param topic
+     * @param wab
+     * @param contextPath
+     * @param ex
+     */
     public  void send (String topic, Bundle wab, String contextPath, Exception ex)
     {        
         if (_eventAdmin == null)
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/FakeURLClassLoader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/FakeURLClassLoader.java
new file mode 100644
index 0000000..77f8510
--- /dev/null
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/FakeURLClassLoader.java
@@ -0,0 +1,83 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.boot.utils;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+/**
+ * 
+ * FakeURLClassLoader
+ * 
+ * A URLClassloader that overrides the getURLs() method to return the list
+ * of urls passed in to the constructor, but otherwise acts as if it has no
+ * urls, which would cause it to delegate to the parent classloader (in this
+ * case an OSGi classloader).
+ * 
+ * The main use of this class is with jars containing tlds. Jasper expects a
+ * URL classloader to inspect for jars with tlds.
+ * 
+ */
+public class FakeURLClassLoader extends URLClassLoader
+{
+
+    private URL[] _jars;
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param osgiClassLoader
+     * @param jars
+     */
+    public FakeURLClassLoader(ClassLoader osgiClassLoader, URL[] jars)
+    {
+        super(new URL[] {},osgiClassLoader);
+        _jars = jars;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the jars that contains tlds so that TldLocationsCache or
+     *         TldScanner can find them.
+     */
+    @Override
+    public URL[] getURLs()
+    {
+        return _jars;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+
+        if (_jars != null)
+        {
+            for (URL u:_jars)
+                builder.append(" "+u.toString());
+            return builder.toString();
+        }
+        else
+            return super.toString();
+    }
+}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java
index 8850f5e..91f42af 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java
@@ -78,8 +78,7 @@
         }
 
         if (url == null)
-        {
-            
+        {           
             url = _osgiBundleClassLoader.getResource(name);
 
             if (url == null && name.startsWith("/"))
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/TldBundleDiscoverer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/TldBundleDiscoverer.java
new file mode 100644
index 0000000..bdd6168
--- /dev/null
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/TldBundleDiscoverer.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.boot.utils;
+
+import java.net.URL;
+
+import org.eclipse.jetty.deploy.DeploymentManager;
+
+
+/**
+ * TldBundleDiscoverer
+ * 
+ * Convert bundles that contain tlds into URL locations for consumption by jasper.
+ */
+public interface TldBundleDiscoverer
+{
+    /**
+     * Find bundles that contain tlds and convert into URL references to their location.
+     * 
+     * @param manager
+     * @param fileLocator
+     * @return array of URLs representing locations of tld containing bundles
+     * @throws Exception
+     */
+    URL[] getUrlsForBundlesWithTlds(DeploymentManager manager, BundleFileLocatorHelper fileLocator) throws Exception;
+
+}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java
deleted file mode 100644
index 813bff4..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java
+++ /dev/null
@@ -1,61 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.utils;
-
-import java.net.URL;
-
-import org.eclipse.jetty.deploy.DeploymentManager;
-
-
-/**
- * Fix various shortcomings with the way jasper parses the tld files.
- */
-public interface WebappRegistrationCustomizer
-{
-    /**
-     * we could do something a lot more pluggable with a custom header in the
-     * manifest or some customer declarative services let's keep it simple for
-     * now. hopefully the rest of the world won't need to customize this.
-     */
-    public static final String CLASS_NAME = "org.eclipse.jetty.osgi.boot.jasper.WebappRegistrationCustomizerImpl";
-
-    /**
-     * TODO: right now only the jetty-jsp bundle is scanned for common taglibs.
-     * Should support a way to plug more bundles that contain taglibs.
-     * 
-     * The jasper TldScanner expects a URLClassloader to parse a jar for the
-     * /META-INF/*.tld it may contain. We place the bundles that we know contain
-     * such tag-libraries. Please note that it will work if and only if the
-     * bundle is a jar (!) Currently we just hardcode the bundle that contains
-     * the jstl implemenation.
-     * 
-     * A workaround when the tld cannot be parsed with this method is to copy
-     * and paste it inside the WEB-INF of the webapplication where it is used.
-     * 
-     * Support only 2 types of packaging for the bundle: - the bundle is a jar
-     * (recommended for runtime.) - the bundle is a folder and contain jars in
-     * the root and/or in the lib folder (nice for PDE developement situations)
-     * Unsupported: the bundle is a jar that embeds more jars.
-     * 
-     * @return array of URLs
-     * @throws Exception
-     */
-    URL[] getJarsWithTlds(DeploymentManager manager, BundleFileLocatorHelper fileLocator) throws Exception;
-
-}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java
index 9bb074a..ee0d7c2 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java
@@ -28,6 +28,9 @@
 import org.osgi.framework.Bundle;
 
 /**
+ * DefaultBundleClassLoaderHelper
+ * 
+ * 
  * Default implementation of the BundleClassLoaderHelper. Uses introspection to
  * support equinox-3.5 and felix-2.0.0
  */
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java
index 2d31459..7349cd0 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java
@@ -31,11 +31,14 @@
 
 import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
 import org.eclipse.jetty.util.URIUtil;
-import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.util.resource.FileResource;
+import org.eclipse.jetty.util.resource.Resource;
 import org.osgi.framework.Bundle;
 
 /**
+ * DefaultFileLocatorHelper
+ * 
+ * 
  * From a bundle to its location on the filesystem. Assumes the bundle is not a
  * jar.
  * 
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/PackageAdminServiceTracker.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/PackageAdminServiceTracker.java
index 384b392..7ce7551 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/PackageAdminServiceTracker.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/PackageAdminServiceTracker.java
@@ -35,8 +35,14 @@
 import org.osgi.service.startlevel.StartLevel;
 
 /**
+ * PackageAdminServiceTracker
+ * 
+ * 
  * When the PackageAdmin service is activated we can look for the fragments
- * attached to this bundle and "activate" them.
+ * attached to this bundle and do a fake "activate" on them.
+ * 
+ * See particularly the jetty-osgi-boot-jsp fragment bundle that uses this
+ * facility.
  */
 public class PackageAdminServiceTracker implements ServiceListener
 {
diff --git a/jetty-server/src/main/config/etc/jetty.xml b/jetty-server/src/main/config/etc/jetty.xml
index 2d9b859..668980e 100644
--- a/jetty-server/src/main/config/etc/jetty.xml
+++ b/jetty-server/src/main/config/etc/jetty.xml
@@ -47,11 +47,6 @@
         <Arg name="minThreads" type="int">10</Arg>
         <Arg name="maxThreads" type="int">200</Arg>
         <Arg name="idleTimeout" type="int">60000</Arg>
-        <!-- Arg >
-          <New class="org.eclipse.jetty.util.ConcurrentArrayBlockingQueue$Unbounded">
-            <Arg type='int'>32</Arg>
-          </New>
-        </Arg --> 
         <Set name="detailedDump">false</Set>
       </New>
     </Arg>
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
index 2fced56..28fea90 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
@@ -289,8 +289,9 @@
 
         // If we have a stop timeout
         long stopTimeout = getStopTimeout();
-        if (stopTimeout > 0 && _stopping!=null)
-            _stopping.await(stopTimeout,TimeUnit.MILLISECONDS);
+        CountDownLatch stopping=_stopping;
+        if (stopTimeout > 0 && stopping!=null)
+            stopping.await(stopTimeout,TimeUnit.MILLISECONDS);
         _stopping=null;
 
         super.doStop();
@@ -475,7 +476,9 @@
                 {
                     _acceptors[_acceptor] = null;
                 }
-                _stopping.countDown();
+                CountDownLatch stopping=_stopping;
+                if (stopping!=null)
+                    stopping.countDown();
             }
         }
     }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncNCSARequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncNCSARequestLog.java
index 047e3b6..9c5d461 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncNCSARequestLog.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncNCSARequestLog.java
@@ -22,7 +22,7 @@
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.TimeUnit;
 
-import org.eclipse.jetty.util.ConcurrentArrayBlockingQueue;
+import org.eclipse.jetty.util.BlockingArrayQueue;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -42,7 +42,7 @@
     {
         this(null,null);
     }
-    
+
     public AsyncNCSARequestLog(BlockingQueue<String> queue)
     {
         this(null,queue);
@@ -52,12 +52,12 @@
     {
         this(filename,null);
     }
-    
+
     public AsyncNCSARequestLog(String filename,BlockingQueue<String> queue)
     {
         super(filename);
         if (queue==null)
-            queue=new ConcurrentArrayBlockingQueue.Bounded<String>(1024);
+            queue=new BlockingArrayQueue<>(1024);
         _queue=queue;
     }
 
@@ -67,7 +67,7 @@
         {
             setName("AsyncNCSARequestLog@"+Integer.toString(AsyncNCSARequestLog.this.hashCode(),16));
         }
-        
+
         @Override
         public void run()
         {
@@ -78,7 +78,7 @@
                     String log = _queue.poll(10,TimeUnit.SECONDS);
                     if (log!=null)
                         AsyncNCSARequestLog.super.write(log);
-                    
+
                     while(!_queue.isEmpty())
                     {
                         log=_queue.poll();
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
index 6d4f9e1..e789d38 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
@@ -146,6 +146,7 @@
         {
             if (isAlive())
             {
+                // TODO why are we reentrant here?
                 if (DEBUG)
                     System.err.printf("ShutdownMonitorThread already started");
                 return; // cannot start it again
@@ -353,7 +354,9 @@
         {
             if (thread != null && thread.isAlive())
             {
-                System.err.printf("ShutdownMonitorThread already started");
+                // TODO why are we reentrant here?
+                if (DEBUG)
+                    System.err.printf("ShutdownMonitorThread already started");
                 return; // cannot start it again
             }
          
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
index 3d5fad4..95d307c 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
@@ -303,7 +303,8 @@
      *
      * @param virtualHosts
      *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
-     *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
+     *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names. Host names may start with '@', in which case they
+     *            will match the {@link Connector#getName()} for the request.
      */
     public void addVirtualHosts(String[] virtualHosts)
     {
@@ -817,8 +818,8 @@
             if (!_contextListeners.isEmpty())
             {
                 ServletContextEvent event = new ServletContextEvent(_scontext);
-                for (ServletContextListener listener : _contextListeners)
-                    callContextDestroyed(listener,event);
+                for (int i = _contextListeners.size(); i-->0;) 
+                    callContextDestroyed(_contextListeners.get(i),event);
             }
 
             if (_errorHandler != null)
@@ -1093,20 +1094,15 @@
                 if (!_requestListeners.isEmpty())
                 {
                     final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
-                    ListIterator<ServletRequestListener> iter = _requestListeners.listIterator(_requestListeners.size());
-                    while (iter.hasNext())
-                        iter.next();
-                    while (iter.hasPrevious())
-                        iter.previous().requestDestroyed(sre);
+                    for (int i=_requestListeners.size();i-->0;)
+                        _requestListeners.get(i).requestDestroyed(sre);
                 }
 
                 if (!_requestAttributeListeners.isEmpty())
                 {
                     ListIterator<ServletRequestAttributeListener> iter = _requestAttributeListeners.listIterator(_requestAttributeListeners.size());
-                    while(iter.hasNext())
-                        iter.next();
-                    while(iter.hasPrevious())
-                        baseRequest.removeEventListener(iter.previous());
+                    for (int i=_requestAttributeListeners.size();i-->0;)
+                        baseRequest.removeEventListener(_requestAttributeListeners.get(i));
                 }
             }
         }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/LowResourcesMonitorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/LowResourcesMonitorTest.java
index 365a85b..eac1057 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/LowResourcesMonitorTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/LowResourcesMonitorTest.java
@@ -80,13 +80,14 @@
     @Test
     public void testLowOnThreads() throws Exception
     {
+        Thread.sleep(1200);
         _threadPool.setMaxThreads(_threadPool.getThreads()-_threadPool.getIdleThreads()+10);
         Thread.sleep(1200);
         Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());
         
         final CountDownLatch latch = new CountDownLatch(1);
         
-        for (int i=0;i<20;i++)
+        for (int i=0;i<100;i++)
         {
             _threadPool.dispatch(new Runnable()
             {
@@ -110,7 +111,6 @@
         
         latch.countDown();
         Thread.sleep(1200);
-        System.err.println(_threadPool.dump());
         Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());      
     }
     
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java
index bd5a21b..03752cb 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java
@@ -26,26 +26,22 @@
 import java.util.regex.Pattern;
 import java.util.zip.Deflater;
 import java.util.zip.DeflaterOutputStream;
-import java.util.zip.GZIPOutputStream;
 
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
-import javax.servlet.ServletResponseWrapper;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
 
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.continuation.ContinuationSupport;
 import org.eclipse.jetty.http.HttpMethod;
 import org.eclipse.jetty.servlets.gzip.AbstractCompressedStream;
 import org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper;
-import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.servlets.gzip.GzipOutputStream;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -129,6 +125,9 @@
     protected int _minGzipSize=256;
     protected int _deflateCompressionLevel=Deflater.DEFAULT_COMPRESSION;
     protected boolean _deflateNoWrap = true;
+    
+    // non-static, as other GzipFilter instances may have different configurations
+    protected final ThreadLocal<Deflater> _deflater = new ThreadLocal<Deflater>();
 
     protected final Set<String> _methods=new HashSet<String>();
     protected Set<String> _excludedAgents;
@@ -296,10 +295,10 @@
         }
         finally
         {
-            Continuation continuation = ContinuationSupport.getContinuation(request);
-                if (continuation.isSuspended() && continuation.isResponseWrapped())
+            if (request.isAsyncStarted())
             {
-                continuation.addContinuationListener(new ContinuationListenerWaitingForWrappedResponseToFinish(wrappedResponse));
+                 
+                request.getAsyncContext().addListener(new FinishOnCompleteListener(wrappedResponse));
             }
             else if (exceptional && !response.isCommitted())
             {
@@ -403,64 +402,55 @@
     protected CompressedResponseWrapper createWrappedResponse(HttpServletRequest request, HttpServletResponse response, final String compressionType)
     {
         CompressedResponseWrapper wrappedResponse = null;
-        if (compressionType==null)
+        wrappedResponse = new CompressedResponseWrapper(request,response)
         {
-            wrappedResponse = new CompressedResponseWrapper(request,response)
+            @Override
+            protected AbstractCompressedStream newCompressedStream(HttpServletRequest request, HttpServletResponse response) throws IOException
             {
-                @Override
-                protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
+                return new AbstractCompressedStream(compressionType,request,this,_vary)
                 {
-                    return new AbstractCompressedStream(null,request,this,_vary)
+                    private Deflater _allocatedDeflater;
+
+                    @Override
+                    protected DeflaterOutputStream createStream() throws IOException
                     {
-                        @Override
-                        protected DeflaterOutputStream createStream() throws IOException
+                        if (compressionType == null)
                         {
                             return null;
                         }
-                    };
-                }
-            };
-        }
-        else if (compressionType.equals(GZIP))
-        {
-            wrappedResponse = new CompressedResponseWrapper(request,response)
-            {
-                @Override
-                protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
-                {
-                    return new AbstractCompressedStream(compressionType,request,this,_vary)
-                    {
-                        @Override
-                        protected DeflaterOutputStream createStream() throws IOException
+                        
+                        // acquire deflater instance
+                        _allocatedDeflater = _deflater.get();   
+                        if (_allocatedDeflater==null)
+                            _allocatedDeflater = new Deflater(_deflateCompressionLevel,_deflateNoWrap);
+                        else
                         {
-                            return new GZIPOutputStream(_response.getOutputStream(),_bufferSize);
+                            _deflater.remove();
+                            _allocatedDeflater.reset();
                         }
-                    };
-                }
-            };
-        }
-        else if (compressionType.equals(DEFLATE))
-        {
-            wrappedResponse = new CompressedResponseWrapper(request,response)
-            {
-                @Override
-                protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
-                {
-                    return new AbstractCompressedStream(compressionType,request,this,_vary)
-                    {
-                        @Override
-                        protected DeflaterOutputStream createStream() throws IOException
+                        
+                        switch (compressionType)
                         {
-                            return new DeflaterOutputStream(_response.getOutputStream(),new Deflater(_deflateCompressionLevel,_deflateNoWrap));
+                            case GZIP:
+                                return new GzipOutputStream(_response.getOutputStream(),_allocatedDeflater,_bufferSize);
+                            case DEFLATE:
+                                return new DeflaterOutputStream(_response.getOutputStream(),_allocatedDeflater,_bufferSize);
                         }
-                    };
-                }
-            };
-        } 
-        else
-        {
-            throw new IllegalStateException(compressionType + " not supported");
-        }
+                        throw new IllegalStateException(compressionType + " not supported");
+                    }
+
+                    @Override
+                    public void finish() throws IOException
+                    {
+                        super.finish();
+                        if (_allocatedDeflater != null && _deflater.get() == null)
+                        {
+                            _deflater.set(_allocatedDeflater);
+                        }
+                    }
+                };
+            }
+        };
         configureWrappedResponse(wrappedResponse);
         return wrappedResponse;
     }
@@ -472,18 +462,18 @@
         wrappedResponse.setMinCompressSize(_minGzipSize);
     }
 
-    private class ContinuationListenerWaitingForWrappedResponseToFinish implements ContinuationListener
+    private class FinishOnCompleteListener implements AsyncListener
     {    
         private CompressedResponseWrapper wrappedResponse;
 
-        public ContinuationListenerWaitingForWrappedResponseToFinish(CompressedResponseWrapper wrappedResponse)
+        public FinishOnCompleteListener(CompressedResponseWrapper wrappedResponse)
         {
             this.wrappedResponse = wrappedResponse;
         }
 
         @Override
-        public void onComplete(Continuation continuation)
-        {
+        public void onComplete(AsyncEvent event) throws IOException
+        {          
             try
             {
                 wrappedResponse.finish();
@@ -495,7 +485,17 @@
         }
 
         @Override
-        public void onTimeout(Continuation continuation)
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
         {
         }
     }
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java
index 3ea1c2a..ec125b9 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java
@@ -29,13 +29,12 @@
 import java.util.zip.DeflaterOutputStream;
 import java.util.zip.GZIPOutputStream;
 
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.continuation.ContinuationSupport;
 import org.eclipse.jetty.http.HttpMethod;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.handler.HandlerWrapper;
@@ -265,12 +264,28 @@
                 }
                 finally
                 {
-                    Continuation continuation = ContinuationSupport.getContinuation(request);
-                    if (continuation.isSuspended() && continuation.isResponseWrapped())
+                    if (request.isAsyncStarted())
                     {
-                        continuation.addContinuationListener(new ContinuationListener()
+                        request.getAsyncContext().addListener(new AsyncListener()
                         {
-                            public void onComplete(Continuation continuation)
+                            
+                            @Override
+                            public void onTimeout(AsyncEvent event) throws IOException
+                            {
+                            }
+                            
+                            @Override
+                            public void onStartAsync(AsyncEvent event) throws IOException
+                            {
+                            }
+                            
+                            @Override
+                            public void onError(AsyncEvent event) throws IOException
+                            {
+                            }
+                            
+                            @Override
+                            public void onComplete(AsyncEvent event) throws IOException
                             {
                                 try
                                 {
@@ -281,9 +296,6 @@
                                     LOG.warn(e);
                                 }
                             }
-
-                            public void onTimeout(Continuation continuation)
-                            {}
                         });
                     }
                     else if (exceptional && !response.isCommitted())
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipOutputStream.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipOutputStream.java
new file mode 100644
index 0000000..8f20c2f
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipOutputStream.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets.gzip;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.CRC32;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+/**
+ * Reimplementation of {@link java.util.zip.GZIPOutputStream} that supports reusing a {@link Deflater} instance.
+ */
+public class GzipOutputStream extends DeflaterOutputStream
+{
+
+    private final static byte[] GZIP_HEADER = new byte[]
+    { (byte)0x1f, (byte)0x8b, Deflater.DEFLATED, 0, 0, 0, 0, 0, 0, 0 };
+
+    private final CRC32 _crc = new CRC32();
+
+    public GzipOutputStream(OutputStream out, Deflater deflater, int size) throws IOException
+    {
+        super(out,deflater,size);
+        out.write(GZIP_HEADER);
+    }
+
+    public synchronized void write(byte[] buf, int off, int len) throws IOException
+    {
+        super.write(buf,off,len);
+        _crc.update(buf,off,len);
+    }
+
+    public void finish() throws IOException
+    {
+        if (!def.finished())
+        {
+            super.finish();
+            byte[] trailer = new byte[8];
+            writeInt((int)_crc.getValue(),trailer,0);
+            writeInt(def.getTotalIn(),trailer,4);
+            out.write(trailer);
+        }
+    }
+
+    private void writeInt(int i, byte[] buf, int offset)
+    {
+        int o = offset;
+        buf[o++] = (byte)(i & 0xFF);
+        buf[o++] = (byte)((i >>> 8) & 0xFF);
+        buf[o++] = (byte)((i >>> 16) & 0xFF);
+        buf[o++] = (byte)((i >>> 24) & 0xFF);
+    }
+
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PipelineHelper.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PipelineHelper.java
index 8e2fea7..95f0a23 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PipelineHelper.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PipelineHelper.java
@@ -219,8 +219,8 @@
             int val = inputStream.read();
             try
             {
-                if (left % 10 == 0)
-                    Thread.sleep(1);
+                if (left % 1000 == 0)
+                    Thread.sleep(10);
             }
             catch (InterruptedException e)
             {
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java
index f9296ce..61ff30a 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java
@@ -18,7 +18,6 @@
 
 package org.eclipse.jetty.servlets;
 
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
@@ -42,7 +41,9 @@
 import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
+import org.hamcrest.Matchers;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -91,8 +92,10 @@
 
         _doneRequests.await(10,TimeUnit.SECONDS);
 
-        assertFalse("TEST WAS NOT PARALLEL ENOUGH!",TestServlet.__maxSleepers<=MAX_QOS);
-        assertTrue(TestServlet.__maxSleepers<=NUM_CONNECTIONS);
+        if (TestServlet.__maxSleepers<=MAX_QOS)
+            LOG.warn("TEST WAS NOT PARALLEL ENOUGH!");
+        else
+            Assert.assertThat(TestServlet.__maxSleepers,Matchers.lessThanOrEqualTo(NUM_CONNECTIONS));
     }
 
     @Test
@@ -109,8 +112,10 @@
         }
 
         _doneRequests.await(10,TimeUnit.SECONDS);
-        assertFalse("TEST WAS NOT PARALLEL ENOUGH!",TestServlet.__maxSleepers<MAX_QOS);
-        assertTrue(TestServlet.__maxSleepers==MAX_QOS);
+        if (TestServlet.__maxSleepers<MAX_QOS)
+            LOG.warn("TEST WAS NOT PARALLEL ENOUGH!");
+        else
+            Assert.assertEquals(TestServlet.__maxSleepers,MAX_QOS);
     }
 
     @Test
@@ -126,8 +131,10 @@
         }
 
         _doneRequests.await(20,TimeUnit.SECONDS);
-        assertFalse("TEST WAS NOT PARALLEL ENOUGH!",TestServlet.__maxSleepers<MAX_QOS);
-        assertTrue(TestServlet.__maxSleepers<=MAX_QOS);
+        if (TestServlet.__maxSleepers<MAX_QOS)
+            LOG.warn("TEST WAS NOT PARALLEL ENOUGH!");
+        else
+            Assert.assertEquals(TestServlet.__maxSleepers,MAX_QOS);
     }
 
     class Worker implements Runnable {
@@ -137,6 +144,7 @@
             _num = num;
         }
 
+        @Override
         public void run()
         {
             for (int i=0;i<NUM_LOOPS;i++)
@@ -170,6 +178,7 @@
             _num = num;
         }
 
+        @Override
         public void run()
         {
             URL url=null;
@@ -198,6 +207,7 @@
         private static int __sleepers;
         private static int __maxSleepers;
 
+        @Override
         protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
         {
             try
@@ -232,6 +242,7 @@
 
     public static class QoSFilter2 extends QoSFilter
     {
+        @Override
         public int getPriority(ServletRequest request)
         {
             String p = request.getParameter("priority");
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
index c642050..30dc870 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
@@ -110,7 +110,9 @@
     private final AtomicBoolean goAwaySent = new AtomicBoolean();
     private final AtomicBoolean goAwayReceived = new AtomicBoolean();
     private final AtomicInteger lastStreamId = new AtomicInteger();
+    private final AtomicInteger localStreamCount = new AtomicInteger(0);
     private final FlowControlStrategy flowControlStrategy;
+    private volatile int maxConcurrentLocalStreams = -1;
     private boolean flushing;
     private Throwable failure;
 
@@ -181,6 +183,8 @@
             // TODO: for SPDYv3 we need to support the "slot" argument
             SynStreamFrame synStream = new SynStreamFrame(version, synInfo.getFlags(), streamId, associatedStreamId, synInfo.getPriority(), (short)0, synInfo.getHeaders());
             IStream stream = createStream(synStream, listener, true, promise);
+            if (stream == null)
+                return;
             generateAndEnqueueControlFrame(stream, synStream, synInfo.getTimeout(), synInfo.getUnit(), stream);
         }
         flush();
@@ -240,7 +244,6 @@
     @Override
     public PingResultInfo ping(PingInfo pingInfo) throws ExecutionException, InterruptedException, TimeoutException
     {
-        //TODO: find a better name for PingResultInfo
         FuturePromise<PingResultInfo> result = new FuturePromise<>();
         ping(pingInfo, result);
         if (pingInfo.getTimeout() > 0)
@@ -535,27 +538,45 @@
         }
 
         int streamId = stream.getId();
+
+        if (local)
+        {
+            while (true)
+            {
+                int oldStreamCountValue = localStreamCount.get();
+                int maxConcurrentStreams = maxConcurrentLocalStreams;
+                if (maxConcurrentStreams > -1 && oldStreamCountValue >= maxConcurrentStreams)
+                {
+                    String message = String.format("Max concurrent local streams (%d) exceeded.",
+                            maxConcurrentStreams);
+                    LOG.debug(message);
+                    promise.failed(new SPDYException(message));
+                    return null;
+                }
+                if (localStreamCount.compareAndSet(oldStreamCountValue, oldStreamCountValue + 1))
+                    break;
+            }
+        }
+
         if (streams.putIfAbsent(streamId, stream) != null)
         {
+            String message = "Duplicate stream id " + streamId;
+            IllegalStateException duplicateIdException = new IllegalStateException(message);
+            promise.failed(duplicateIdException);
             if (local)
-                throw new IllegalStateException("Duplicate stream id " + streamId);
+            {
+                localStreamCount.decrementAndGet();
+                throw duplicateIdException;
+            }
             RstInfo rstInfo = new RstInfo(streamId, StreamStatus.PROTOCOL_ERROR);
             LOG.debug("Duplicate stream, {}", rstInfo);
-            try
-            {
-                rst(rstInfo);
-            }
-            catch (InterruptedException | ExecutionException | TimeoutException e)
-            {
-                e.printStackTrace(); // TODO: really catch???
-            }
+            rst(rstInfo, new Callback.Adapter()); // We don't care (too much) if the reset fails.
             return null;
         }
         else
         {
             LOG.debug("Created {}", stream);
-            if (local)
-                notifyStreamCreated(stream);
+            notifyStreamCreated(stream);
             return stream;
         }
     }
@@ -590,10 +611,15 @@
 
         IStream removed = streams.remove(stream.getId());
         if (removed != null)
+        {
             assert removed == stream;
 
-        LOG.debug("Removed {}", stream);
-        notifyStreamClosed(stream);
+            if (streamIds.get() % 2 == stream.getId() % 2)
+                localStreamCount.decrementAndGet();
+
+            LOG.debug("Removed {}", stream);
+            notifyStreamClosed(stream);
+        }
     }
 
     private void notifyStreamClosed(IStream stream)
@@ -666,6 +692,13 @@
             setWindowSize(windowSize);
             LOG.debug("Updated session window size to {}", windowSize);
         }
+        Settings.Setting maxConcurrentStreamsSetting = frame.getSettings().get(Settings.ID.MAX_CONCURRENT_STREAMS);
+        if (maxConcurrentStreamsSetting != null)
+        {
+            int maxConcurrentStreamsValue = maxConcurrentStreamsSetting.value();
+            maxConcurrentLocalStreams = maxConcurrentStreamsValue;
+            LOG.debug("Updated session maxConcurrentLocalStreams to {}", maxConcurrentStreamsValue);
+        }
         SettingsInfo settingsInfo = new SettingsInfo(frame.getSettings(), frame.isClearPersisted());
         notifyOnSettings(listener, settingsInfo);
         flush();
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardSessionTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardSessionTest.java
index 44e56ff..813fb0c 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardSessionTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardSessionTest.java
@@ -18,15 +18,6 @@
 
 package org.eclipse.jetty.spdy;
 
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
 import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
 import java.util.HashSet;
@@ -47,12 +38,14 @@
 import org.eclipse.jetty.spdy.api.RstInfo;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Settings;
 import org.eclipse.jetty.spdy.api.Stream;
 import org.eclipse.jetty.spdy.api.StreamFrameListener;
 import org.eclipse.jetty.spdy.api.StreamStatus;
 import org.eclipse.jetty.spdy.api.StringDataInfo;
 import org.eclipse.jetty.spdy.api.SynInfo;
 import org.eclipse.jetty.spdy.frames.DataFrame;
+import org.eclipse.jetty.spdy.frames.SettingsFrame;
 import org.eclipse.jetty.spdy.frames.SynReplyFrame;
 import org.eclipse.jetty.spdy.frames.SynStreamFrame;
 import org.eclipse.jetty.spdy.generator.Generator;
@@ -74,6 +67,15 @@
 import org.mockito.runners.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
 
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
 @RunWith(MockitoJUnitRunner.class)
 public class StandardSessionTest
 {
@@ -457,7 +459,30 @@
         stream.headers(new HeadersInfo(headers, true));
 
         verify(controller, times(3)).write(any(ByteBuffer.class), any(Callback.class));
+    }
 
+    @Test
+    public void testMaxConcurrentStreams() throws InterruptedException
+    {
+        final CountDownLatch failedBecauseMaxConcurrentStreamsExceeded = new CountDownLatch(1);
+
+        Settings settings = new Settings();
+        settings.put(new Settings.Setting(Settings.ID.MAX_CONCURRENT_STREAMS, 0));
+        SettingsFrame settingsFrame = new SettingsFrame(VERSION, (byte)0, settings);
+        session.onControlFrame(settingsFrame);
+
+        PushSynInfo pushSynInfo = new PushSynInfo(1, new PushInfo(new Fields(), false));
+        session.syn(pushSynInfo, null, new Promise.Adapter<Stream>()
+        {
+            @Override
+            public void failed(Throwable x)
+            {
+                failedBecauseMaxConcurrentStreamsExceeded.countDown();
+            }
+        });
+
+        assertThat("Opening push stream failed because maxConcurrentStream is exceeded",
+                failedBecauseMaxConcurrentStreamsExceeded.await(5, TimeUnit.SECONDS), is(true));
     }
 
     @Test
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java
index f52c875..099f736 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java
@@ -101,14 +101,14 @@
     @Test
     public void testPushHeadersAreValid() throws Exception
     {
-        sendMainRequestAndCSSRequest();
+        sendMainRequestAndCSSRequest(null);
         run2ndClientRequests(true, true);
     }
 
     @Test
     public void testClientResetsPushStreams() throws Exception
     {
-        sendMainRequestAndCSSRequest();
+        sendMainRequestAndCSSRequest(null);
         final CountDownLatch pushDataLatch = new CountDownLatch(1);
         final CountDownLatch pushSynHeadersValid = new CountDownLatch(1);
         Session session = startClient(version, serverAddress, null);
@@ -125,14 +125,14 @@
     public void testUserAgentBlackList() throws Exception
     {
         pushStrategy.setUserAgentBlacklist(Arrays.asList(".*(?i)firefox/16.*"));
-        sendMainRequestAndCSSRequest();
+        sendMainRequestAndCSSRequest(null);
         run2ndClientRequests(false, false);
     }
 
     @Test
     public void testReferrerPushPeriod() throws Exception
     {
-        Session session = sendMainRequestAndCSSRequest();
+        Session session = sendMainRequestAndCSSRequest(null);
 
         // Sleep for pushPeriod This should prevent application.js from being mapped as pushResource
         Thread.sleep(referrerPushPeriod + 1);
@@ -148,13 +148,38 @@
         connector.addConnectionFactory(defaultFactory);
         connector.setDefaultProtocol(defaultFactory.getProtocol()); // TODO I don't think this is right
 
-        Session session = sendMainRequestAndCSSRequest();
+        Session session = sendMainRequestAndCSSRequest(null);
 
         sendRequest(session, associatedJSRequestHeaders, null, null);
 
         run2ndClientRequests(false, true);
     }
 
+    @Test
+    public void testMaxConcurrentStreamsToDisablePush() throws Exception
+    {
+        final CountDownLatch pushReceivedLatch = new CountDownLatch(1);
+        Session session = sendMainRequestAndCSSRequest(new SessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                pushReceivedLatch.countDown();
+                return null;
+            }
+        });
+
+//        Settings settings = new Settings();
+//        settings.put(new Settings.Setting(Settings.ID.MAX_CONCURRENT_STREAMS, 0));
+//        SettingsInfo settingsInfo = new SettingsInfo(settings);
+//
+//        session.settings(settingsInfo);
+
+        sendRequest(session, mainRequestHeaders, null, null);
+
+        assertThat(pushReceivedLatch.await(1, TimeUnit.SECONDS), is(false));
+    }
+
     private InetSocketAddress createServer() throws Exception
     {
         GzipHandler gzipHandler = new GzipHandler();
@@ -177,9 +202,9 @@
         return startHTTPServer(version, gzipHandler);
     }
 
-    private Session sendMainRequestAndCSSRequest() throws Exception
+    private Session sendMainRequestAndCSSRequest(SessionFrameListener sessionFrameListener) throws Exception
     {
-        Session session = startClient(version, serverAddress, null);
+        Session session = startClient(version, serverAddress, sessionFrameListener);
 
         sendRequest(session, mainRequestHeaders, null, null);
         sendRequest(session, associatedCSSRequestHeaders, null, null);
@@ -197,7 +222,8 @@
             @Override
             public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
             {
-                validateHeaders(pushInfo.getHeaders(), pushSynHeadersValid);
+                if (pushSynHeadersValid != null)
+                    validateHeaders(pushInfo.getHeaders(), pushSynHeadersValid);
 
                 assertThat("Stream is unidirectional", stream.isUnidirectional(), is(true));
                 assertThat("URI header ends with css", pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version))
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/AbstractTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/AbstractTest.java
index 08625cc..cdbe2ba 100644
--- a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/AbstractTest.java
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/AbstractTest.java
@@ -53,13 +53,15 @@
         }
     };
 
+    protected final short version = SPDY.V2;
+
     protected Server server;
     protected SPDYClient.Factory clientFactory;
     protected SPDYServerConnector connector;
 
     protected InetSocketAddress startServer(ServerSessionFrameListener listener) throws Exception
     {
-        return startServer(SPDY.V2, listener);
+        return startServer(version, listener);
     }
 
     protected InetSocketAddress startServer(short version, ServerSessionFrameListener listener) throws Exception
@@ -99,7 +101,7 @@
 
     protected Session startClient(InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception
     {
-        return startClient(SPDY.V2, socketAddress, listener);
+        return startClient(version, socketAddress, listener);
     }
 
     protected Session startClient(short version, InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/MaxConcurrentStreamTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/MaxConcurrentStreamTest.java
new file mode 100644
index 0000000..5c74ca3
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/MaxConcurrentStreamTest.java
@@ -0,0 +1,120 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.Settings;
+import org.eclipse.jetty.spdy.api.SettingsInfo;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Fields;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class MaxConcurrentStreamTest extends AbstractTest
+{
+    @Test
+    public void testMaxConcurrentStreamsSetByServer() throws Exception, ExecutionException
+    {
+        final CountDownLatch settingsReceivedLatch = new CountDownLatch(1);
+        final CountDownLatch dataReceivedLatch = new CountDownLatch(1);
+
+        Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public void onConnect(Session session)
+            {
+                Settings settings = new Settings();
+                settings.put(new Settings.Setting(Settings.ID.MAX_CONCURRENT_STREAMS, 1));
+                try
+                {
+                    session.settings(new SettingsInfo(settings));
+                }
+                catch (ExecutionException | InterruptedException | TimeoutException e)
+                {
+                    e.printStackTrace();
+                }
+            }
+
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                try
+                {
+                    stream.reply(new ReplyInfo(true));
+                }
+                catch (ExecutionException | InterruptedException | TimeoutException e)
+                {
+                    e.printStackTrace();
+                }
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        dataReceivedLatch.countDown();
+                    }
+                };
+            }
+        }), new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onSettings(Session session, SettingsInfo settingsInfo)
+            {
+                settingsReceivedLatch.countDown();
+            }
+        });
+
+        assertThat("Settings frame received", settingsReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+
+        SynInfo synInfo = new SynInfo(new Fields(), false);
+        Stream stream = session.syn(synInfo, null);
+
+        boolean failed = false;
+        try
+        {
+            session.syn(synInfo, null);
+        }
+        catch (ExecutionException | InterruptedException | TimeoutException e)
+        {
+            failed = true;
+        }
+
+        assertThat("Opening second stream failed", failed, is(true));
+
+        stream.data(new ByteBufferDataInfo(BufferUtil.EMPTY_BUFFER, true));
+        assertThat("Data has been received on first stream.", dataReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+
+        session.syn(synInfo, null);
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java
index dd2f53c..a9cb827 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java
@@ -57,16 +57,6 @@
         return get(s,0,s.length());
     }
 
-
-    @Override
-    public V get(ByteBuffer b, int offset, int len)
-    {
-        b=b.duplicate();
-        b.position(b.position()+offset);
-        b.limit(b.position()+len);
-        return get(BufferUtil.toString(b,StringUtil.__ISO_8859_1_CHARSET));
-    }
-
     @Override
     public V get(ByteBuffer b)
     {
@@ -86,15 +76,6 @@
     }
 
     @Override
-    public V getBest(ByteBuffer b, int offset, int len)
-    {
-        b=b.duplicate();
-        b.position(b.position()+offset);
-        b.limit(b.position()+len);
-        return getBest(BufferUtil.toString(b,StringUtil.__ISO_8859_1_CHARSET));
-    }
-
-    @Override
     public boolean isCaseInsensitive()
     {
         return _caseInsensitive;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java
index 7c92ac5..312bd96 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java
@@ -18,6 +18,7 @@
 
 package org.eclipse.jetty.util;
 
+import java.nio.ByteBuffer;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -194,6 +195,45 @@
     }
 
     
+    @Override
+    public V get(ByteBuffer b, int offset, int length)
+    {
+        int t = _tree[EQ];
+        int len = length;
+        int i=0;
+        offset+=b.position();
+        
+        while(i<len)
+        {
+            byte c=(byte)(b.get(offset+i++)&0x7f);
+            if(isCaseInsensitive())
+                c=(byte)StringUtil.lowercases[c];
+            
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    if (i==len)
+                        return (V)_value[t];
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        return null;
+                    break;
+                }
+
+                t=_tree[row+((diff<0)?LO:HI)];
+                if (t==0)
+                    return null;
+            }
+        }
+        
+        return null;
+    }
+
     /* ------------------------------------------------------------ */
     @Override
     public V getBest(String s)
@@ -248,6 +288,95 @@
     }
 
 
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(ByteBuffer b, int offset, int len)
+    {
+        if (b.hasArray())
+            return getBest(_tree[EQ],b.array(),b.arrayOffset()+b.position()+offset,len);
+        return getBest(_tree[EQ],b,offset,len);
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,byte[] b, int offset, int len)
+    {
+        int node=0;
+        for(int i=0; t!=0 && i<len; i++)
+        {
+            byte c=(byte)(b[offset+i]&0x7f);
+            if(isCaseInsensitive())
+                c=(byte)StringUtil.lowercases[c];
+
+            while (t!=0)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    node=t;
+                    t=_tree[row+EQ];
+                    
+                    // if this node is a match, recurse to remember 
+                    if (_key[node]!=null)
+                    {
+                        V best=getBest(t,b,offset+i+1,len-i-1);
+                        if (best!=null)
+                            return best;
+                        return (V)_value[node];
+                    }
+                    
+                    break;
+                }
+
+                t=_tree[row+((diff<0)?LO:HI)];
+            }
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,ByteBuffer b, int offset, int len)
+    {
+        int node=0;
+        int o= offset+b.position();
+        
+        for(int i=0; t!=0 && i<len; i++)
+        {
+            byte c=(byte)(b.get(o+i)&0x7f);
+            if(isCaseInsensitive())
+                c=(byte)StringUtil.lowercases[c];
+
+            while (t!=0)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    node=t;
+                    t=_tree[row+EQ];
+                    
+                    // if this node is a match, recurse to remember 
+                    if (_key[node]!=null)
+                    {
+                        V best=getBest(t,b,offset+i+1,len-i-1);
+                        if (best!=null)
+                            return best;
+                        return (V)_value[node];
+                    }
+                    
+                    break;
+                }
+
+                t=_tree[row+((diff<0)?LO:HI)];
+            }
+        }
+        return null;
+    }
+
     @Override
     public String toString()
     {
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
index dcfa977..6aa3287 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
@@ -89,9 +89,9 @@
         setMaxThreads(maxThreads);
         setIdleTimeout(idleTimeout);
         setStopTimeout(5000);
-        
+
         if (queue==null)
-            queue=new BlockingArrayQueue<Runnable>(_minThreads, _minThreads);// TODO ConcurrentArrayBlockingQueue.Unbounded<Runnable>();
+            queue=new BlockingArrayQueue<>(_minThreads, _minThreads);
         _jobs=queue;
 
     }
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/QueueBenchmarkTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/QueueBenchmarkTest.java
new file mode 100644
index 0000000..750c288
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/QueueBenchmarkTest.java
@@ -0,0 +1,224 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.toolchain.test.annotation.Stress;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AdvancedRunner.class)
+public class QueueBenchmarkTest
+{
+    private static final Logger logger = Log.getLogger(QueueBenchmarkTest.class);
+    private static final Runnable ELEMENT = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+        }
+    };
+    private static final Runnable END = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+        }
+    };
+
+    @Stress("High CPU")
+    @Test
+    public void testQueues() throws Exception
+    {
+        int cores = Runtime.getRuntime().availableProcessors();
+        Assume.assumeTrue(cores > 1);
+
+        final int readers = cores / 2;
+        final int writers = readers;
+        final int iterations = 16 * 1024 * 1024;
+
+        final List<Queue<Runnable>> queues = new ArrayList<>();
+        queues.add(new ConcurrentArrayQueue<Runnable>()); // Jetty lock-free queue, allocating array blocks
+        queues.add(new ConcurrentLinkedQueue<Runnable>()); // JDK lock-free queue, allocating nodes
+        queues.add(new ArrayBlockingQueue<Runnable>(iterations * writers)); // JDK lock-based, circular array queue
+        queues.add(new BlockingArrayQueue<Runnable>(iterations * writers)); // Jetty lock-based, circular array queue
+
+        testQueues(readers, writers, iterations, queues, false);
+    }
+
+    @Stress("High CPU")
+    @Test
+    public void testBlockingQueues() throws Exception
+    {
+        int cores = Runtime.getRuntime().availableProcessors();
+        Assume.assumeTrue(cores > 1);
+
+        final int readers = cores / 2;
+        final int writers = readers;
+        final int iterations = 16 * 1024 * 1024;
+
+        final List<Queue<Runnable>> queues = new ArrayList<>();
+        queues.add(new ConcurrentArrayBlockingQueue.Unbounded<Runnable>());
+        queues.add(new ConcurrentArrayBlockingQueue.Bounded<Runnable>(iterations * writers));
+        queues.add(new LinkedBlockingQueue<Runnable>());
+        queues.add(new ArrayBlockingQueue<Runnable>(iterations * writers));
+        queues.add(new BlockingArrayQueue<Runnable>(iterations * writers));
+
+        testQueues(readers, writers, iterations, queues, true);
+    }
+
+    private void testQueues(final int readers, final int writers, final int iterations, List<Queue<Runnable>> queues, final boolean blocking) throws Exception
+    {
+        final int runs = 8;
+        int threads = readers + writers;
+        final CyclicBarrier barrier = new CyclicBarrier(threads + 1);
+
+        for (final Queue<Runnable> queue : queues)
+        {
+            for (int r = 0; r < runs; ++r)
+            {
+                for (int i = 0; i < readers; ++i)
+                {
+                    Thread thread = new Thread()
+                    {
+                        @Override
+                        public void run()
+                        {
+                            await(barrier);
+                            consume(queue, writers, blocking);
+                            await(barrier);
+                        }
+                    };
+                    thread.start();
+                }
+                for (int i = 0; i < writers; ++i)
+                {
+                    Thread thread = new Thread()
+                    {
+                        @Override
+                        public void run()
+                        {
+                            await(barrier);
+                            produce(queue, readers, iterations);
+                            await(barrier);
+                        }
+                    };
+                    thread.start();
+                }
+
+                await(barrier);
+                long begin = System.nanoTime();
+                await(barrier);
+                long end = System.nanoTime();
+                long elapsed = TimeUnit.NANOSECONDS.toMillis(end - begin);
+                logger.info("{} Readers/Writers: {}/{} => {} ms", queue.getClass().getSimpleName(), readers, writers, elapsed);
+            }
+        }
+    }
+
+    private static void consume(Queue<Runnable> queue, int writers, boolean blocking)
+    {
+        while (true)
+        {
+            Runnable element = blocking ? take(queue) : poll(queue);
+            if (element == END)
+                if (--writers == 0)
+                    break;
+        }
+    }
+
+    private static void produce(Queue<Runnable> queue, int readers, int iterations)
+    {
+        for (int i = 0; i < iterations; ++i)
+            append(queue, ELEMENT);
+        for (int i = 0; i < readers; ++i)
+            append(queue, END);
+    }
+
+    private static void append(Queue<Runnable> queue, Runnable element)
+    {
+        if (!queue.offer(element))
+            logger.warn("Queue {} capacity is too small", queue);
+    }
+
+    private static Runnable take(Queue<Runnable> queue)
+    {
+        try
+        {
+            return ((BlockingQueue<Runnable>)queue).take();
+        }
+        catch (InterruptedException x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+
+    private static Runnable poll(Queue<Runnable> queue)
+    {
+        int loops = 0;
+        while (true)
+        {
+            Runnable element = queue.poll();
+            if (element != null)
+                return element;
+            // Busy loop
+            sleepMicros(1);
+            ++loops;
+            if (loops % 16 == 0)
+                logger.warn("Spin looping while polling empty queue: {} spins: ", loops);
+        }
+    }
+
+    private static void sleepMicros(long sleep)
+    {
+        try
+        {
+            TimeUnit.MICROSECONDS.sleep(sleep);
+        }
+        catch (InterruptedException x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+
+    private static void await(CyclicBarrier barrier)
+    {
+        try
+        {
+            barrier.await();
+        }
+        catch (Exception x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+}
diff --git a/pom.xml b/pom.xml
index bf4d128..5b0ccf0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -284,7 +284,7 @@
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-surefire-plugin</artifactId>
           <configuration>
-            <argLine>-showversion -XX:+PrintGCDetails</argLine>
+            <argLine>-showversion -Xmx1g -Xms1g -XX:+PrintGCDetails</argLine>
             <failIfNoTests>false</failIfNoTests>
             <!--systemProperties>
               <property>
@@ -702,7 +702,7 @@
                       })();
                    </script>
                 ]]>
-              </header>           
+              </header>
             </configuration>
           </plugin>
         </plugins>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.xml
index 1bef8ab..ff7eb6b 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.xml
+++ b/tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.xml
@@ -45,7 +45,10 @@
   <!-- virtual hosts
   <Set name="virtualHosts">
     <Array type="String">
-      <Item>www.myVirtualDomain.com</Item>
+      <Item>www.MyVirtualDomain.com</Item>
+      <Item>m.MyVirtualDomain.com</Item>
+      <Item>*.OtherVirtualDomain.com</Item>
+      <Item>@ConnectorName</Item>
       <Item>localhost</Item>
       <Item>127.0.0.1</Item>
     </Array>